18

I am building a throwaway application in MVC 6 and experimenting with different architectures for dependencies.

The problem I am facing is how to create a custom 'MyAppContext' object specific to the Application. This would require some information from the HttpContext and some information from the database, and will be a request-scoped repository for application specific attributes. I want to pass the instance of the HttpContext into the constructor of the 'MyAppContext'.

I have successfully created a 'DataService' object with an IDataService interface using DI and this works Ok. The difference with the 'MyAppContext' class is that it has two parameters in the constructor - the 'DataService' and the Microsoft.AspNet.Http.HttpContext. Here is the MyAppContext class:

public class MyAppContext : IMyAppContext
{
    public MyAppContext(IDataService dataService, HttpContext httpContext)
    {
       //do stuff here with the httpContext
    }
}

In the startup code, I register the DataService instance and the MyAppContext instance:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        //adds a singleton instance of the DataService using DI
        services.AddSingleton<IDataService, DataService>();
        services.AddScoped<IMyAppContext, MyAppContext>();    

    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseErrorPage();
        app.UseRequestServices();
        app.UseMvc(routes => /* routes stuff */);
    }

I am expecting the HttpContext parameter in the constructor to get resolved by DI. When running the code, this is the exception I get returned:

InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNet.Http.HttpContext' while attempting to activate 'MyAppContext'

I figure this is because there is no specific instance of HttpContext that this error is occurring, but I don't know how to register the HttpContext instance in DI. I added the line 'app.UseRequestServices();' but this hasn't made any difference. I also tried a variant of:

services.AddScoped<HttpContext, HttpContext>();

But this fails because the second HttpContext is supposed to be an instance - I know it's not correct but haven't been able to work out what is.

So, in summary - how can I pass in the HttpContext object into the constructor of MyAppContext?

Hossein Narimani Rad
  • 31,361
  • 18
  • 86
  • 116
Bruce Chapman
  • 1,227
  • 3
  • 12
  • 19
  • HttpContext.Current? – Dan May 05 '15 at 14:09
  • This should work if you are using Simple injector [http://stackoverflow.com/questions/21600678/simple-injector-how-to-inject-httpcontext][1] [1]: http://stackoverflow.com/questions/21600678/simple-injector-how-to-inject-httpcontext – DkAngelito May 05 '15 at 14:10
  • check my answer here http://stackoverflow.com/a/28973242/1184056 – Kiran May 05 '15 at 14:29
  • possible duplicate of [How properly inject HttpContext in MVC6](http://stackoverflow.com/questions/28970140/how-properly-inject-httpcontext-in-mvc6) – Kiran May 05 '15 at 14:47
  • I found that a controller, apparently from inhereting from Controller, has access to HttpContect and was able to pass this to my service when needed. _myService.PassTheUser(HttpContext.User) – Nathan Hartley Aug 04 '16 at 18:55

4 Answers4

23

Inject IHttpContextAccessor in the constructor

Victor Hurdugaci
  • 28,177
  • 5
  • 87
  • 103
  • Yes, this works. Specifically its the Microsoft.AspNet.Hosting.IHttpContextAccessor type, which then provides the .Value property containing the HttpContext. – Bruce Chapman May 05 '15 at 15:29
18

By injecting an HttpContext into your component you are violating the SOLID principles. To be more specifically, you are violating:

Both violations make it much harder to test your code. Although you can instead inject the IHttpContextAccessor as @victor suggests, this is still a violation of both the DIP and ISP, because this is an abstraction that is provided by the framework and you still depend on HttpContext. According to the DIP it is the client who should define the abstraction. This causes your code to be needlessly coupled to the framework.

Instead you should strive to specify narrow role interfaces; interfaces that do one specific thing for you that is specific to the needs of your application. Injecting a big dictionary with string values (as what HttpContext is, is never very specific). From your question it's unclear what kind of data you need from our MyAppContext, but I expect something like information of the currently logged in user. For this you can define a specific IUserContext abstraction, for instance:

public interface IUserContext {
    IPrincipal CurrentUser { get; }
}

An adapter that connects the application to the ASP.NET framework can be easily created for this abstraction:

sealed class AspNetUserContextAdapter : IUserContext  {
    private readonly IHttpContextAccessor accessor;
    public AspNetUserContextAdapter(IHttpContextAccessor accessor) {
        this.accessor = accessor;
    }
    public IPrincipal CurrentUser => accessor.HttpContext.User;
}

This adapter does depend on IHttpContextAccessor, but this is okay, since the adapter is an infrastructural component located in the Composition Root. There are serveral ways to register this class, for instance:

services.AddSingleton<IUserContext, AspNetUserContext>();
Steven
  • 166,672
  • 24
  • 332
  • 435
  • I instinctively knew that binding directly to HttpContext was incorrect, I was expecting to find an interface to use (which is what IHttpContextAccessor is). The application has no purpose, I'm just exploring these issues by way of practical example. I understand the example shown - the only concern I have with this type is that by not using an IHttpContextAccessor (or similar) you would have to keep going back and redefining the constructor parameters each time you discovered new requirements. (first it's the user, then the querystring, then the request headers, etc). – Bruce Chapman May 05 '15 at 16:00
  • @BruceChapman: if you find yourself having to go back "and redifining the constructor parameters each time you discovered new requirements", that might indicate a Single Responsibility Principle violation. That `IUserContext` abstraction should never do anything else than getting the current user. If you need data from the query string etc, you are probably getting different information and this deserves an abstraction on its own. The hard part of course is getting your abstractions right, but it usually gets much easier if you start by sticking to the basic principles (the SOLID principles). – Steven May 05 '15 at 16:04
  • `HttpContext.Current` is gone in vNext – Mihai Bratulescu Sep 02 '15 at 07:31
  • HttpContext is an abstract class, not a concrete type. – Dave Van den Eynde May 23 '16 at 14:21
  • @DaveVandenEynde: [Nope](https://msdn.microsoft.com/en-us/library/system.web.httpcontext%28v=vs.110%29.aspx). The class is even marked with the `sealed` keyword. – Steven May 23 '16 at 14:27
  • @Steven please explain to me which part of HttpContext is marked as `sealed`. https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNetCore.Http.Abstractions/HttpContext.cs – Dave Van den Eynde May 23 '16 at 14:47
  • @DaveVandenEynde: I'm sorry, I was too quick. Things have changed; HttpContext is indeed abstract now in Core. That doesn't change the fact though that DIP is violated. I'll update my answer accordingly. – Steven May 23 '16 at 14:51
  • Am I missing something? 'HttpContext' does not contain a definition for 'Current' – Nathan Hartley Aug 04 '16 at 15:23
  • @NathanHartley: You are right, there is no static `Current` property on `HttpContext` in ASP.NET Core. I'm not sure what went wrong here. I updated my answer. – Steven Aug 05 '16 at 06:23
3

In the startup class:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.DependencyInjection;

public void ConfigureServices(IServiceCollection services)
{        
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddMvcCore();
}

In the controller:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Core;

private readonly IHttpContextAccessor _httpContextAccessor;

public ServerSentEventController(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
}
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
-6

Why would you pass the HttpContext in the constructor? Why not directly access it wherever you want?

public MyAppContext(IDataService dataService)
    {
       HttpContext mycontext = HttpContext.Current;
       //do stuff here with mycontext
    }
DkAngelito
  • 1,147
  • 1
  • 13
  • 30