Autofac Lifetimes and the Default Provider within a Matching Lifetime Scope - c#

I have an ASP.NET MVC web application using Autofac for dependency injection. Occasionally, this web application will start a thread to do some work separate from the request thread. When this background thread starts up, it establishes a new Autofac lifetime scope from the root container and runs some action.
public IAsyncResult Run<T>(Action<T> action)
{
var NewTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
using (var Scope = Runtime.Container.BeginLifetimeScope())
{
var Input = Scope.Resolve<T>();
action(Input);
}
});
return NewTask;
}
One of my dependencies registered with Autofac has two different implementations: one appropriate for http-request lifetimes and another appropriate for all other lifetimes. I tried to register them as follows:
builder
.Register(c => new Foo())
.As<IFoo>()
.InstancePerLifetimeScope();
builder
.Register(c => new FooForHttp(HttpContext.Current))
.As<IFoo>()
.InstancePerMatchingLifetimeScope(WebLifetime.Request);
Autofac selects FooForHttp for http requests (as expected). However, when my background thread spins up, any attempt to resolve IFoo results in an exception:
No scope with a Tag matching 'httpRequest' is visible from the scope
in which the instance was requested. This generally indicates that a
component registered as per-HTTP request is being reqested by a
SingleInstance() component (or a similar scenario.) Under the web
integration always request dependencies from the
DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime,
never from the container itself.
I know that Autofac always uses the last registered provider as the default provider for a particular component. I made an assumption here that it would use the last registered suitable provider.
Am I missing something, or is there a better approach to selecting a provider based on the tag of the current lifetime scope?

Register the web Foo as normal, but don't register the other Foo. When creating the lifetime scope for the async task, use the overload of BeginLifetimeScope() that takes an action on ContainerBuilder. Register the background Foo in this action (b => b.Register()) and this should override the web one. (Small keyboard here sorry :))

This can also be solved by using a tagged life time scope.
Register your fisrt Foo as instance of your tagged scope:
builder.RegisterType<Foo>().As<IFoo>.InstancePerMatchingLifetimeScope("YourScopeTag");
And create the scope with the same tag you registered your dependencie:
using (var Scope = Runtime.Container.BeginLifetimeScope("YourScopeTag"))
{
var Input = Scope.Resolve<T>();
action(Input);
}
Haven't tested it, but it should work
http://docs.autofac.org/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope

Related

C# Autofac New Instance

