How to register a TImpl that has one moregeneric type parameter than TService in SimpleInjector?
-
03-12-2019 - |
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?
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).