Context based injection and .Verify()

Nov 6, 2013 at 2:44 PM
While implementing some new code utilizing the Simple Injector context based injection extensions I was running into a problem where the call to my
Func<DependencyContext, TService>
was happening as expected but the
DependencyContext.ImplementationType
and
DependencyContext.ServiceType
were both null. After a lot of hair-pulling it seems that this may be a product of the container.Verify() command I'm running at the end of my container setup. As a workaround I've written an
if (dependencyContext.ImplementationType == null && dependencyContext.ServiceType == null)
block into my
Func<DependencyContext, TService>
to handle the situation but it seems a little hokey. Is the behavior I'm seeing expected and if so, do you have a more elegant solution?
Coordinator
Nov 6, 2013 at 7:54 PM
The ServiceType and ImplementationType properties will return null when the registered service is requested directly from the container, opposed to being injected into another type. What you see happening is the Verify method requesting that service directly from the container to test whether the registration can be resolved or not. The Verify method simply iterates the list of registered services and calls GetInstance on every one of them.

So what you see happening is by design, although it's not very convenient in your case.

btw, note that you don't have to check both ImplementationType and ServiceType, since they will either both be null or both be not null. And there's a little helper extension method in the SimpleInjector.Advanced namespace (well hidden) called IsVerifying that allows you to check whether the container is currently verifying itself. So you could do something like this:
container.RegisterWithContext<ISomeService>(context =>
{
    if (context.ServiceType == null) {
        if (!container.IsVerifying())
            throw new InvalidOperationException("Don't request some service as root type");
        return new ServiceImpl<object>();
    }

    return (ISomeService)container.GetInstance(typeof(ServiceImpl<>).MakeGenericType(context.ServiceType));
}
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:18 PM
Nov 6, 2013 at 8:02 PM
Thanks for the quick and thorough response (as always)! The .IsVerifying() method should help make the intent of the code much clearer, great tip.
Coordinator
Nov 6, 2013 at 8:11 PM
One quick tip, if performance is a concern, prevent calling IsVerifying in critical code paths, since it will request a lock on the container. But if you're doing it the way as I showed in my example you're safe :-)
Nov 6, 2013 at 8:14 PM
The only added complication is that in my case the ServiceImpl<T> has restrictions on T as well as additional required constructor parameters.

So, unless you've got a trick up your sleeve I think I'll be have to do something like:
container.RegisterWithContext<ISomeService>(context =>
{
    if (context.ServiceType == null) {
        if (!container.IsVerifying())
            throw new InvalidOperationException("Can't request ISomeService directly from container, it must be injected as a dependency.");
        return (ISomeService)container.GetInstance(typeof(ServiceImpl<ArbitraryServiceType>));
    }

    return (ISomeService)container.GetInstance(typeof(ServiceImpl<>).MakeGenericType(context.ServiceType));
}
Coordinator
Nov 6, 2013 at 8:31 PM
I think this is as good as it gets, although you can use the generic GetInstance<T> overload when requesting the fake type like this:
return container.GetInstance<ServiceImpl<ArbitraryServiceType>();
Or you can refactor your code to this:
container.RegisterWithContext<ISomeService>(context =>
{
    if (context.ServiceType == null && !container.IsVerifying()) {
        throw new InvalidOperationException(
            "Can't request ISomeService directly from container, it must be injected as a dependency.");
    }

    return (ISomeService)container.GetInstance(typeof(ServiceImpl<>).MakeGenericType(
        context.ServiceType ?? typeof(ArbitraryServiceType)));
}