Autofac - How to access IRegistrationBuilder from IResolvePipelineBuilder & ResolveRequestContext? - c#

I would like to extend my service registration [e.g. EnableInterfaceInterceptors] from Autofac Pipeline. However, I couldn't find a way to achieve that. Is there any entrypoint for me to access the registration as IRegistrationBuilder?
Provided codes below for your reference
containerBuilder.RegisterGeneric(typeof(CredentialService<>)).InstancePerDependency()
.ConfigurePipeline(p =>
{
p.Use(PipelinePhase.RegistrationPipelineStart, (context, next) =>
{
// WHAT SHOULD I DO HERE TO GET BACK THE IRegistrationBuilder IN ORDER TO EXTEND MY REGISTRATION?
next(context);
});
});
Thanks in advance

Autofac docs
The context object passed into all middleware is an instance of ResolveRequestContext. This object stores the initial attributes of a resolve request, and any properties updated while the request executes.
You can use this context to:
Check the service being resolved with the Service property.
Check the Registration being used to provide the service.
Get or set the result of the resolve operation with the Instance property.
Access the parameters of the request with the Parameters property and change those parameters with the ChangeParameters method.
Resolve another service (using any of the normal Resolve methods).
In short
Although it is called registration pipeline, it is actually used when a resolve request occurs.
Instances of IRegistrationBuilder are not stored anywhere except in the configuration callback (see details below), hence you cannot access them in the pipeline in any way.
By the time your pipeline delegate is being executed, IRegistrationBuilder instances are already gone. You have only the IComponentRegistration instance in the ResolveRequestContext.Registration property.
In detail
When an instance of IRegistrationBuilder is created then
It will have an instance of RegistrationData that contains the Lifetime, Metadata, Options, Ownership and Sharing properties. The extension methods of IRegistrationBuilder actually configures these properties.
It will also have an instance of IResolvePipelineBuilder field you actually configure with the .ConfigurePipeline() extension method.
It will register a configuration callback into the current ContainerBuilder instance with the IRegistrationBuilder instance.
These callbacks are executed by the ContainerBuilder.Build() method.
When the callback is executed then it will create an instance of IComponentRegistration from the IRegistrationBuilder instance.
This IComponentRegistration contains the same properties that come from the RegistrationData and also gets the configured IResolvePipelineBuilder.
var registrationBuilder =
containerBuilder
.RegisterType<ServiceWithDependency>()
.AsSelf()
.InstancePerDependency();
registrationBuilder.ConfigurePipeline(
resolvePipelineBuilder =>
{
resolvePipelineBuilder.Use(
PipelinePhase.RegistrationPipelineStart,
(context, next) =>
{
// context is ResolveRequestContext
// context.Registration is IComponentRegistration
next(context);
// Autofac.Extras.DynamicProxy
// EnableInterfaceInterceptors() extension method actually uses
// - context.Registration
// - context.Instance (this will be overridden with the generated proxy)
// - context.ResolveService(...)
// You may also use these properties from context.Registration
// - context.Registration.Lifetime
// - context.Registration.Metadata
// - context.Registration.Options
// - context.Registration.Ownership
// - context.Registration.Sharing
}
);
}
);

Related

Can you create transient DI service inside of middleware?

I am calling a purchased package, which I do not have source code for. It is expecting a service pre-configured through DI. I can't change this.
I don't know all of the parameters for the service until the user logs on. User 'A' might have a different configuration than User 'B'. If no user is logged in, then the service needs to be a different way.
I can't inject it during 'Startup', as it will be configured differently for 'A' and 'B' (and 'no user')
The best place (I think) to do it is during Middleware 'Invoke', however, I can't seem to get access to IServicesCollection to perform '.AddTransient<>'
Is this even possible?
Or is there a better way to create an '.AddTransient' service dynamically?
If intending to access the service within the middleware invoke then there is no need to try and access ServiceCollection outside of Startup
register desired transient service using deferred factory delegate
//...
services.AddTransient<IService>(sp => {
var ctx = sp.GetService<IHttpContextAccessor>().HttpContext;
var user = //get user however you intended to get user
//create instance of service
return ActivatorUtilities.CreateInstance<Service>(sp, user);
});
//...
In the middleware the service can be explicitly injected via method injection
//...
public async Task InvokeAsync(HttpContext context, IService service) {
//...
}
//...
Additional parameters for the middleware's InvokeAsync, after HttpContext are populated by dependency injection (DI).
This will in turn resolve your transient service when injecting into the member

ApplicationServices resolves a different scoped instance in net core?

