Reputation: 1237
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?
Upvotes: 19
Views: 14667
Reputation: 172606
By injecting an HttpContext
into your component you are violating the SOLID principles. To be more specifically, you are violating:
HttpContext
).HttpContext
has many methods, while the consumer never uses them all.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>();
Upvotes: 20
Reputation: 141434
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;
}
Upvotes: 3
Reputation: 1187
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
}
Upvotes: -6