如何使用Simple Injector从ValidationContext获得服务?

Tim*_*thy 2 c# dependency-injection simple-injector

在我的Asp.Net MVC Core项目中,我使用SimpleInjector作为IoC。我之所以使用它,是因为有可能注册开放的泛型。

在某些视图模型中,我实现了IValidatableObject

public class MyViewmodel: IValidatableObject
{
    public string SomeProperty { get;set; }

    //...

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        //...
        IMyService service = validationContext.GetService(typeof(IMyService)) as IMyService;
    }
}
Run Code Online (Sandbox Code Playgroud)

方法GetService返回,null因为IMyService已由SimpleInjector在应用程序中注册。

在我的控制器中,我使用了这样的验证:

[HttpPost]
public async Task<IActionResult> Edit(MyViewmodel model)
{
    if (ModelState.IsValid)
    {
        //...
    }

    return View(model);
}
Run Code Online (Sandbox Code Playgroud)

那么,有没有办法从IServiceProviderValidationContext的Asp.Net Core获取IMyService ?

Ste*_*ven 6

尽管将验证逻辑放置在模型对象本身内部并没有错,但是当验证逻辑需要服务才能工作时,问题就开始出现。在这种情况下,您最终将应用Service Locator反模式(通过调用validationContext.GetService)。

相反,当涉及需要服务才能运行的更复杂的验证时,最好将数据和行为分开。这使您可以将验证逻辑移至单独的类。该类可以应用构造函数注入,因此不必使用任何反模式。

为此,请从您自己的可以验证实例的抽象开始。例如:

public interface IValidator<T>
{
    IEnumerable<string> Validate(T instance);
}
Run Code Online (Sandbox Code Playgroud)

在此抽象之上,您可以定义尽可能多的实现,例如一个(或多个)用于验证的实现MyViewmodel

public class MyViewmodelValidator : IValidator<MyViewmodel>
{
    private readonly IMyService service;
    public MyViewmodelValidator(IMyService service) => this.service = service;

    public IEnumerable<string> Validate(MyViewmodel instance)
    {
        yield return "I'm not valid.";
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是使事情运转起来所需的所有应用程序代码。当然,您应该IValidator<T>根据您的应用程序需求对接口进行建模。

剩下的就是确保MVC在验证视图模型时使用这些验证器。这可以通过自定义IModelValidatorProvider实现来完成:

class SimpleInjectorModelValidatorProvider : IModelValidatorProvider
{
    private readonly Container container;

    public SimpleInjectorModelValidatorProvider(Container container) =>
        this.container = container;

    public void CreateValidators(ModelValidatorProviderContext ctx)
    {
        var validatorType =
            typeof(ModelValidator<>).MakeGenericType(ctx.ModelMetadata.ModelType);
        var validator = (IModelValidator)this.container.GetInstance(validatorType);
        ctx.Results.Add(new ValidatorItem { Validator = validator });
    }
}

// Adapter that translates calls from IModelValidator into the IValidator<T>
// application abstraction.
class ModelValidator<TModel> : IModelValidator
{
    private readonly IEnumerable<IValidator<TModel>> validators;

    public ModelValidator(IEnumerable<IValidator<TModel>> validators) =>
        this.validators = validators;

    public IEnumerable<ModelValidationResult> Validate(ModelValidationContext ctx) =>
        this.Validate((TModel)ctx.Model);

    private IEnumerable<ModelValidationResult> Validate(TModel model) =>
        from validator in this.validators
        from errorMessage in validator.Validate(model)
        select new ModelValidationResult(string.Empty, errorMessage);
}
Run Code Online (Sandbox Code Playgroud)

剩下要做的唯一一件事就是添加SimpleInjectorModelValidatorProvider到MVC管道并进行所需的注册:

services.AddMvc(options =>
    {
        options.ModelValidatorProviders.Add(
            new SimpleInjectorModelValidatorProvider(container));
    });

// Register ModelValidator<TModel> adapter class
container.Register(typeof(ModelValidator<>), typeof(ModelValidator<>),
    Lifestyle.Singleton);

// Auto-register all validator implementations
container.Collection.Register(
    typeof(IValidator<>), typeof(MyViewmodelValidator).Assembly);
Run Code Online (Sandbox Code Playgroud)

瞧!您可以使用它—一个完全松散耦合的验证结构,可以根据应用程序的需求进行定义,同时使用诸如构造器注入之类的最佳实践,并且可以对验证代码进行全面测试,而不必求助于反模式。与MVC基础架构紧密结合。


Nic*_*cki 6

IServiceProvider@Steven 提供了一个令人惊奇的答案,但是对于那些想知道如何使用而不是其他库中的 a 来使其适应内置依赖注入机制的人来说Container,并且被困在

services.AddMvc(options =>
    {
        options.ModelValidatorProviders.Add(
            new SimpleInjectorModelValidatorProvider(/* TODO how do I get the IServiceProvider */));
    });
Run Code Online (Sandbox Code Playgroud)

秘密武器是创建另一个类来配置MvcOptions并注入IServiceProvider它:

public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
    private readonly IServiceProvider provider;

    public ConfigureMvcOptions(IServiceProvider provider)
    {
        this.provider = provider;
    }

    public void Configure(MvcOptions options)
    {
        options.ModelValidatorProviders.Add(new SimpleInjectorModelValidatorProvider(this.provider));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用Startup.cs通常的方式注册它:

services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
Run Code Online (Sandbox Code Playgroud)