I have a class "DependencyResolver" where I return instances of objects by hand. There I used "Activator.CreateInstance".
I wanted to change it so it uses autofac.
My function "Get" works fine:
public T Get<T>()
{
return _container.Resolve<T>();
}
But I also have a function "CreateNew" where I need a new instance:
public T CreateNew<T>()
{
return _container.Resolve<T>();
}
The Problem is that I always get the same instance.
My Registration looks like this:
var builder = new ContainerBuilder();
foreach (var dllFileName in DependencyMapping.GetAllDllFilenames())
{
builder
.RegisterAssemblyTypes(Assembly.LoadFile(Path.Combine(GetPathFromInstalledSys(), dllFileName)))
.AsImplementedInterfaces()
.SingleInstance();
}
_container = builder.Build();
So there is a place where I can control the behaviour: "SingleInstance" or "InstancePerDependency". But I dont know whether the user needs a new instance or not. Is there any way to change the behavior when "CreateNew" is called?
Lifetime scope (single instance, instance per dependency) is controlled at registration time, not resolve time. There's no way to change it. This is the case with all DI containers, not just Autofac. Part of the point of it is that the consumer shouldn't have to know what scope they want - they just ask for a thing and get it.
Consumers also generally shouldn't deal with disposal - things get disposed by the container.
Note what you have here is service location (client asks the container for a thing), not dependency injection (client takes dependencies in constructor and doesn't know about the container). While service location is sometimes required, generally try to avoid it if you can; it's not really much better than just calling new in your code.

Unable to access services from DI on delegate in startup Configure?

I am trying to add some items to the piranha sitemap using the delegate method OnGenerateSitemaps.
In this method I am calling to a service that gets data from entity framework context and then caches it. Whenever I try to use this service in the delegate-method I get a error that the dbContext has already been disposed.
System.AggregateException: 'One or more errors occurred. (Cannot
access a disposed context instance. A common cause of this error is
disposing a context instance that was resolved from dependency
injection and then later trying to use the same context instance
elsewhere in your application. This may occur if you are calling
'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances.
I've tried making the service sync instead of async, I've tried awaiting the result and running the task sync, none of which works.
Any ideas on how to use my service in this delegate in Configure on startup?
services.AddScoped<ICachedSitemapService, CachedSitemapService>();
In startup I inject the service, which is scoped.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api, ICachedSitemapService cachedSitemapService)
{
App.Hooks.OnGenerateSitemap += (sitemap) =>
{
var items = await cachedSitemapService.GetCachedClinics().Result;
sitemap.AddRange(items);
return sitemap;
};
}
The service that is called is DbContext to get items:
private async Task<IEnumerable<SitemapItem>> GetSitemapClinics()
{
var spec = new ClinicSitemapSpecification();
//This throws error
var allClinics = await _clinicRepo.ListAsync(spec);
//code shortened for brevity, but returns a list.
}
I've tried below without any luck.
var items = await cachedSitemapService.GetCachedClinics().GetAwaiter().GetResult();
sitemap.AddRange(items);
We're (the Piranha team) planning on redesigning the hook system in version 10 (has to be a major version as it will break backward compatibility) to provide DI functionality in all hooks. However, in the meantime, the following should fix your issues.
using (var scope = app.ApplicationServices.CreateScope())
{
var service = scope.ServiceProvider.GetService< ICachedSitemapService>();
}
Since your service is registered to be scoped it can't be resolved directly from the root provider (like the error states). When resolving the service from a controller you are in the request scope, so the solution here is to just create a scope that will allow you to resolve it.

Cleaner way to choose LifetimeScope in a dependency chain with Autofac

