简体   繁体   中英

AutoFac NamedParameter not resolving correctly

Im messing round in autofac and im having some issues binding to a specific constructor.

I have the following code:

var builder = new ContainerBuilder();

builder
    .RegisterType<GenericIocFactory>()
    .As<IGenericIocFactory>();

builder
    .RegisterType<Product>()
    .As<IProduct>()
    .PropertiesAutowired();

IContainer Container = builder.Build();

IGenericIocFactory Fac = Container.Resolve<IGenericIocFactory>();

_product = Fac.Get<IProduct>(new Dictionary<string,object>() { {"returnEmpty" , false} }) as Product;

Then in the factory:

public interface IGenericIocFactory
{
    T Get<T>(Dictionary<string,object> options) where T: class;
}

public class GenericIocFactory : IGenericIocFactory
{
    private readonly IComponentContext  _icoContext;
    private object _options;

    public GenericIocFactory(IComponentContext icoContext,bool isInjected = true)
    {
         _icoContext= icoContext;
    }

    public T Get<T>(Dictionary<string,object> options) where T: class
    {
        var _parameters = new List<Parameter>();
        foreach (var parameter in options)
        {
            _parameters.Add(new NamedParameter(parameter.Key, parameter.Value));
        }
        return _icoContext.Resolve<T>(_parameters);
        //operate on new object

        // tried this as well
        //return _icoContext.Resolve<T>(
            //new NamedParameter("returnEmpty" , false)
            //new TypedParameter(typeof(bool),false)
        //);
    }
}

This resolves a product but not with the constructor i expect.

Target constructor

public Product(bool returnEmpty)

Resolving constructor

public Product(IList<string> productCodes, string fields = "", string orderBy = "ProductCode")

There is a total of 23 constructors and the one resolving is not the biggest(so i don't think its being greedy)

ie

public Product(string strFields, string strFrom, string strFilter, string strOrderBy, string whseCode,
        bool addExistsInWharehouse, string additionalAfterorderBy, bool forceUniqueRecords = false)

Nor is it the first or last in or of definition.

Im stumped can anyone see what im doing wrong.

Unfortunately Autofac doesn't provide this mechanism.

You could have implemented IConstructorSelector which select a constructor when more than one constructor is available and set it to the registration by using the UsingSelector method but unfortunately there is no way to access the available parameters of the current resolve operation.

Another solution would be to implement IInstanceActivator which is responsible of creating the instance based on the type and parameters. to use a custom IInstanceActivator you also need to implement IRegistrationBuilder which is quite difficult. To guarantee good performance, I would also recommend the use of ConstructorParameterBinding which will create an optimized factory using dynamic compiled expression.

If you can't change your constructor, the only solution I can see is to implement your own factory. Because your object don't have any dependencies, you can create them without using Autofac .

public class GenericIocFactory : IGenericIocFactory
{
    public GenericIocFactory(ILifetimeScope scope)
    {
        this._scope = scope; 
    }

    private readonly ILifetimeScope _scope; 

    public T Get<T>(params object[] args) where T: class
    {           
        ConstructorInfo ci = this.GetConstructorInfo(args);
        if (ci == null) 
        {
            throw ...
        }

        var binder = new ConstructorParameterBinding(ci, args, this._scope);

        T value = binder.Instanciate() as T; 

        if (value == null) 
        {
            throw ...
        }
        if(value is IDisposable)
        {
            this._scope.Disposer.AddInstanceForDisposal(value);
        }
        return value; 
    }


    protected virtual ConstructorInfo GetConstructorInfo<T>(params object[] args)
    {
      // TODO 
    }
}

So having read up again on the doco. I needed to bind the constructor at the start. But this wont fix my problem so I made another container ever single time an instance is requested and construct it based on the params. Its a little bit incorrect but this is a real world solution to anyone who is transitioning to autofac for a very large existing solution.

Hope this helps someone.

public interface IGenericIocFactory
{
    T Get<T>(params object[] constructorParams) where T: class;
}

public interface ICustomAutoFacContainer
{
    IContainer BindAndReturnCustom<T>(IComponentContext context, Type[] paramsList);
}

public class CustomAutoFacContainer : ICustomAutoFacContainer
{
    public IContainer BindAndReturnCustom<T>(IComponentContext context, Type[] paramsList)
    {
        if (context.IsRegistered<T>())
        {
            // Get the current DI binding type target
            var targetType = context
                .ComponentRegistry
                .Registrations
                .First(r => ((TypedService) r.Services.First()).ServiceType == typeof(T))
                .Target
                .Activator
                .LimitType;

            // todo: exception handling and what not .targetType

            var builder = new ContainerBuilder();

            builder
               .RegisterType(targetType)
               .As<T>()
               .UsingConstructor(paramsList)
               .PropertiesAutowired();

            return builder.Build();
        }
        return null;
    }
}

public class GenericIocFactory : IGenericIocFactory
{
    private ICustomAutoFacContainer _iCustomContainer;
    private readonly IComponentContext _icoContext;
    public GenericIocFactory(ICustomAutoFacContainer iCustomContainer, IComponentContext icoContext)
    {
         _iCustomContainer = iCustomContainer;
        _icoContext = icoContext;
    }

    public T Get<T>(params object[] constructorParams) where T: class
    {
        //TODO handle reflection generation? ?? ?not needed?? ??

        var parameters = constructorParams
            .Select((t, index) => new PositionalParameter(index, t))
            .Cast<Parameter>()
            .ToList();

        var parameterTypes = constructorParams
            .Select((t, index) => t.GetType())
            .ToArray();

        return _iCustomContainer
            .BindAndReturnCustom<T>(_icoContext,parameterTypes)
            .Resolve<T>(parameters);
    }
}

Setup and usage looks something like this:

var builder = new ContainerBuilder();

// Usually you're only interested in exposing the type
// via its interface:
builder
    .RegisterType<GenericIocFactory>()
    .As<IGenericIocFactory>();

builder
    .RegisterType<CustomAutoFacContainer>()
    .As<ICustomAutoFacContainer>();

builder
    .RegisterType<Product>()
    .As<IProduct>()
    .PropertiesAutowired();

var container = builder.Build();

var factory = container.Resolve<IGenericIocFactory>();

_product = factory.Get<IProduct>(false) as Product;
_product = factory.Get<IProduct>("","") as Product;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM