Idea for a new feature to be added to the NamedServices library.
The idea is:
In application code, something requests a named service but no service has been registered with that name yet.
You can provide a delegate that will respond to the above, and optionally add a new registration for that name that will be used in future.
For example this might be useful if:
Your system is very dynamic - perhaps you create new tenants at runtime, and these tenants have different identifiers created at runtime. When a tenant spins up during application use, you want to get a service based on the tenant id. You can't register this on application startup because its not known ahead of time how many tenants there will be and what their ideas are. With late registrations the first time the service is requested, it can "late bound" and a registration created for example, as singleton, scoped, or transient service registration.
For example (pseudocode)
services.AddNamed<MyService>((namedServices)=>
{
namedServices.AddTransient<MyService>("Foo");
// different idea that just occurred (seperate issue):
// namedServices.ForwardName("bar", "foo"); // forwards any requests for "bar" to service named "foo".
// When no named registration exists for the name requested by the caller, then it can be lazily provided:
namedService.MapName("bar", "foo"); // maps any requests for "bar" to be resolved by "foo".
<MyService>("LookedUpA");
namedService.AddTransient<MyService>("LookedUpB");
namedServices.AddLookup((result, requestedName)=>
{
if(requestedName == "Bar")
{
return result,Singleton<DerivedMyService>();
}
else
{
return descriptors,Scoped(sp=>new MyService());
}
}
});
This equates to:
If the service named "Foo" is requested, it resolves to a Transient instance.
If the service named "Bar" is requested, as there is no named registration for "Bar", the DynamicName delegate is executed, which returns a Singleton service descriptor that returns DerivedMyService to satisfy that request,
Each subsequent time "Bar" is requested, the previously returned registration will be used to resolve the service the DyanmicName delegate will no longer fire when service is requested with that same name,
If the service is requested with an unrecognised name, it will be resolved to a new
wait for a Named named service to be requested, before lazily creating the service registration for that name that is requested.
This would be useful, for example, in a multi-tenant system, where tenants are lazily identified for an incoming request, you might want to register that tenants IServiceProvider by name, and you don't know what this name will be when the application starts up, as tenants are added dynamically whilst the application is running.
Should you create and register your own Factory class for this? Probably yes. However in essence that's what this is - you are essentially registering a factory that will resolve a service by name, except that the name can be dynamic, and the service can also be associated with a lifetime, and then used with DI which is really useful.
I am thinking of extending the API with something like this:
var services = new ServiceCollection();
services.AddNamed<AsyncLazy<Foo>>(names =>
{
names.Resolve((requestedName, satisfy) =>
{
return satisfy.Transient<AsyncLazy>(sp=>new AsyncLazy<Foo>(async () =>await CreateFooAsync()));
});
});
It would also be amazing to evict / expire the registration so that it gets recreated on the next request. However applications wishing to use this feature would have to put in their own control to make sure singleton IDisposable services is not currently being used (for example on current http request scopes) when it is triggered for eviction as it will be disposed.
AsyncLazy is a well known pattern and it looks like this:
public sealed class AsyncLazy<T>
{
/// <summary>
/// The underlying lazy task.
/// </summary>
private readonly Lazy<Task<T>> instance;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncLazy<T>"/> class.
/// </summary>
/// <param name="factory">The delegate that is invoked on a background thread to produce the value when it is needed.</param>
public AsyncLazy(Func<T> factory)
{
instance = new Lazy<Task<T>>(() => Task.Run(factory));
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncLazy<T>"/> class.
/// </summary>
/// <param name="factory">The asynchronous delegate that is invoked on a background thread to produce the value when it is needed.</param>
public AsyncLazy(Func<Task<T>> factory)
{
instance = new Lazy<Task<T>>(() => Task.Run(factory));
}
/// <summary>
/// Asynchronous infrastructure support. This method permits instances of <see cref="AsyncLazy<T>"/> to be await'ed.
/// </summary>
public TaskAwaiter<T> GetAwaiter()
{
return instance.Value.GetAwaiter();
}
/// <summary>
/// Starts the asynchronous initialization, if it has not already started.
/// </summary>
public void Start()
{
var unused = instance.Value;
}
}
In this example, if suppose in some part of the system you tried to resolve a service with a name that was completely made up. like today's date, or a new GUID
public MyController(NamedServiceResolver<AnimalService> namedServices)
{
AnimalService serviceA = namedServices[Guid.NewGuid().ToString()];
}
This would invoke the Dynamic delegate, and give you an opportunity to lazily add a registration for that new name. Or log an error,, or anything else you might want to do.
In the case above, we set up a new transient registration for that name, In future resolutions using the same name, the service will just be resolved as if it was always registered with that name to begin with.
You will not be able to specify your own name when satisfying a dynamic name registration, as the registration is for the requestedName and will be associated with that name only with no option to change that.
names.Dynamic((requestedName, satisfy) =>
{
// The name for this registration is implicitly the requestedName - you won't have to specify that.
return satisfy.Transient<LionService>();
});
Why would this be useful
It would allow you too dynamically partition the application based on named areas.
For example, suppose you have a multi-tenant system;
Idea for a new feature to be added to the NamedServices library.
The idea is:
For example this might be useful if:
For example (pseudocode)
This equates to:
If the service is requested with an unrecognised name, it will be resolved to a new
wait for a Named named service to be requested, before lazily creating the service registration for that name that is requested.
This would be useful, for example, in a multi-tenant system, where tenants are lazily identified for an incoming request, you might want to register that tenants
IServiceProvider
by name, and you don't know what this name will be when the application starts up, as tenants are added dynamically whilst the application is running.Should you create and register your own Factory class for this? Probably yes. However in essence that's what this is - you are essentially registering a factory that will resolve a service by name, except that the name can be dynamic, and the service can also be associated with a lifetime, and then used with DI which is really useful.
I am thinking of extending the API with something like this:
It would also be amazing to evict / expire the registration so that it gets recreated on the next request. However applications wishing to use this feature would have to put in their own control to make sure singleton IDisposable services is not currently being used (for example on current http request scopes) when it is triggered for eviction as it will be disposed.
AsyncLazy
is a well known pattern and it looks like this:In this example, if suppose in some part of the system you tried to resolve a service with a name that was completely made up. like today's date, or a new GUID
This would invoke the
Dynamic
delegate, and give you an opportunity to lazily add a registration for that new name. Or log an error,, or anything else you might want to do.In the case above, we set up a new transient registration for that name, In future resolutions using the same name, the service will just be resolved as if it was always registered with that name to begin with.
You will not be able to specify your own name when satisfying a dynamic name registration, as the registration is for the requestedName and will be associated with that name only with no option to change that.
Why would this be useful
It would allow you too dynamically partition the application based on named areas.
For example, suppose you have a multi-tenant system;