Asp.net Core AutoFac register generic using factory

99封情书 提交于 2020-01-15 04:58:05

问题


I'm using Asp.net Core with AutoFac and following the accepted answer here:

Validation: How to inject A Model State wrapper with Ninject?

This uses ninject. I don't understand how to do the equivalent of this ninject part in autoFac, specifically the kernel.Get:

Func<Type, IValidator> validatorFactory = type =>
{
    var valType = typeof(Validator<>).MakeGenericType(type);
    return (IValidator)kernel.Get(valType);
};

kernel.Bind<IValidationProvider>()
    .ToConstant(new ValidationProvider(validatorFactory));

Startup.cs

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    var containerBuilder = new ContainerBuilder();

    IValidator ValidatorFactory(Type type)
    {
        var valType = typeof(Validator<>).MakeGenericType(type);

        //This line is the problem
        // return (IValidator)container.Resolve(valType);
    }


    containerBuilder.Register(x => new ValidationProvider(ValidatorFactory)).As<IValidationProvider>().SingleInstance();

    containerBuilder.RegisterType<UploadValidator>().As<Validator<AudioModel>>();

    containerBuilder.Populate(services);

    var container = containerBuilder.Build();

    return container.Resolve<IServiceProvider>();
}

The problem is that the container is only available after using .Build() so I don't see how I can do it. Do I need to register this service after calling .Build() and then call .Build() again or is .Resolve() the wrong thing to use here.

Validation classes:

internal sealed class ValidationProvider : IValidationProvider
{
    private readonly Func<Type, IValidator> _validatorFactory;

    public ValidationProvider(Func<Type, IValidator> validatorFactory)
    {
        _validatorFactory = validatorFactory;
    }

    public void Validate(object entity)
    {
        var results = _validatorFactory(entity.GetType()).Validate(entity).ToArray();

        if (results.Length > 0)
            throw new ValidationException(results);
    }

    public void ValidateAll(IEnumerable entities)
    {
        var results = (
            from entity in entities.Cast<object>()
            let validator = _validatorFactory(entity.GetType())
            from result in validator.Validate(entity)
            select result).ToArray();

        if (results.Length > 0)
            throw new ValidationException(results);
    }
}

public abstract class Validator<T> : IValidator
{
    IEnumerable<ValidationResult> IValidator.Validate(object entity)
    {
        if (entity == null)
            throw new ArgumentNullException(nameof(entity));

        return Validate((T)entity);
    }

    protected abstract IEnumerable<ValidationResult> Validate(T entity);
}

public class UploadValidator : Validator<AudioModel>
{
    protected override IEnumerable<ValidationResult> Validate(AudioModel model)
    {
        if (string.IsNullOrWhiteSpace(model.Name))
        {
            yield return new ValidationResult("Name", "Name is required");
        }
    }
}

回答1:


Autofac has a great feature that enables us to register factories to create instances based on a parameter(s). In your example, we could register a Func<Type, IValidator> with Autofac, and have that automagically injected into our ValidationProvider.

var builder = new ContainerBuilder();
builder
    //register our factory function
    .Register<Func<Type, IValidator>>(
        x =>
        {
            //get a reference to the scoped container
            //e.g. if this is a web app, each HTTP request will
            //spawn a child container used for the lifetime of that request
            var context = x.Resolve<IComponentContext>();
            return type => 
            {
                //create the validator from our scoped container
                var valType = typeof(Validator<>).MakeGenericType(type);
                return (IValidator) context.Resolve(valType);
            }
        }
)};

public class ValidationProvider
{
    readonly Func<Type, IValidator> _factory;        
    //Autofac will see this class requires our previously registered
    //function and inject this for us
    public ValidationProvider(Func<Type, IValidator> factory)
    {
        _factory = factory;
    }
}

As an alternative, is it possible for you to constrain the IValidator with a generic argument? Perhaps it is not feasible to refactor the code, but if it is, it may be better practice to give our services the exact dependencies they require, rather than a factory which may hide their intent.

public interface IValidator<T>
{
    void Validate(T instance);
}

public class SomeClassRequiringAudioModelValidator
{
    readonly IValidator<AudioModel> _validator;
    public SomeClassRequiringAudioModelValidator(IValidator<AudioModel> validator)
    {
        _validator = validator;
    }
}


来源:https://stackoverflow.com/questions/45172019/asp-net-core-autofac-register-generic-using-factory

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!