views:

65

answers:

2
using System;
using Castle.Windsor;
using Castle.MicroKernel.Registration;
using System.Reflection;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;

namespace Windsor
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            var container = new WindsorContainer ();

            container.Register (Component.For (typeof(IIface<, >)).ImplementedBy (typeof(HandlerImpl<, >)));
            //container.Register (Component.For (typeof(IIface<, >)).ImplementedBy(typeof(Impl2)));
            container.Kernel.Resolver.AddSubResolver (new ArrayResolver (container.Kernel));
            var normal = container.ResolveAll<IIface<Impl2, Stub>> ();
            var ex = container.ResolveAll<IIface<Impl1, Stub>> ();

            //var qwe = new HandlerImpl<Impl1, Stub> ();

            Console.WriteLine("Hello World!");
        }
    }

    public class Base {}

    public class Stub {}

    public interface AdditionalIface
    {
    }

    public interface IIface<T1, T2> where T1 : Base where T2 : class
    {
        T1 Command { get; set; }
    }

    public class HandlerImpl<T1, T2> : IIface<T1, T2> where T1 : Base, AdditionalIface where T2 : class
    {
        public T1 Command { get; set; }
    }

    public class Impl1 : Base
    {
    }

    public class Impl2 : Base, AdditionalIface
    {
    }
}

So, now if i do smth like:

var normal = container.ResolveAll<IIface<Impl2, Stub>> (); // this works ok
var ex = container.ResolveAll<IIface<Impl1, Stub>> (); // this throws exception abou not fullfilled constraints
// instead i want it just show no resolved implementations

Is there any way to make this work as I want it to?

+2  A: 

Actually this seems to be a bug in Windsor's code.

Update:

And it's fixed now

Krzysztof Koźmic
Thanks! Btw, it seems that trunk version is not using IHandlerSelector. I tried this one http://kozmic.pl/archive/2009/12/07/overriding-generic-componentrsquos-resolution-in-castle-windsor.aspxbut my selector was never called. Or are IHandlerSelector/ISubDependencyResolver called only in normal injection?
xumix
They are called for resolving single component. `ResolveAll`... well -resolves all, so there's nothing really to decide about here.
Krzysztof Koźmic
Ok, thanks again
xumix
+1  A: 

If you're injecting things normally (i.e. using the ArrayResolver and not calling ResolveAll() directly) you can work around it with a custom array resolver for this type only (or for types that have this problem):

public class CustomArrayResolver : ISubDependencyResolver {
    private readonly IKernel kernel;
    private readonly Type serviceTypeDefinition;

    public CustomArrayResolver(IKernel kernel, Type serviceTypeDefinition) {
        this.kernel = kernel;
        this.serviceTypeDefinition = serviceTypeDefinition;
    }

    private bool MatchesConstraints(Type service, Type impl) {
        try {
            impl.MakeGenericType(service.GetGenericArguments());
            return true;
        } catch (ArgumentException) {
            return false;
        }
    }

    public object Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver,
                          ComponentModel model,
                          DependencyModel dependency) {
        var service = dependency.TargetType.GetElementType();
        var handlers = kernel.GetAssignableHandlers(service);
        var components = handlers
            .Where(h => h.CurrentState == HandlerState.Valid)
            .Where(h => MatchesConstraints(service, h.ComponentModel.Implementation))
            .Select(h => h.Resolve(context, contextHandlerResolver, model, dependency))
            .ToArray();
        var r = Array.CreateInstance(service, components.Length);
        components.CopyTo(r, 0);
        return r;
    }

    public bool CanResolve(CreationContext context, ISubDependencyResolver contextHandlerResolver,
                           ComponentModel model,
                           DependencyModel dependency) {
        return dependency.TargetType != null &&
               dependency.TargetType.IsArray &&
               kernel.HasComponent(dependency.TargetType.GetElementType()) && 
               dependency.TargetType.GetElementType().IsGenericType &&
               dependency.TargetType.GetElementType().GetGenericTypeDefinition() == serviceTypeDefinition;
    }       
}

Register it right before the standard ArrayResolver:

container.Kernel.Resolver.AddSubResolver(new CustomArrayResolver(container.Kernel, typeof(IIface<,>)));
Mauricio Scheffer
Yeap, in the meantime until we fix the bug this is a good workaround :)
Krzysztof Koźmic
Thanks for solution! Btw, could you show me in a full test-case like mine, how to inject "normally"?
xumix
@xumix: By 'normally' I mean not calling ResolveAll() directly. When doing normal injection, you don't call directly Resolve() or ResolveAll(), you let the container resolve whatever it has to resolve. Otherwise you'd be doing Service Locator, not dependency injection. See `ResolveAll_CustomResolver()` in http://mausch.googlecode.com/svn/trunk/WindsorInitConfig/WindsorInitConfig/ResolveAllWithConstraints.cs
Mauricio Scheffer
I've looked into your example, but I see the same Resolve/All calls.
xumix
@xumix: you need to differentiate application code from infrastructure code. An ISubDependencyResolver is infrastructure and very Windsor-specific, you are allowed to use Resolve there. The Resolve() calls on the test itself are there because we are testing the container itself here. This is not regular application code. The point is that `Service` (which is application code) gets its array of `IIface` normally injected, it does not call the container directly.
Mauricio Scheffer