I'm using .net core 3.1 with this configuration :
public interface IFoo
{
public void Work();
}
public class Foo : IFoo
{
readonly string MyGuid;
public Foo()
{
MyGuid = Guid.NewGuid().ToString();
}
public void Work() { Console.WriteLine(MyGuid); }
}
And this is the configuration :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IFoo, Foo>();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IFoo foo, IServiceProvider myServiceProvider)
{
Console.WriteLine("Via ApplicationServices");
app.ApplicationServices.GetService<IFoo>().Work();
Console.WriteLine("Via IServiceProvider");
myServiceProvider.GetService<IFoo>().Work();
Console.WriteLine("Via IFoo injection");
foo.Work();
}
Result are :
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----different
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb
More , in the controller action , If I use IserviceProvider :
[HttpGet]
public IActionResult Get([FromServices] IServiceProvider serviceProvider)
{
serviceProvider.GetService<IFoo>().Work();
}
I see another different Guid : 45d95a9d-4169-40a0-9eae-e29e85a3cc19.
Question:
Why does the IServiceProvider and Injection of IFoo in the Configure method yield the same Guid , while the controller action and app.ApplicationServices.GetService yield different ones?
There are 4 different guids in this example
It's a scoped service. It supposed to be the same Guid.
TL;DR;
IFoo foo is resolved using myServiceProvider.GetService<IFoo>() before both get passed into Configure method. So you're resolving same IFoo instance from the same myServiceProvider instance for the 2nd time.
app.ApplicationServices is special root service provider of the application. Parent of myServiceProvider. Thus it resolves different IFoo.
**Default behavior of app.ApplicationServices should be to throw exception when you try to resolve scoped service.
LONGER EXPLANATION
If you have three dummy classes:
class Singleton { }
class Scoped { }
class Transient { }
And you register them as such in IServiceConfiguration:
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Singleton>();
services.AddScoped<Scoped>();
services.AddTransient<Transient>();
}
Now, you create your "root" IServiceProvider from IServiceCollection - in command-line app, it would look like this:
ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();
ROOT SERVICE PROVIDER BEHAVIOR (equivalent to app.ApplicationServices):
If you now test root:
root.GetService<Singleton>(); - every time it's called returns same object instance.
root.GetService<Scoped>(); - every time it's called returns same object instance.
root.GetService<Transient>(); every time it's called returns new object instance.
CHILD-SCOPE SERVICE PROVIDER BEHAVIOR (eg: IServiceProvider in Configure method):
If you now create child scope and use it's own IServiceProvider:
IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;
sp1.GetService<Singleton>(); - every time it's called returns same object instance whichroot.GetService<Singleton>(); returns. Singleton is the same instance no matter from which scope you call it. It is resolved climbing the hierarchy of scopes back to the root service provider (scopeless one).
sp1.GetService<Scoped>(); - every time it's called returns same object instance, but not the same instance that root returns. Object instance is cached on the current scope. Every scope creates/caches it's own scoped instance.
sp1.GetService<Transient>(); every time it's called returns new object instance, same behavior like for the root.
root scope is "special" only because it has no parent scope, so resolving scoped or singleton service from the root technically does the same thing - object instance returned is cached in the root itself.
This also explains why you cannot resolve service from IServiceCollection directly. IServiceCollection does not have hierarchy of scopes and caching infrastructure which IServiceProvider has. It just contains list of ServiceDescriptor. In addition, it would be unclear in which scope service instance should be cached.
ASP.NET Core
For ASP.NET Core root IServiceProvider is app.ApplicationServices. Configure method receives first child-scope created from the root - application-scope. For every HTTP request application-scope creates child-scope which is used to resolve all services and is itself injected in controllers and views of that HTTP request. It is also used to inject all other types in a controller constructor or a view.
IFoo resolution
So, your foo from Configure method is resolved using myServiceProvider, then they both get used as input parameters for Configure. Framework does something like this:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
var appScope = root.CreateScope();
IFoo foo = appScope.ServiceProvider.GetService<IFoo>();
ConfigureServices(foo, appScope.ServiceProvider);
When you call sp.GetService<IFoo>() inside of Configure method it is identical to appScope.ServiceProvider.GetService<IFoo>(); that was already called from the outside. root.GetService<IFoo>() creates different IFoo instance, as it should.
More ASP.NET Core:
To prevent developers making mistake trying to resolve scoped service from the app.ApplicationServices and not realizing that it is application scoped (global), instead of being scoped to HTTP request, by default, ASP.NET Core creates root ServiceProvider using BuildServiceProvider overload:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
validateScopes: true to perform check verifying that scoped services never gets resolved from root provider; otherwise false.
However, this might depend on compatibility mode you're using. I'm guessing that's the reason why it allows you to resolve scoped service via app.ApplicationServices.
Update answer:
For being able to provide you with the same instance, the DI engine will need to know the current scope.
The Configure method in the Startup class is called initially by the ASP.NET Core engine, not by a request from the user which is how a scoped service is meant to be used.
Now if you try this example instead:
IServiceScope scope = app.ApplicationServices.CreateScope();
scope.ServiceProvider.GetService<IFoo>().Work();
As long as you use the same ServiceProvider from the same IServiceScope instance, you will have access to the same IFoo object and will get the same GUID.
Old answer:
A scoped service is meant to be living for a short period (scope) and get removed when they are needed anymore.
From Dependency injection in .NET
a scoped lifetime indicates that services are created once per client request (connection)
furthermore,
Do not resolve a scoped service from a singleton and be careful not to do so indirectly, for example, through a transient service.
app.ApplicationServices.GetService will try to provide you a Singleton which is not what the service is registered as.
What you see is the expected behavior. If you need the service to have the same instance for the whole period of your application's lifetime, you will have to register it as a Singleton.
Read more about the singleton lifetime and change your code from services.AddScoped<IFoo, Foo>() to services.AddSingleton<IFoo, Foo>() if you need the same value everywhere.
The IApplicationBuilder "app" is created and passed to the Configure method.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-5.0#the-configure-method
IApplicationBuilder is available to the Configure method, but it isn't registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.
On the other hand the IServiceProvider "myServiceProvider" is actually activated/created and injected:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#services-injected-into-startup
Services can be injected into the Startup constructor and the Startup.Configure method.
Any service registered with the DI container can be injected into the Startup.Configure method
So we are in two different scopes, so the guid, as expected, is different.
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <---- created in scope "A"
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb <-- created in scope "B"
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb < -- created in scope "B"
When you are in your controller each request will have it's brand new scope, a different scope than the ones discussed above:
https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
It seems to me that you expected that also the guid in the controller to be the same, you should be aware that for each request an instance of your controller is created and a DI chain starts in a fresh scope for each request.

