Basically when we resolve a component with Autofac, we could register the object instance in the root container; this is not the best practice, because these components will never be disposed as long as the container lives, that normally is the lifetime of the application.
Then, the root container will hold the references until the application shutdown, and this could be cause memoty leaks.
A better approach is the using of the lifetime scopes, that help us to define an area where the service can be shared with other components and disposed at the end.
Let’s start defining the classes to be used as services in the Autofac configuration.
Services
We can use two simple class, a CustomerService class and an OrdersService:
public class CustomerService { public CustomerService() { } }
public class OrdersService { private ITokenService _tokenService; public OrdersService(ITokenService tokenService) { this._tokenService = tokenService; } }
The OrdersService accepts in the constructor an ITokenService and we have an implementation of that:
public class PerDependencyTokenService : ITokenService { private Guid _token { get; set; } public Guid GetToken() { if (_token == Guid.Empty) _token = Guid.NewGuid(); return _token; } }
Now we can define the services registration, let’s start creating the Autofac modules.
Modules
First of all, we need to register the PerDependencyTokenService and we must identify it in order to distinguish this service from the others that implements the ITokenService interface:
public class PerDependencyModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<PerDependencyTokenService>() .AsSelf() .AsImplementedInterfaces() .Keyed<ITokenService>("perDependencyTokenService"); } }
We registered the service as Keyed, and we identified that with a specific tag.
Now we can register the other services:
public class PerLifetimeScopeModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<OrdersService>() .AsSelf() .InstancePerLifetimeScope() .WithParameter(new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ITokenService), (pi, ctx) => ctx.ResolveKeyed<ITokenService>("perDependencyTokenService") )); // PER MATCHING LIFETIMESCOPE builder.RegisterType<CustomerService>() .AsSelf() .InstancePerMatchingLifetimeScope("scope1"); } }
OrderService is registered as InstancePerLifetimeScope, so an instance of this component will be unique in a specific scope.
We specify the Parameter ITokenService as well, and we tell to Autofac to resolve this with a specific key; so we are able to specify the concrete class that implements the ITokenService interface.
The second registration is about the CustomerService; this registration is slightly different from the first one, because we want to resolve the CustomerService only for the lifetime scopes that has a specific name.
Now we are going to implement the test methods that will use the services.
Test methods
The first step is register the module in the Autofac container:
builder.RegisterModule(new PerLifetimeScopeModule());
Then we can implement the test methods:
public class InstancePerLifetimeScopeTests : BaseTests { [Test] public void should_is_not_the_same_instance_for_different_lifetime_scopes() { OrdersService ordersService1, ordersService2; using (var scope = containerBuilder.BeginLifetimeScope()) { ordersService1 = scope.Resolve<OrdersService>(); } using (var scope = containerBuilder.BeginLifetimeScope()) { ordersService2 = scope.Resolve<OrdersService>(); } ReferenceEquals(ordersService1, ordersService2).ShouldBeEquivalentTo(false); } [Test] public void should_not_be_able_to_resolve_instance_per_lifetime_scope() { CustomerService customerService1 = null, customerService2 = null; try { using (var scope = containerBuilder.BeginLifetimeScope()) { customerService1 = scope.Resolve<CustomerService>(); using (var scope1 = containerBuilder.BeginLifetimeScope("scope1")) { customerService2 = scope.Resolve<CustomerService>(); } customerService1.ShouldBeEquivalentTo(null); } } catch (Exception) { customerService1.ShouldBeEquivalentTo(null); } } [Test] public void should_be_able_to_resolve_instance_per_lifetime_scope() { CustomerService customerService1, customerService2; using (var scope = containerBuilder.BeginLifetimeScope("scope1")) { customerService1 = scope.Resolve<CustomerService>(); using (var scope1 = containerBuilder.BeginLifetimeScope()) { customerService2 = scope.Resolve<CustomerService>(); } } ReferenceEquals(customerService1, customerService2).ShouldBeEquivalentTo(true); } }
In the first method, we create two different lifetime scopes and we check that the two objects have not the same reference.
In the second one, we should not be able to resolve the CustomerService for a generic lifetime scope, because we registered this service for a scope named scope1.
So this row:
customerService1 = scope.Resolve<CustomerService>();
will throw a DependencyResolutionException.
Instead in the third method will be able to resolve the service, because we try to create an instance in a LifetimeScope named scope1; and of course, this service will be single instance for every lifetime scope that match the name.
You can find the source code here.
Leave a Reply