如何使用Simple Injector从ValidationContext获得服务? [英] How to get service from ValidationContext using Simple Injector?

查看:42
本文介绍了如何使用Simple Injector从ValidationContext获得服务?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的Asp.Net MVC Core项目中,我使用SimpleInjector作为IoC。

In my Asp.Net MVC Core project I use SimpleInjector as IoC. I use it because of possibility of registering open generics.

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

In some of my viewmodels I implement IValidatableObject.

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

    //...

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

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

And method GetService returns null because IMyService was registered in application by SimpleInjector.

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

In my controller I use such a validation:

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

    return View(model);
}

那么,有没有办法从Asp.Net Core <$ c获取IMyService $ c> IServiceProvider 是否在ValidationContext中?

So, is there way to get IMyService from Asp.Net Core IServiceProvider in ValidationContext?

推荐答案

尽管将验证逻辑放在内部并没有本质上的错误对于模型对象本身,当验证逻辑要求服务起作用时,问题开始出现。在这种情况下,您最终将使用服务定位器反模式(通过调用 validationContext.GetService )。

Although there is nothing inherently wrong with placing validation logic inside the model object itself, problems start to appear when that validation logic requires services to work. In that case you'll end up applying the Service Locator anti-pattern (by calling validationContext.GetService).

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

Instead, when it comes to more complex validations that require services to run, it's much better to separate data and behavior. This allows you to move the validation logic to a separate class. This class can apply Constructor Injection and, therefore, doesn't have to use any anti-patterns.

要实现这一点,请从您自己的可以验证实例的抽象。例如:

To achieve this, start off with your own abstraction that can validate instances. For instance:

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

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

On top of this abstraction, you can define as many implementations as you will, for instance one (or more) for validating 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.";
    }
}

这是获取内容所需的所有应用程序代码在运动中。当然,应该根据您的应用程序需求对 IValidator< T> 接口进行建模。

This is all the application code you need to get things in motion. Of course you should model the IValidator<T> interface according to your application needs.

MVC在验证视图模型时会使用这些验证器。这可以通过自定义的 IModelValidatorProvider 实现来实现:

Only thing left is ensure MVC uses these validators when validating your view models. This can be done with a custom IModelValidatorProvider implementation:

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);
}

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

The only thing left to do is add SimpleInjectorModelValidatorProvider to the MVC pipeline and make the required registrations:

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);

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

Et voila! There you have it—a completely loosely coupled validation structure that can be defined according to the needs of your application, while using best practices like Constructor Injection and allows your validation code to be fully tested without having to resort to anti-patterns, and without being tightly coupled with the MVC infrastructure.

这篇关于如何使用Simple Injector从ValidationContext获得服务?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