Microsoft dependency injection. How can I get data to a constructor injected class several layers deep

This is dotNet core 2.2 project using Microsoft.Extensions.DependencyInjection.
I have 3 classes. Class A uses Class B in the constructor. Class B uses class C, and Class C uses the ITenant interface.
ITenant determines which database will be used.
example:
public A(IB b)
public B(IC c)
public C(ITenant t)
They are setup in the injection container as follows:
services.AddTransient<IA, A>();
services.AddTransient<IB, b>();
services.AddTransient<IC, c>();
services.AddTransient<ITenant , HttpTenant>()>();
In the web project the controller uses Class A as a constructor parameter and the container createClass A and all it's dependencies. The implementation of ITenant (HttpTenant) pulls the tenant name from an HTTP request header and gets the database informaiotn from the config file. Everything works perfectly.
Now I need to call this from a windows service, which does not involve as HTTP request. I have a handler that responds to a message queue and Class A is a construction parameter. For the windows service I have a different ITenant (WindowServiceTenant):
services.AddTransient<ITenant , WindowServiceTenant>()>();
I cannot figure out how to get the tenant code into WindowServiceTenant.
The tenant is determined at run time based on a value read form a message queue.
By the time my handler is instantiated the WindowServiceTenant is also instantiated.
I don't know the tenant before the handler is instated.
I need to get a reference that instance of WindowServiceTenant and provide the tenant. Or, this implementation WindowServiceTenant needs a reference to the handler that initiated the instantiation.
Any ideas?
There are basically two solutions:
Configure the WindowServiceTenant instance with the required value before you resolve the handler
Communicate the value through ambient state, for instance a value that is available for the thread (ThreadLocal<T>) or asynchronous operation (AsyncLocal<T>)
The first option requires the WindowServiceTenant to be registered as Scoped service and the creation of a IServiceScope, from which you resolve the WindowServiceTenant and the appropriate handler:
// Registration
services.AddScoped<WindowServiceTenant>();
services.AddScoped<ITenant>(c => c.GetRequiredService<WindowServiceTenant>());
// Usage
using (var scope = serviceProvider.CreateScope())
{
var services = serviceScope.ServiceProvider;
var tenant = services.GetRequiredService<WindowServiceTenant>();
// Set the right tenant based on a value from the queue
tenant.SetTenantValue(...);
// Resolve and execute handler
var handler = services.GetRequiredService(handlerType);
}
The previous code listing does the following:
It registers the WindowServiceTenant both by its concrete type as by its interface in such a way that both resolving WindowServiceTenant and ITenant will result in the same instance during a single service scope. This is important because otherwise state is set on that scoped instance. Having multiple instances within the same service scope will obviously not yield the right result.
When your message is handled, you start a new IServiceScope using the CreateScope extension method on IServiceProvider.
Within that scope you resolve WindowServiceTenant. You resolve this concrete type, as the ITenant abstraction will have no possibility to set the right value (as that is an implementation detail)
You store the tenant value from the queue inside the WindowServiceTenant instance. As this same instance is reused for the during of the service scope, it will be injected into any resolved object graph that depends on ITenant.

