Aspnetcore: Get HttpContext features via dependency injection

Created on 15 Jun 2019  路  11Comments  路  Source: dotnet/aspnetcore

Hi,
every now and then I need to get a HttpContext feature with code like this:

public IActionResult Index()
{
   var cultureFeature = HttpContext.Features.Get<IRequestCultureFeature>();
   //...
   return View();
}

While it's indeed working, it feels like I'm using the service locator pattern. Why can't I just leverage dependency injection and do the following?

public IActionResult Index([FromServices] IRequestCultureFeature feature)
{
   //...
   return View();
}

I am aware I could let a component depend upon IHttpContextAccessor and then proceed from there. But why can't features be considered services? At least... could we have an IFeatureProvider service to easily retrieve / check the existence of a feature? Sometimes I don't need to see the whole HttpContext API surface but just the API of that single feature.

public IActionResult Index([FromServices] IFeatureProvider featureProvider)
{
   if (featureProvider.TryGetFeature<IRequestCultureFeature>(out IRequestCultureFeature feature))
   {
      //...
   }
   return View();
}

What's your opinion about this?

area-servers enhancement

Most helpful comment

@anurse bumping out of discussion for triage, https://github.com/aspnet/AspNetCore/issues/11252#issuecomment-502479394 is actionable.

All 11 comments

Features are very different from services. They represent functionality provided by the server, _or other middleware_, related specifically to the processing of the current request. Most importantly, the feature collection for a given request is mutable.

For example, take WebSockets. Kestrel does not natively support WebSockets but it does support "HTTP Upgrade" (which is a functionality that WebSockets builds upon). So Kestrel provides the IHttpUpgradeFeature in the feature collection. Later on in the pipeline, if you have the WebSockets middleware, it uses the IHttpUpgradeFeature in order to provide an implementation of a new feature, IHttpWebSocketFeature. Thus, _during the request_, a new feature is added to the HttpContext. DI does not support mutating the service collection (it, in fact, depends on the services being immutable at runtime).

Meanwhile, IIS natively support WebSockets, so it provides an implementation of this feature directly. In fact, it's _not possible for the WebSockets middleware to work on IIS_, so the WebSockets middleware first checks to see if the server support WebSockets. If it does, it no-ops.

SignalR, even later in the pipeline, then checks for the presence of the IHttpWebSocketsFeature to determine if the current server/middleware configuration support WebSockets and uses that information to guide it's transport negotiation process (falling back to Long Polling).

All of this requires a mutable feature collection, and it really doesn't fit within what DI supports.

That said, there are relatively few features you need to access directly. Most of their functionality is accessible through HttpContext properties, extension methods, or MVC attributes / filters. Anywhere you need direct access to a feature interface tells me there's a gap in the higher level APIs. Was IRequestCultureFeature a real example for you? Do you have others?

Was IRequestCultureFeature a real example for you? Do you have others?

Yes @Tratcher, I use IRequestCultureFeature in a tag helper, to determine which is the currently selected culture and then style the active culture-switching link a little different.
demo

Here's another example with IExceptionHandlerPathFeature, a feature added by the ExceptionHandlerMiddleware. I use it to determine the type of the thrown exception and then display an appropriate and helpful error message to the user.

var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
switch(feature.Error)
{
    case ProductNotFoundException exc:
        ViewData["Title"] = "Product not found";
        Response.StatusCode = 404;
        return View("ProductNotFound");

    default:
        ViewData["Title"] = "Generic error";
        return View("GenericError");
}

Features are very different from services.

Thank you so much @anurse for the detailed explanation, I learned a lot. The question still stands though: if I can get a feature via the IHttpContextAccessor, why can't I do it with a specialized IFeatureAccessor<TFeature> service which would reduce the exposed API surface and simplify the mocking of that service for black-box unit testing?

Of course, I could just write that service myself but I wonder why an IFeatureAccessor<TFeature> was not provided with ASP.NET Core. Maybe features were not intended to be directly used in application code?

Thanks for your time.

Maybe features were not intended to be directly used in application code?

Indeed, features are meant to be the underlying infrastructure, not direct consumption.

An IFeatureAccessor service couldn't be implemented without IHttpContextAccessor, so it doesn't add much value.

An IFeatureAccessor service couldn't be implemented without IHttpContextAccessor, so it doesn't add much value.

Also, I'd view IHttpContextAccessor as more of an anti-pattern than the "service lookup" you're doing with features :). As much as possible, you should avoid relying on an ambient HttpContext.

Ok. I don't know the localization area very well, but there's several pages of docs and they only mention the feature once. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-2.2#dataannotations-localization. Do you have a more specific example of what you were trying to do and why the higher level mechanics weren't appropriate?

For IExceptionHandlerPathFeature I agree, we should add extension methods for those fields.

@anurse bumping out of discussion for triage, https://github.com/aspnet/AspNetCore/issues/11252#issuecomment-502479394 is actionable.

Also, I'd view IHttpContextAccessor as more of an anti-pattern than the "service lookup" you're doing with features :). As much as possible, you should avoid relying on an ambient HttpContext.

I agree with you, that's why I opened the issue. Relying on the IHttpContextAccessor violates the interface segregation principle. And I don't want to rely on the HttpContext property either since I just need to get these two references, not just in a Controller but in any POCO dependency-injectable service.

  • A reference to the current exception;
  • A reference to the current culture.

why the higher level mechanics weren't appropriate?

If you mean CultureInfo.CurrentCulture, I'd prefer something injectable so that my components could just state their dependencies in their constructor.

Of course I can just code my very own ICultureProvider service and make it return CultureInfo.CurrentCulture. I thought it would be more consistent if this was provided by ASP.NET Core.

I agree with you, that's why I opened the issue. Relying on the IHttpContextAccessor violates the interface segregation principle.

My concern is _not_ about interface segregation, it's about ambient state. IHttpContextAccessor depends on AsyncLocal which is "magic ambient state" managed by the runtime. It makes testing more complicated since there are effectively global variables in play (global variables that are scoped to the current async execution context, but they are still global). A better approach is to pass the HttpContext (or the specific feature you need) in to methods that require it rather than expecting it to be present in the ambient state. We view IHttpContextAccessor itself as something that should only be used when it's absolutely impossible to flow HttpContext (or a specific feature) directly, so we don't have plans to expand that functionality to include features.

If you want to write IFeatureAccessor<T> you certainly could with the current system. You just need a DI service that accepts IHttpContextAccessor as a constructor argument.

If you want to write IFeatureAccessor<T> you certainly could with the current system. You just need a DI service that accepts IHttpContextAccessor as a constructor argument.

Yes @anurse, that would be easy to put into practice. However, I think I had a change of mind since @Tratcher pointed out features should not be directly used in application code. In the end, I'd rather not create a generic IFeatureAccessor<T> service but smaller specialized services, such as IExceptionAccessor and ICultureAccessor in order to hide features from higher level code.

Thanks for all the clarifications you provided.

Thank you for contacting us. Due to a lack of activity on this discussion issue we're closing it in an effort to keep our backlog clean. If you believe there is a concern related to the ASP.NET Core framework, which hasn't been addressed yet, please file a new issue.

This issue will be locked after 30 more days of inactivity. If you still wish to discuss this subject after then, please create a new issue!

Was this page helpful?
0 / 5 - 0 ratings