SimpleInjector force Lifestyle.Transient for Unregistered-Type

Viewed 145

I have a wrapper class as below:

// this is not used for IoC purposes, but by code to cast object to known T
internal interface ITaskCompletionSourceWrapper
{
    void SetResult(object result);
    /* snip */
}

internal class TaskCompletionSourceGenericWrapper<TResult> : ITaskCompletionSourceWrapper
{
    readonly TaskCompletionSource<TResult> tcs;
    public TaskCompletionSourceGenericWrapper() =>        
        this.tcs = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
    public void SetResult(object result) => tcs.SetResult((TResult)result);
    /* snip */
}

Where i currently instantiate using Activator:

var responseType = request
                     .GetType()
                     .GetInterfaces()
                     .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>))
                     GetGenericArguments()[0];

var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(responseType);
var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;

I switched this to SimpleInjector (as may use logging and other IOC goodness) and thought all was well until I found an issue (which was due to lifestyle being shared):

/* snip responseType code from above */
var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(responseType);
var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;

// i can see the type has been registered as scoped, but we need it to be unique
[30] = ServiceType = TaskCompletionSourceGenericWrapper<Object>, Lifestyle = Async Scoped

The above is being called within a AsyncScopedLifestyle.BeginScope block.

I would like to instantiate this wrapper at runtime using SimpleInjector (but ensuring Lifestyle.Transient):

The types for the wrapper are pretty much arbitrary from int, string, bool etc but could also be simple POCO classes.

This is similar to a case of open generic registration but I would rather avoid having other developers specify assemblies for the simple types (as for sure they will forget, and their classes are simple anyways - command/query POCOs and the like).

How can I achieve the above please or am I forced to register collection?

Or is this deeper with lifestyle mismatches - I hope not, since the wrapper has no dependencies (and will not other than ILogger perhaps) is only doing something instantiation of a simple class (which I want to replace Activator rather than decouple components IoC style).

1 Answers

Based on their docs Batch-Registration / Auto-Registration and experience, you need to register the implementation of the interface when using dependency injection.

var container = new Container();
// If related classes exist in a different assembly then add them here
var assemblies = new[] { typeof(TaskCompletionSourceGenericWrapper<>).Assembly };

container.Collection.Register(typeof(TaskCompletionSourceGenericWrapper<>), assemblies, Lifestyle.Transient);

DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

One hack that I can think of but haven't tested is to create a wrapper class that returns a deep copy a.k.a clone of your wrapper. Returning a clone of an object is similar to transient as it returns a new instance every time you request for it but the downside on this approach is that you can only do it for serializable classes so you may need to add [Serializable()]. Refer to Deep cloning objects for more info.

public interface IUglyDuckling
{
    ITaskCompletionSourceWrapper GetITaskCompletionSourceWrapper(Type type);
}
public class UglyDuckling : IUglyDuckling
{
    public ITaskCompletionSourceWrapper GetITaskCompletionSourceWrapper(Type type)
    {
        var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(type);
        var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;
        return ObjectCopier.Clone<ITaskCompletionSourceWrapper>(tcsWrapper);
    }
}
Related