Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RegisterOpenGeneric with SimpleInjector resolves incorrect type

We'll start with a standalone story, just so you understand why: I want to treat any actions that change data against the same interface: ICommand There are things that exist out there called ICommandHandlers that handle any command I want. So, if I want a CreatePersonCommand I'd need a CreatePersonCommandHandler out there.

So here's the body of a console application that demonstrates this: (Requires Simple Injector)

// The e.g. CreatePersonCommand, with TResult being Person, as an example.
public interface ICommand<TResult>
{
}

//This handles the command, so CreatePersonCommandHandler
public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

// Imagine a generic CRUD set of operations here where we pass 
// in an instance of what we need made
public class CreateBaseCommand<TModel> : ICommand<TModel>
{
    public TModel ItemToCreate { get; set; }
}

public class DeleteBaseCommand<TModel> : ICommand<TModel>
{
    public TModel ItemToDelete { get; set; }
}

public class CreateCommandBaseHandler<TModel> 
    : ICommandHandler<CreateBaseCommand<TModel>, TModel>
{
    public TModel Handle(CreateBaseCommand<TModel> command)
    {
        // create the thing
        return default (TModel);
    }
}

public class DeleteCommandBaseHandler<TModel> 
    : ICommandHandler<DeleteBaseCommand<TModel>, TModel>
{
    public TModel Handle(DeleteBaseCommand<TModel> command)
    {
        // delete the thing
        return default(TModel);
    }
}

public class Program
{
    private static Container container;

    static void Main(string[] args)
    {
        container = new Container();

        // Order does not seem to matter, I've tried both ways.
        container.RegisterOpenGeneric(typeof(ICommandHandler<,>),
            typeof(DeleteCommandBaseHandler<>));
        container.RegisterOpenGeneric(typeof(ICommandHandler<,>),
            typeof(CreateCommandBaseHandler<>));

        container.Verify();

        // So I want to make the usual hello world
        var commandToProcess = new CreateBaseCommand<string> { ItemToCreate = "hello world"};

        // Send it away!
        Send(commandToProcess);
    }

    private static void Send<TResult>(ICommand<TResult> commandToProcess)
    {
        //{CreateBaseCommand`1[[System.String,..."}
        var command = commandToProcess.GetType();
        //{Name = "String" FullName = "System.String"}
        var resultType = typeof (TResult);

        //"ICommandHandler`2[[CreateBaseCommand`1[[System.String,..."}
        // so it's the right type here
        var type = typeof(ICommandHandler<,>).MakeGenericType(command, resultType); 

        // This is where we break!
        var instance = container.GetInstance(type);
        // The supplied type DeleteCommandBaseHandler<String> does not implement 
        // ICommandHandler<CreateBaseCommand<String>, String>.
        // Parameter name: implementationType
    }
}

So for whatever reason SimpleInjector always tries to resolve the DeleteCommandHandler<> for the CreateBaseCommand<> that I have. Again, the order does not matter. I have other, closed-type, commandhandlers (and their respective commands) that just inherits ICommandHandler<,> which work fine.

I spent a good bit of time going through every possible type of registration I could from this.

like image 310
Matt V Avatar asked Oct 03 '22 16:10

Matt V


1 Answers

UPDATE:

This is definitely a bug in the current release. This somehow slipped through the unit testing cracks. The code misses a check that verifies whether a built closed generic implementation actually implements the requested closed generic service type. If all the generic type constraints are valid, the framework considers the resolution successful, which is incorrect in your case.

The fix is rather easy and the coming v2.4 will definitely fix this, but in the meantime, you'll have to with the following workaround.

UPDATE 2:

The is actually quite nasty and can be quite hard to workaround in some cases. Besides RegisterOpenGeneric, decorator registrations are affected as well. This made me conclude that this had to be fixed fast and can't wait till the next minor release. I therefore pushed Version 2.3.6 to NuGet and CodePlex. v2.3.6 fixes this issue.

WORKAROUND:

The workaround is to prevent supplying generic type arguments that are nested into other types (such as your DeleteBaseCommand<TModel>). Instead you can fall back to using generic type constraints instead, as can be seen in the following example:

public class CreateCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel> // no nested generic arguments here
    where TCommand : CreateBaseCommand<TModel> // but type constraint here.
{
    public TModel Handle(TCommand command)
    {
        // create the thing
        return default(TModel);
    }
}

public class DeleteCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel>
    where TCommand : DeleteBaseCommand<TModel>
{
    public TModel Handle(TCommand command)
    {
        // delete the thing
        return default(TModel);
    }
}
like image 69
Steven Avatar answered Oct 13 '22 09:10

Steven