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
Related
Perhaps I'm just using the wrong terms while searching, but I haven't found any solid guidance around how to do what I'm seeking to do.
All the guidance around DI registration follows something like the following:
builder.Services.AddSingleton<MyService>(() => new MyService("connectionString"));
But this seems too simple for me to use over here in the real world. I don't store my various credentials in my applications, but rather put them somewhere else like Azure Key Vault or authenticate using a managed identity that itself retrieves connection strings and keys.
This introduces the need then to access the credentials/connection string first, which increasingly is exposed only as an asynchronous operation and introduces the problem I regularly face: namely, asynchronous registration isn't a thing.
I could register a service that itself retrieves and exposes the credential in an async method, but now every downstream service is going to need to know about that method in order to utilize it - I can't simply abstract it away in a DI registration.
I could just use .Result or Wait(), but there's plenty of solid guidance that suggests this shouldn't be done for deadlocking reasons. Because this code may be intended for a library that's consumed by an app with a UI, that's a no-go.
So the question is: When I'm unable to synchronously provide my credentials, how do I register my services?
Real-world example
For example, let's say I've got a web app that needs to access Cosmos DB, but via a managed identity, following the instructions here. I need to store some information about the Cosmos DB instance which means a dependency on IConfiguration and I'd like to use a singleton HttpClient to retrieve the necessary keys.
I want to put this into a separate service responsible for setting up the Cosmos DB client so that downstream usages can just inject the CosmosClient, so my class looks like:
public class CosmosKeyService
{
private readonly MyCosmosOptions _cosmosOptions;
private readonly HttpClient _http;
public CosmosKeyService(IOptions<MyCosmosOptions> options, HttpClient http)
{
_cosmosOptions = options.Value;
_http = http;
}
private async Task<string> GetCosmosKey()
{
//Follow instructions at https://learn.microsoft.com/en-us/azure/cosmos-db/managed-identity-based-authentication#programmatically-access-the-azure-cosmos-db-keys
//...
var keys = await result.Content.ReadFromJsonAsync<CosmosKeys>();
return keys.PrimaryMasterKey;
}
public async Task<CosmosClient> GetCosmosClient()
{
var key = await GetCosmosKey();
return new CosmosClient(_cosmosOptions.CosmosDbEndpoint, key);
}
}
To support the DI used in this class, my registration then looks like:
builder.Services.Configure<MyCosmosOptions>(builder.Configuration.GetSection("cosmosdb"));
builder.Services.AddSingleton<HttpClient>();
And of course I'm going to need to register this service:
builder.Services.AddSingleton<CosmosKeyService>();
But now I'd also like to register the CosmosClient as created by the method in that service and this is where I start getting confused about the best way forward.
I can't retrieve an instance of the CosmosKeyService from the builder because I haven't yet built it, and after I do, I can't then register new services.
I can't use async methods in the registration itself or I could easily do something like:
builder.Services.AddSingleton<CosmosClient>(async services => {
var keyService = services.GetService<CosmosKeyService>();
return await keyService.GetCosmosClient();
});
...and downstream services could simply inject CosmosClient in their various constructors.
Again, any downstream consumer can just inject a CosmosKeyService, but now they're all going to have to "remember" to call the initialization method first so they can retrieve the CosmosClient and utilize it. I'd rather that be handled in registration so that 1) this initialization is hidden and centrally located and 2) the CosmosClient is truly a singleton and not just an artifact of every utilization.
I could create another intermediate service that injects this Key resolver service and retrieve the keys, but it too will need to have this async method that retrieves the keys since I can't just hide that initialization in a registration somewhere (for lack of async support).
For example, I could make another service:
public class CosmosBuilder
{
private readonly CosmosKeyService _keySvc;
public CosmosBuilder(CosmosKeyService keySvc)
{
_keySvc = keySvc;
}
public async Task<CosmosClient> GetCosmosClient()
{
return async _keySvc.GetCosmosClient();
}
}
But this ultimately still requires a downstream service to inject this service and call that initialization method and if that's necessary, I might as well just stick with injecting the CosmosKeyService and call the method there.
What I'd ideally like to see is some way to hide any async initialization in the registration so that downstream consumers can simply inject CosmosClient and it works, but it's beyond me how that's intended to happen. Can anyone shed some light on this?
Edit to address comment:
I don't want to comment on a 4-year old answer, but the issue I assert with the accepted answer boils down to this part:
Move [initialization] into the Composition Root. At that point, you can create an initialize those classes before registering them in the container and feed those initialized classes into the container as part of registrations.
That's all well and good except:
I only get to "build" my container a single time. I can't build it, then utilize the registrations to accomplish the initialization, then append still more registrations to it for later use.
In my example above, I explicitly utilize elements registered in DI by ASP.NET Core itself (namely IConfiguration), so there's simply no way to even access these except via DI (which, per #1, precludes me from being able to initialize and later supplement my registrations with more implementations).
I am trying to find a way to inject a dependency in Startup.cs, for a aspnet5 application. I am aware of the following previous question, but hopefully I can expand on my particular requirements:
Aspnet core 5.0 inject dependency in startup.cs constructor
I initially needed a way to fire a custom method after OnSignIn with MS Identity Platform (IP) within my app. After posting the following questions, I was quickly pointed in the right direction:
How to fire a custom method after OnSignIn with MS Identity Platform
In short, during ConfigureServices in StartUp.cs, I needed to configure the IP middleware service, to add my own methods to some of its available events. Works a treat!
So I initially tried:
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = async ctex =>
{
// Blah
},
});
As I say, this works great. However, to do anything serious, I need access to my existing code and the DI magic that goes with it. This is where I a hitting this problem. I need my custom logic that requires DI, during ConfigureServices, before I have configured the services for DI.
On the same post, someone pointed out that you can wrap up the configuration, and essentially inject it back in, so you can get DI back. Great! But for some reason, I can only inject the handler, either as a Singleton, or Transient. Not Scoped. I get to some extent why it can't be scoped (if it is has a dependency of its own on a transient service), but then are why both extremes ok? Why is Singleton OK? Why is Transient? It just so happens, but my DI requirements are that I would need to inject scoped services, as that is what I need. I need some of the data to persist across instances, but I can't have a single instances shared across the whole application/user base.
So then I tried:
public class AzureAdOpendIdHandler : IConfigureNamedOptions<OpenIdConnectOptions>
{
public AzureAdOpendIdHandler(Authentication.AuthenticationManager authenticationManager)
{
AuthenticationManager = authenticationManager;
}
public Authentication.AuthenticationManager AuthenticationManager { get; }
public void Configure(string name, OpenIdConnectOptions options)
{
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
//Blah
return Task.CompletedTask;
},
};
}
}
Then called in StartUp.cs like:
services.AddScoped<Authentication.IAuthenticationManager, Authentication.AuthenticationManager>();
services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, AzureAdOpendIdHandler>();
Again, the handler works fine, and everything fires, as long as I don't try and pass "AuthenticationManager" within the constructor. I get an error saying that I can't inject a scoped service into a singleton service.
I keep getting within spitting distance of doing what I need, but there is always another block in the way.
In summary:
To override/extend MS Identity Platform, I need to make changes in ConfigureServices() in StartUp.cs
These changes require my classes to be available via DI.
The DI must allow me to pass them as scoped
So far, I can only meet the first two requirements, I can not find a way to satisfy all three.
My end requirement is to be able to add a call to my AuthenticationManager class from within the following OnTicketReceived Event:
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = async ctex =>
{
//use my AuthenticationManager class!!
},
});
I have tried the direct method, wrapping things up in a handler and injecting, but DI seems to be getting in the way now.
Any help, would be gratefully received.
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.
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.
I've been using a trick for a while to help with maintaining an audit trail. In or before the controller, I create a User which is bound in some way to the request. I can use DI to create most of my application as singletons and I can just inject a Func<User> wherever I think I need User information. I get the per-request User from the Func and can easily add audit information to everything.
This keeps my domain classes User agnostic and lets my DI container act as a User management system.
Now I'm using asp.net 5 and I'm having trouble doing the same thing. Honestly I've never been sure I should be able to do this, but I've gotten used to it.
I'm trying to do something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<IUser, User>();
services.AddSingleton<IDependantOnUser, DependantOnUser>
services.AddScoped<Func<IUser>(c => c.GetRequiredService<IUser>);
}
Then in or before my controller I create and populate the user instance.
public class ValuesController : Controller
{
public ValuesController(Func<User> userFunc)
{
user = userFunc();
// hydrate user instance as needed
}
}
Then finally, I should have access to the user instance in my singleton object.
public class DependantOnUser : IDependantOnUser
{
public DependantOnUser(Func<User> userFunc)
{
user = userFunc();
// I want this to be the same instance as that generated by the controller
}
}
But I can't get this to work. Before asp.net 5, I've been using Autofac to achieve this, but haven't had any luck there. I've tried playing around with transient/scoped/singleton a bit with no luck. I've even tried resolving my own IServiceProvider and using it directly instead of just generating a user with c => c.GetRequiredService<IUser>
Everything I do seems to be working with the wrong IServiceProvider instance. Is there a way resolve an instance from a different ServiceProvider? Any other suggestions would also be helpful.
Before you suggest I just register everything using AddScoped(), some of the objects between my presentation and persistence layers work a lot better as singletons.
Also I would prefer not to just pass User information as a parameter to every method in my domain (we record it with nearly every CRUD operation and pass it with most external calls we make)
I believe that it is antipattern to inject scope depedency to singleton one, please refer to Captive Dependencies
Autofac Captive Dependencies