Reputation: 3
I am in the process of migrating a ASP.NET MVC (net48) application to ASP.NET Core (net6+). In this application we make heavy use of tagged autofac scopes. In net48 we used the AutofacDependencyResolver with a custom LifetimeScopeProvider to create a tagged lifetime scope for each request, depending for example on the area of the request. This allowed us to have different service implementations registered for each area among other things.
This is how we registered the custom lifetime scope provider for ASP.NET MVC and WebApi:
var customLifetimeScopeProvider = new CustomLifetimeScopeProvider(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container, customLifetimeScopeProvider));
GlobalConfiguration.Configuration.DependencyResolver = new CustomWebApiDependencyResolver(container, customLifetimeScopeProvider);
Our custom lifetime scope provider would then dynamically create tagged autofac scopes based on the request:
public class CustomLifetimeScopeProvider : ILifetimeScopeProvider {
...
public ILifetimeScope GetLifetimeScope(Action<ContainerBuilder> configurationAction) {
var request = HttpContext.Current.Request;
var scope = CreateTaggedScopeBasedOnRequest(request, configurationAction);
return scope;
}
...
}
I haven't found a way to replicate this functionality in ASP.NET Core. There is no DependencyResolver anymore because the framework handles dependency injection on its own.
Is there a way to create a custom lifetime scope for each request somewhere, that will be used to resolve all dependencies in controllers, views, view components, action filters, middlewares and so on?
I am still using autofac in the migrated app and have no problem with getting autofac to work. But i haven't found a way to manually create the scope that will be used by the framework to resolve controllers, views etc. I know that generally this is not necessary anymore and hence there is no DependencyResolver. BUT in our application we need to create dynamically tagged Scopes because this is how we build our architecture in .net framework where this was possible.
We have two ScopeTags: "Area1" and "Area2" and two services that are registered for the same interface depending on the tag:
builder.Register<Area1Service>.As<IService>.InstancePerMatchingLifetimeScope("Area1");
builder.Register<Area2Service>.As<IService>.InstancePerMatchingLifetimeScope("Area2");
We have a controller in each area that has the service as a dependency:
public class MyController: Controller {
public MyController(IService service) {
...
}
}
Request 1: example.org/area1/controller/action => A scope with tag "Area1" is created and the controller gets a instance of "Area1SService".
Request 2: example.org/area2/controller/action => A scope with tag "Area2" is created and the controller gets a instance of "Area2Service"
In our real application the logic is not quite as simple as in this example, but it shows how we made use of the tagged scope feature in combination with the DependencyResolver/ LifetimeScopeResolver-Mechanism in the old .net framework.
Upvotes: 0
Views: 2012
Reputation: 23924
You probably noticed, both through experience and documentation, that Autofac is no longer what actually spawns the request lifetime in ASP.NET Core - it's the framework itself, using the Microsoft.Extensions.DependencyInjection
package:
Use InstancePerLifetimeScope instead of InstancePerRequest. In previous ASP.NET integration you could register a dependency as
InstancePerRequest
which would ensure only one instance of the dependency would be created per HTTP request. This worked because Autofac was in charge of setting up the per-request lifetime scope. With the introduction ofMicrosoft.Extensions.DependencyInjection
, the creation of per-request and other child lifetime scopes is now part of the conforming container provided by the framework, so all child lifetime scopes are treated equally - there’s no special “request level scope” anymore. Instead of registering your dependenciesInstancePerRequest
, useInstancePerLifetimeScope
and you should get the same behavior. Note if you are creating your own lifetime scopes during web requests, you will get a new instance in these child scopes.
I'm not trying to avoid the question, but it's important that you understand what you're getting into, for a couple of reasons:
Microsoft.Extensions.DependencyInjection
now. You should be searching for more general things about how to control lifetime scope creation in ASP.NET Core and not limiting the search to Autofac.So, knowing all that, I'll provide you with the moving pieces and a link to a repo where we have a sort of example, but I'm not going to just write all the code for you.
The moving pieces:
HttpContext.Features
- The pipeline relies on several registered "features" which is sort of like a dependency resolver for pipeline components. The one you care about is...IServiceProvidersFeature
- This is the feature that manages the request lifetime scope. The default implementation creates the scope and, on dispose, removes the scope. You'll use this in your solution.IServiceProviderFactory
- The RequestServicesFeature
needs one of these to create the scope. You'll implement a custom version of this to create your Autofac scope.IServiceProvidersFeature
before anything has a chance to resolve anything from the request lifetime scope.That's the stuff you'll need to work with.
HttpContext.Features
, replaces the IServiceProvidersFeature
with your custom version.HttpContext.RequestServices
.HttpContext.RequestServices
will get your custom IServiceProvidersFeature
and ask for the lifetime scope.IServiceProvidersFeature
will create the scope if needed and return it.IServiceProvidersFeature
will get disposed and that's where you dispose the lifetime scope.As for the working example, check out the Autofac.AspNetCore.Multitenant repo. We had to do this for multitenant-aware request lifetimes. It has a little more in it than you'll need, but you should get the idea.
IStartupFilter
that will run first thing before other middleware gets registered. The startup filter is where the middleware gets registered.IServiceProvidersFeature
.IServiceProvidersFeature
that uses the custom IServiceProviderFactory
.IServiceProvidersFeature
for disposal at the end of the request.IServiceProvidersFeature
just in case something downstream needs that at the end of the request.Now I've given you that, let me give you a warnings because this may not actually do what you think it's going to do. This is the part where you're going to have to test and see if things are working as expected.
ASP.NET Core assumes scopes are flat. That is, it assumes that if it calls IServiceProvider.CreateScope()
to create a scope off the container, and then you call IServiceScope.CreateScope()
to create another scope... that every scope is actually right from the container. For folks used to Autofac, this is kind of weird and a change to how one might think about scopes. You can dig into some discussion on it here.
For implementing the IServiceProvider
interface along with IServiceProviderFactory
in the default case (that's Autofac.Extensions.DependencyInjection
) there was a lot of work to make sure everything stayed flat. You may be able to use the code in that library as a bootstrap for figuring out how to do your own factory.
The problem you may have is if someone inside a controller calls HttpContext.RequestServices.CreateScope()
- from an Autofac perspective, you'd want that to be a child of your named scope, and that the created scope will not be named. (That may be tricky to achieve.) From a Microsoft perspective, they think it's going to be a child of the container, not a child of the request scope so you may encounter some weirdness in the case of background/scheduled tasks that make the assumption scopes are flat.
Long story too long - this is probably possible, but it's not "I'll just override this one method and magic will happen." It's going to be a lot of work and testing to figure out the right incantation to make it happen.
Upvotes: 2