I'm using a web app with Autofac injecting services into controllers. Those services are sometimes injected with other services, and repositories. Repositories are injected with DbContexts. These 3 layers (service, repository, context) are all registered with Autofac. My default lifetime for these is InstancePerLifetimeScope.
Unfortunately, I have some code in a specific controller that I want to execute in parallel threads. Since DbContext is not thread-safe, this means I need to give a factory method to each thread to resolve a Service in a per dependency lifetime scope, which in turn will need to resolve per dependency repositories and db contexts.
The options I am considering are to create a new lifetime scope per thread, or to use a separate registration using a named or keyed registration to resolve the per-dependency services.
The challenge with creating a new lifetime scope per thread is that I need access to some per-scope objects. Some objects would need to be inherited and to not have a new instance created in the new scope, but other objects (the non-thread-safe DbContexts) need to have new instances generated in the new scope. I have no idea how to control this behavior implicitly when creating my new lifetime scope.
The other method would be to use a registration key so that when I execute the factory method to resolve a service on each thread, it would resolve one in the per-dependency scope. This would work if the service had no dependencies, but since it depends on a bunch of repositories or services for which the default lifetime scope is set to InstancePerLifetimeScope, I have to write something like this:
builder.RegisterType<MyService>()
.As<IMyService>()
.Named<IMyService>(RegistrationKeys.PerDependency)
.WithParameter(new ResolvedParameter(
(pi, ctx) => pi.ParameterType == typeof(IMyRepository),
(pi, ctx) => ctx.ResolveNamed<IMyRepository>(RegistrationKeys.PerDependency))
).InstancePerDependency();
Since the repositories depend on the DbContext, each repository has to be registered separately using this registration name. And it needs to be configured to resolve the DbContext using the registration name. And the DbContext needs to be registered using the registration name.
With 10 services each using about 4-5 repositories, I wager the amount of boilerplate registration code I will have to write will be around 10-20 full pages. It's not going to be maintainable.
So my question is, is there a way to create a specific type of lifetime scope that will allow me to easily control which objects will have a new instance or which will be inherited from the parent lifetime scope that won't break the asp.net per-request lifetime scope?
Or is there a way I can register or resolve a service to explicitly resolve all of its dependencies in the same scope without relying on their default registrations and without having to hard code an entire second set of registrations for everything?
The challenge with creating a new lifetime scope per thread is that I need access to some per-scope objects. Some objects would need to be inherited and to not have a new instance created in the new scope, but other objects (the non-thread-safe DbContexts) need to have new instances generated in the new scope. I have no idea how to control this behavior implicitly when creating my new lifetime scope.
This is the challenge InstancePerRequest solve. You can create child scope and object scoped to Request will be shared amongst child scope. To do this, tagged lifetimescope and InstancePerMatchingLifetimeScope is used.
You can see InstancePerRequest and Tagging a lifetime scope in the official documentation.
Example :
builder.RegisterType<Service>().As<IService>().InstancePerMatchingLifetimeScope("KEY");
builder.RegisterType<DbContext>().InstancePerLifetimeScope();
// ...
using (ILifetimeScope scope = container.BeginLifetimeScope("KEY"))
{
scope.Resolve<IService>(); // Instance #1
using (ILifetimeScope childScope = scope.BeginLifetimeScope())
{
childScope.Resolve<DbContext>();
childScope.Resolve<IService>(); // shared instance (#1)
}
}
but that's mean you have to change all your InstancePerLifetimeScope to InstancePerMatchingLifetimeScope and can control the creation of the unit of work lifetime scope which can be quite difficult.
Another way of doing this is by using Owned<T> with Func<T>. You can get more information here : Owned instance
builder.RegisterType<Service>().As<IService>().InstancePerLifetimeScope();
builder.RegisterType<DbContext>().InstancePerLifetimeScope();
builder.RegisterType<Operation>().As<IOperation>().InstancePerLifetimeScope();
public class Operation : IOperation
{
public Operation(Func<Owned<DbContext>> contextFactory, IService service)
{
this._contextFactory = contextFactory;
this._service = service;
}
private readonly Func<Owned<DbContext>> _contextFactory;
private readonly IService _service;
public void Do()
{
using Owned<DbContext> context = this._contextFactory();
context.Value // => new instance
this._service // shared instance (#1)
}
}
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
scope.Resolve<IService>(); // Instance #1
IEnumerable<IOperation> operations = scope.Resolve<IEnumerable<IOperation>>();
operations.AsParallel()
.ForAll(operation => operation.Do());
}
The only downside of this solution is that your service will have dependency on Autofac but if you don't want it, it is quite easy to create your own abstraction over Owned
If you don't want to use Owned<T> or your own abstraction instead of trying to make DbContext a special case you can reverse the problem and manually share some dependency between your custom scope.
Something like :
using ILifetimeScope childScope = scope.BeginLifetimeScope(b => {
b.Register<XContext>(c => scope.Resolve<XContext>()).ExternallyOwned();
});
var operation = childScope.Resolve<IOperation>();
operation.Do();
This way IOperation would be resolved in a new scope but XContext will be from parent scope

How to make AutoFac use same instance of nested dependency per top-level object? (SignalR dependency injection per hub)