.NET Core Singleton Creation is called multiple times

I'm registering a service as a singleton in .NET Core. Yet I'm seeing the constructor for the singleton called multiple times.
services.AddSingleton<DbAuthorizationOptions, ContextAuthorizationOptions>();
My context authorization options is just Dictionary of Entity Types to IValidators, The context authorization options are passed into the DBContext, to automatically run validations.
During the registration of my services, I also register dynamic Validators with my container registered in DI.
var useDynamicValidator = serviceOption.ValidatorOptions != null;
if(useDynamicValidator)
{
//TODO: Extract this to before the register service no sense in building the provider each time
//TODO: Make this cleaner don't be dependent on Authorization options
var provider = services.BuildServiceProvider();
var authOptions = provider.GetService<DbAuthorizationOptions>();
var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
authOptions.ValidatorOptions.AddValidatorForSet(validator);
}
I notice that when I call GetService on the provider I receive a new singleton instead of the existing one. Does building the provider create a new container so all of the services get re-registered?
If so, How can I call a method to register my dynamic validators in the singleton container with the existing IServiceProvider, is there a way to invoke some registration once after the service container is built?
Does building the provider create a new container so all of the services get reregistered?
Yes. See the source code.
If so, How can I call a method to register my dynamic validators in the singleton container with the existing IServiceProvider, is there a way to invoke some registration once after the servicecontainer is built?
I'm not really understanding why this is a problem. You should be registering all of your services one time at application startup in the Composition Root.
The DI container is then responsible for resolving the object graphs of the application. The application itself shouldn't have a dependency on it, nor be required to update it.
You should be injecting DbAuthorizationOptions in the place where you need to use it.
public class Foo : IFoo
{
private readonly DbAuthorizationOptions authOptions;
public Foo(DbAuthorizationOptions authOptions) // <-- Inject parameters
{
this.authOptions = authOptions ??
throw new ArgumentNullException(nameof(authOptions));
}
public void DoSomething()
{
// TODO: Inject the type that has the BuildDynamicValidatorFactory
// method and the serviceOption (whatever type that is) here
// either as a method parameter of this method, or a constructor
// parameter of this class.
var validator = BuildDynamicValidatorFactory(serviceOption).Invoke(provider, null);
// Now we have an instance of authOptions that can be used
authOptions.ValidatorOptions.AddValidatorForSet(validator);
}
}
Note that the DI container automatically provides the DbAuthorizationOptions if injected into another type that is also resolved through DI (such as a controller or filter).
NOTE: It isn't very clear from your question where you need to do this. You mention that you want it to happen once, which usually means to put it at application startup. But users cannot interact with code that runs at startup. So, maybe you could use a filter. It really all depends on where in the lifecycle of the application it has to happen.
You can declare a dependency on IServiceProvider -- don't build it, inject it.
public class SomeController
{
DbAuthorizationOptions authOptions;
public SomeController(IServiceProvider provider)
{
authOptions = provider.GetSerivce<DbAuthorizationOptions>();
}
}
But this is the service locator anti-pattern. As I commented on NightOwl888's post after you gave more details, a factory is probably a better approach.

Simple Injector FilterInjection seems to be reinitialising RegisterPerWebRequest injected item

