Reputation: 60564
I'm trying to get something like this working:
public class FooService : IFooService {
public FooService(Func<IBarService> barFactory) { ... }
}
public class BarService : IBarService, IDisposable { ... }
services.AddSingleton<IFooService, FooService>();
services.AddTransient<IBarService, BarService>();
services.AddSingleton<Func<IBarService>>(ctx => () => ctx.GetService<IBarService());
This works as far as resolving the BarService
instance, but I can't figure out how to properly manage its lifetime. When I do this inside one of the members of FooService
:
using (var bar = _barFactory())
{
...
}
I get an ObjectDispoedException
:
System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
However, if I just do var bar = _barFactory();
, without the using statement, I have no way to signal to the DI container that I'm done with the instance, and it can be disposed.
What's the correct approach here?
(Side note: yes, I know that some will object that a singleton service should not be dependent on a transient service. That's not what's happening here; the singleton service is dependent on a singleton factory, that produces transient instances. The singleton then uses the transient service for one or two statements and then is done with it, so there should be no actual lifetime problems here.)
Upvotes: 7
Views: 6987
Reputation: 2575
In a similar situation, I needed to dispose of created services before the end of scope. Also, I wanted to dynamically register factories by reflection. I had managed to get it working by using expression trees:
public static class FactoryFuncServiceCollectionExtensions
{
/// <summary>
/// Add <see cref="System.Func{}">Func<<paramref name="serviceType"/>></see> that creates a fresh instance.
/// </summary>
public static void AddFactoryFunc(this IServiceCollection services, Type serviceType, Type implType)
{
services.AddSingleton(typeof(Func<>).MakeGenericType(serviceType), (sp) =>
{
ObjectFactory factory = ActivatorUtilities.CreateFactory(implType, Array.Empty<Type>());
InvocationExpression invoke = Expression.Invoke(Expression.Constant(factory), Expression.Constant(sp), Expression.Default(typeof(object[])));
LambdaExpression lambda = Expression.Lambda(Expression.Convert(invoke, serviceType));
return lambda.Compile();
});
}
/// <summary>
/// Add <see cref="System.Func{,}">Func<object[], <paramref name="serviceType"/>></see> that creates a fresh instance.
/// </summary>
public static void AddFactoryFuncWithArgs(this IServiceCollection services, Type serviceType, Type implType)
{
ConstructorInfo ctor = implType.GetConstructors(BindingFlags.Instance | BindingFlags.Public) switch
{
{ Length: 1 } ctors => ctors[0],
{ Length: 0 } => throw new ConfigurationErrorsException($"No public constructor for {implType}"),
_ => throw new ConfigurationErrorsException($"Found more than one constructor for {implType}"),
};
services.AddSingleton(typeof(Func<,>).MakeGenericType(typeof(object[]), serviceType), (sp) =>
{
IServiceProviderIsService isService = sp.GetRequiredService<IServiceProviderIsService>();
Type[] ctorParamTypes = ctor
.GetParameters()
.Select(param => param.ParameterType)
.Where(t => !isService.IsService(t))
.ToArray();
ObjectFactory factory = ActivatorUtilities.CreateFactory(implType, ctorParamTypes);
ParameterExpression funcParam = Expression.Parameter(typeof(object[]));
InvocationExpression invoke = Expression.Invoke(Expression.Constant(factory), Expression.Constant(sp), funcParam);
UnaryExpression cast = Expression.Convert(invoke, serviceType);
LambdaExpression lambda = Expression.Lambda(cast, funcParam);
return lambda.Compile();
});
}
}
Add the factory func like:
services.AddFactoryFunc(typeof(IBarService), typeof(BarService));
Upvotes: 0
Reputation: 25019
As described in documentation:
The container will call
Dispose
forIDisposable
types it creates. However, if you add an instance to the container yourself, it will not be disposed.
So just don't use using
statement and all should be OK.
Upvotes: 4