I am trying to set up my AutoFac registration in such a way that this test passes:
[Test]
public void Autofac_registration_test()
{
// Given
var builder = new ContainerBuilder();
RegisterServices(builder);
var container = builder.Build();
// When
var firstHub = container.Resolve<Hub>();
var secondHub = container.Resolve<Hub>();
// Then
firstHub.Should().NotBe(secondHub);
firstHub.FooRepo.Context.Should().Be(firstHub.BarRepo.Context);
firstHub.FooRepo.Context.Should().NotBe(secondHub.FooRepo.Context);
}
i.e. I want to use the same Context object all the way down within a single Hub, but use a different one when a new Hub is created.
RegisterServices is currently just:
private void RegisterServices(ContainerBuilder builder)
{
builder.RegisterType<MyHub>();
builder.RegisterType<FooRepo>();
builder.RegisterType<BarRepo>();
builder.RegisterType<Context>(); // How should I scope this?
}
Which fails at firstHub.FooRepo.Context.Should().Be(firstHub.BarRepo.Context); because Context is transiently scoped.
But scoping context per lifetime also fails, this time at firstHub.FooRepo.Context.Should().NotBe(secondHub.FooRepo.Context);.
It feels like this is a reasonable thing to want to do, so am I missing anything obvious out-of-the-box here?
Or will I have to do something manual to track Hub creation?
(For context, this is for a SignalR app. Hubs are created per SignalR request, so this was an attempt to match the unit-of-work lifetime of an HTTP request in normal webby situations).
What #Steven said in his comment was correct, I needed a per-object-graph lifestyle.
Castle.Windsor supports this, so I swicthed to using that for my dependency injection instead of AutoFac. The registration now looks like:
container.Register(Component.For<Hub>().LifestyleTransient());
container.Register(Component.For<FooRepo>().LifestyleTransient());
container.Register(Component.For<BarRepo>().LifestyleTransient());
container.Register(Component.For<Context>().LifestyleBoundTo<Hub>()); // Important bit
For more information, see: http://docs.castleproject.org/Windsor.LifeStyles.ashx?HL=scope#Bound_8

Registering component multiple times

I need to instantiate a concrete class based on data in the HttpRequestMessage. I'm using the following code to configure my Web API service:
var builder = new ContainerBuilder();
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
builder.RegisterApiControllers(typeof(WebApiApplication).Assembly);
// ... snip ....
container.Register(x =>
{
if (x.IsRegistered<HttpRequestMessage>())
{
var httpRequestMethod = x.Resolve<HttpRequestMessage>();
var tokenHelper = x.Resolve<ITokenHelper>();
var token = tokenHelper.GetToken(httpRequestMethod);
var connectionContext = x.Resolve<ISqlServerConnectionContext>();
connectionContext.Token = token;
return token ?? new NullMinimalSecurityToken();
}
return new NullMinimalSecurityToken();
});
// ... snip ...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
Unfortunately, the delegate is called multiple times, with the last one not having a registered HttpRequestMessage, meaning I get back a NullMinimalSecurityToken, which causes my security attribute to reject the request (as it should). However, when I'm stepping through, I can see that IMinimalSecurityToken is already registered (i.e. x.IsRegistered<IminimalSecurityToken() returns true). If I try to return the resolved IMinimalSecurityToken if it's already registered, I get a circular reference error.
In addition, attempting to use InstancePerApiRequest while registering results in a "No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested" error.
I'm not entirely clear on why this delegate gets called multiple times, nor why in one of the calls it doesn't have an HttpRequestMethod.
What am I doing wrong that is causing multiple executions of the delegate (assuming that is in fact a problem), and how do I fix this so that I can properly inject a valid IMinimalSecurityToken into my SecurityAttribute (via property injection, but that part is working, it's just that what gets injected isn't valid).
So this problem turned out to be caused by trying to "convert" the Ninject code over to Autofac.
I was able to use Autofac's capabilities to inject Web API filters to wire up the security attribute, which allowed me to inject the required IMinimalSecurityToken in the constructor, rather than the rather hacky way I had to work around Ninject's limitations in that area:
container.RegisterType<SecurityTokenAuthorization>().As<IAutofacAuthorizationFilter>().InstancePerApiRequest();
container.RegisterType<SecurityTokenAuthorization>().AsWebApiAuthorizationFilterFor<DocumentController>().InstancePerApiRequest();
I've got other issues now, but they are sufficiently different that they'll need a new question assuming I'm not able to solve them.

Categories