问题
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