Question

I am doing the following right now

container.Register<IDatabaseMapper<User>, DatabaseMapper<User, OracleException>>();
container.Register<IDatabaseMapper<Desk>, DatabaseMapper<Desk, OracleException>>();
container.Register<IDatabaseMapper<Commodity>, DatabaseMapper<Commodity, OracleException>>();

But i would like to do something like this

container.RegisterOpenGeneric(typeof(IDatabaseMapper<>), typeof(DatabaseMapper<,OracleException>));

Is this somehow possible?

Was it helpful?

Solution

Is this possible? Yes and no :-)

typeof(DatabaseMapper<,OracleException>) is not valid C# code. You either have to supply all generic type arguments or none at all. So there is no way to inform the Container that it should fill in the missing TException type argument with a OracleException. So no, you can't do this.

But yes, of course you can do this :-). Simply create a OracleExceptionDatabaseMapper<T> class that inherits from DatabaseMapper<T, OracleException> and use that type in the registration:

// Helper class
public class OracleExceptionDatabaseMapper<T>
    : DatabaseMapper<T, OracleException>
{
}

// Registration
container.RegisterOpenGeneric(typeof(IDatabaseMapper<>),
    typeof(OracleExceptionDatabaseMapper<>));

This way the given implementation has only 1 generic type and that can be mapped to the single generic type argument of the given service interface.

UPDATE

Since Simple Injector 2.4 it is possible to register parial open generic types, but since this is still not supported by C# you will have to create the partial open generic type by hand as follows:

Type databaseMapperType = typeof(DatabaseMapper<,>).MakeGenericType(
    typeof(DatabaseMapper<,>).GetGenericArguments().First(),
    typeof(OracleException));

container.RegisterOpenGeneric(typeof(IDatabaseMapper<>), databaseMapperType);

OTHER TIPS

For completeness, here is an example of how to do this using unregistered type resolution:

container.ResolveUnregisteredType += (s, e) =>
{
    var serviceType = e.UnregisteredServiceType;

    if (serviceType.IsGenericType &&
        serviceType.GetGenericTypeDefinition() == typeof(IDatabaseMapper<>))
    {
        Type argument = serviceType.GetGenericArguments()[0];

        var closedDatabaseMapperType = typeof(DatabaseMapper<,>)
            .MakeGenericType(argument, typeof(OracleException));

        var registration =
            container.GetRegistration(closedDatabaseMapperType, true);

        e.Register(registration.BuildExpression());
    }
};

The ResolveUnregisteredType event will be called by the container whenever a type is requested that is not registered. This gives you a last chance to register that type. The supplied UnregisteredTypeEventArgs contains two Register method overloads that allow you to register that type (using either a Func<T> or using an Expression).

The code above checks to see if the requested service type is an IDatabaseMapper<T> and if so, it will construct a DatabaseMapper<T, OracleExpression> where T is replaced with the actual type of the service type. Using that type a registration for that type is requested from the container. Using the BuildExpression method of that registration object, we can build an expression tree that describes the creation of a new instance of that DatabaseMapper. This expression id than registered using the e.Register method, which effectively maps the IDatabaseMapper<T> to the creation of a DatabaseMapper<T, OracleException>.

IMPORTANT: I believe that using unregistered type resolution should only be used as fallback option, since there are often easier ways to solve your problem (such as the one I shown in my other answer), but unregistered type resolution can be useful in certain advanced scenarios (when the DatabaseMapper<T, TException> is sealed for instance).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top