开发者

Autofac resolve question about keyed registration via IIndex

This is a follow-up question to Configuring an Autofac delegate factory that's defined on an abstract class. I've implemented the suggestion of using IIndex<K,V> that @Aren made in his answer, but I'm unable to overcome the following error:

Test method IssueDemoProject.WidgetTest.ProblemIllustration threw exception: Autofac.Core.DependencyResolutionException: None of the constructors found with 'Public binding flags' on type 'IssueDemoProject.WidgetWrangler' can be invoked with the available services and parameters: Cannot resolve parameter 'IssueDemoProject.WidgetType widgetType' of constructor 'Void .ctor(Autofac.IComponentContext, IssueDemoProject.WidgetType)'.

UPDATE: It should be noted that if I register different concrete classes based on a parameter, that works. See the second test below.

Here is some sample code that illustrates the issue. [EDIT: I updated the same to use an IIndex lookup.]

Can someone tell me what I'm doing wrong?

using Autofac;
using Autofac.Features.Indexed;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace IssueDemoProject
{

public enum WidgetType
{
    Sprocket,
    Whizbang
}

public class SprocketWidget : Widget
{
}

public class WhizbangWidget : Widget开发者_StackOverflow社区
{
}

public abstract class Widget
{
}

public class WidgetWrangler : IWidgetWrangler
{
    public Widget Widget { get; private set; }

    public WidgetWrangler(IComponentContext context, WidgetType widgetType)
    {
        var lookup = context.Resolve<IIndex<WidgetType, Widget>>();
        Widget = lookup[widgetType];
    }
}

public interface IWidgetWrangler
{
    Widget Widget { get; }
}

[TestClass]
public class WidgetTest
{
    // NOTE: This test throws the exception cited above
    [TestMethod]
    public void ProblemIllustration()
    {
        var container = BuildContainer(
            builder =>
                {
                    builder.RegisterType<WidgetWrangler>().Keyed<IWidgetWrangler>(WidgetType.Sprocket).
                        InstancePerDependency();
                    builder.RegisterType<WidgetWrangler>().Keyed<IWidgetWrangler>(WidgetType.Whizbang).
                        InstancePerDependency();
                }
            );

        var lookup = container.Resolve<IIndex<WidgetType, IWidgetWrangler>>();
        var sprocketWrangler = lookup[WidgetType.Sprocket];
        Assert.IsInstanceOfType(sprocketWrangler.Widget, typeof(SprocketWidget));

        var whizbangWrangler = container.ResolveKeyed<IWidgetWrangler>(WidgetType.Whizbang);
        Assert.IsInstanceOfType(whizbangWrangler.Widget, typeof(WhizbangWidget));
    }

    // Test passes
    [TestMethod]
    public void Works_with_concrete_implementations()
    {
        var container = BuildContainer(
            builder =>
                {
                    builder.RegisterType<SprocketWidget>().Keyed<Widget>(WidgetType.Sprocket).
                        InstancePerDependency();
                    builder.RegisterType<WhizbangWidget>().Keyed<Widget>(WidgetType.Whizbang).
                        InstancePerDependency();
                });

        var lookup = container.Resolve<IIndex<WidgetType, Widget>>();
        var sprocketWrangler = lookup[WidgetType.Sprocket];
        Assert.IsInstanceOfType(sprocketWrangler, typeof(SprocketWidget));

        var whizbangWrangler = container.ResolveKeyed<Widget>(WidgetType.Whizbang);
        Assert.IsInstanceOfType(whizbangWrangler, typeof(WhizbangWidget));
    }

    private IComponentContext BuildContainer(Action<ContainerBuilder> additionalRegistrations)
    {
        var assembly = GetType().Assembly;
        var builder = new ContainerBuilder();
        builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces();
        builder.RegisterAssemblyTypes(assembly).AsSelf();
        additionalRegistrations(builder);
        IComponentContext container = builder.Build();
        return container;
    }    }
}


I think you are going about this wrong. Mainly because your WidgetWrangler is expecting both the factory (IIndex<,> effectively becomes your factory), and a WidgetType.

The exception is because your WidgetType enum doesn't have a default registration in Autofac, and it probably shouldn't. Autofac cannot figure out what value to pass into the constructor as I'm guessing you're trying to use Autofac to Reolve your WidgetWrangler.

In order to resolve something from Autofac, it must contain at least one constructor that can be resolved entirely by Autofac's Registrations, OR with you explicitly passing variables to the resolve operation (please note, this method is reflective and extremely slow).

I am assuming the goal of this whole thing is to have some way to retrieve a new Subclass of Widget given a WidgetType somewhere. If this is the case, then ALL YOU NEED is the IIndex<,>.

Wherever you need to create new Widgets you just use the IIndex<,> type. It's simple.

If you need to perform another operation along with your instantiation you should wrap the IIndex in a factory class that uses IIndex then performs the actions.

For Example:

class Widget
{
    // Stuff...

    public virtual void Configure(XmlDocument xmlConfig)
    {
        // Config Stuff
    }
}

interface IWidgetFactory
{
     Widget Create(WidgetType type, XmlDocument config);
}

class WidgetFactory : IWidgetFactory
{
    private readonly IIndex<WidgetType, Widget> _internalFactory;
    public WidgetFactory(IIndex<WidgetType, Widget> internalFactory)
    {
        if (internalFactory == null) throw new ArgumentNullException("internalFactory");
        _internalFactory = internalFactory;
    }

    public Widget Create(WidgetType type, XmlDocument config)
    {
        Widget instance = null;
        if (!_internalFactory.TryGetValue(type, out instance))
        {
            throw new Exception("Unknown Widget Type: " + type.ToString);
        }

        instance.Configure(config);

        return instance;
    }
}

You can then just used the wrapped factory simply:

class SomethingThatNeedsWidgets
{
    private readonly IWidgetFactory _factory;
    public SomethingThatNeedsWidgets(IWidgetFactory factory)
    {
        if (factory == null) return new ArgumentNullException("factory");
        _factory = factory;
    }

    public void DoSomething()
    {
        Widget myInstance = _factory.Create(WidgetType.Whizbang, XmlDocument.Load("config.xml"));

        // etc...
    }
}

Remember, If you don't need to do anything other than get the Widget instance back, your IIndex<WidgetType, Widget> is a factory in itself. (assuming all registered Widget subclasses are InstancePerDependency registered. Otherwise it's an instance selector.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