I'm trying to move from Ninject to Simple Injector but I'm experiencing an odd issue when trying to duplicate functionality that worked with Ninject.
In Ninject I had a service which contained:
private readonly ICollection<Message> messages;
This service was registered as
Bind<INotificationService>().To<NotificationService>()
.InRequestScope();
This service allowed messages (UI and error) to be passed back to the MVC site.
This service was injected into an ActionFilterAttribute:
kernel.BindFilter<CriticalErrorAttribute>(FilterScope.Last, 1)
.When((context, ad) =>
!string.IsNullOrEmpty(ad.ActionName) &&
ad.ControllerDescriptor.ControllerName.ToLower() != "navigation");
and used within OnActionExecuted.
Because the service was registered to Ninject with InRequestScope, any items pushed to the message queue were available in the Actionfiter. This allowed for a redirect to an error page (displaying critical errors) if necessary.
I've tried to duplicate this with simpleinjector:
container.RegisterPerWebRequest<INotificationService, NotificationService>();
container.RegisterInitializer<CriticalErrorAttribute>(handler =>
{
handler.NotificationService =
container.GetInstance<INotificationService>();
});
The injection is working fine, but even though the message collection contains messages prior to entering the ActionFilter, once in the filter the message collection is empty. It's like the RegisterPerWebRequest is being ignored.
Any help in solving this issues would be appreciated.
UPDATE:
In Simple Injector 2.5 a new RegisterMvcIntegratedFilterProvider extension method has been added to the MVC Integration package that replaces the old RegisterMvcAttributeFilterProvider. This new RegisterMvcIntegratedFilterProvider contains the behavior of the SimpleInjectorFilterAttributeFilterProvider that is given below and allows better integration of attributes into the Simple Injector pipeline. This does mean however that by default, no properties are injected, but this can extended by implementing a custom IPropertySelectionBehavior. The use of the new RegisterMvcIntegratedFilterProvider is adviced over the old RegisterMvcAttributeFilterProvider method, which will be marked [Obsolete] in a future release.
When using the RegisterMvcAttributeFilterProvider extension method, Simple Injector will not call any registered initializer on MVC attributes. If you set a break point inside the anonymous delegate that injects the NotificationService you'll see it's never hit.
Simple Injector does however call the container.InjectProperties method on MVC attributes, but InjectProperties does implicit property injection, which means that it tries to inject all public properties on a type, but skips it if the property can't be injected (for what ever reason).
I bet the CriticalErrorAttribute.NotificationService property has a type of NotificationService instead of INotificationService. Since you didn't register NotificationService explicitly, the container will create a transient instance for you, which means you'll get a different instance for the CriticalErrorAttribute than the rest of the application is getting.
Quick fix: change the property type to INotificationService.
To be honest, I regret ever implemented the MVC integration package for Simple Injector to use the InjectProperties method. Implicit Property injection is very evil, because it doesn't fail fast when there's a misconfiguration and I'm even thinking about removing support for InjectProperties in the future. The problem is however that many developers are depending on InjectProperties. Either directly by calling it, or indirectly by letting the container inject properties on MVC attributes.
InjectProperties does not run any initializer. That's by design, and there are other constructs that allow running the full initialization process on objects that are not created by the container. Problem is however, that adding this could break existing clients, since this could result in properties being injected multiple times.
In your case, I suggest a different solution:
Prevent calling container.RegisterMvcAttributeFilterProvider() in the startup path of your application. This will register a special FilterAttributeFilterProvider that calls InjectProperties internally. You don't want to use implicit property injection, you want a more explicit (and complete) behavior. Instead register the following class:
internal sealed class SimpleInjectorFilterAttributeFilterProvider
: FilterAttributeFilterProvider
{
private readonly ConcurrentDictionary<Type, Registration> registrations =
new ConcurrentDictionary<Type, Registration>();
private readonly Func<Type, Registration> registrationFactory;
public SimpleInjectorFilterAttributeFilterProvider(Container container)
: base(false)
{
this.registrationFactory = type =>
Lifestyle.Transient.CreateRegistration(type, container);
}
public override IEnumerable<Filter> GetFilters(
ControllerContext context,
ActionDescriptor descriptor)
{
var filters = base.GetFilters(context, descriptor).ToArray();
foreach (var filter in filters)
{
object instance = filter.Instance;
var registration = registrations.GetOrAdd(
instance.GetType(), this.registrationFactory);
registration.InitializeInstance(instance);
}
return filters;
}
}
You can use the following code to register this custom provider:
var filterProvider =
new SimpleInjectorFilterAttributeFilterProvider(container);
container.RegisterSingle<IFilterProvider>(filterProvider);
var providers = FilterProviders.Providers
.OfType<FilterAttributeFilterProvider>().ToList();
providers.ForEach(provider => FilterProviders.Providers.Remove(provider));
FilterProviders.Providers.Add(filterProvider);
This custom SimpleInjectorFilterAttributeFilterProvider calls the Registration.InitializeInstance method. This method allows initialization a type that is already created and will initialize it by (among other things) calling the type initializer delegates.
For more information about working with attributes, please read the following discussion.

Categories

Resources