c# - 如何使用 Simple Injector 从 ValidationContext 获取服务?

标签 c# dependency-injection simple-injector

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

在我的一些 View 模型中,我实现了 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 在应用程序中注册的。

在我的 Controller 中我使用这样的验证:

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

    return View(model);
}

那么,有没有办法在ValidationContext中从Asp.Net Core IServiceProvider获取IMyService?

最佳答案

虽然将验证逻辑放在模型对象本身内并没有本质上的错误,但是当验证逻辑需要服务才能工作时,问题就开始出现了。在这种情况下,您最终将应用 Service Locator anti-pattern (通过调用 validationContext.GetService )。

相反,当涉及到需要运行服务的更复杂的验证时,将数据和行为分开会好得多。这允许您将验证逻辑移至单独的类。这个类可以申请Constructor Injection因此,不必使用任何反模式。

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

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

在此抽象之上,您可以定义任意多的实现,例如一个(或多个)用于验证 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> 建模接口(interface)根据您的应用需求。

唯一剩下的就是确保 MVC 在验证您的 View 模型时使用这些验证器。这可以通过自定义 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);
}

唯一剩下要做的就是添加 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);

等等!好了——一个完全松散耦合的验证结构,可以根据您的应用程序的需要定义,同时使用构造函数注入(inject)等最佳实践,并允许您的验证代码得到全面测试,而无需诉诸反模式,并且无需与 MVC 基础架构紧密耦合。

关于c# - 如何使用 Simple Injector 从 ValidationContext 获取服务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55835059/

相关文章:

c# - ASP.NET MVC5 Controller 操作只能使用 RedirectToAction 访问

java - 无法使用 Guice 和 Vertx 将同一实例注入(inject)多个 Verticles

design-patterns - 将依赖传递给一个本身不使用它但将其传递给一些内部使用的类的类是否正确

c# - 在消息处理程序中创建 DbContext

c# - 使用 SimpleInjector 动态选择具有名称的具体对象

.net - asp.net core 2.1 中的共享资源本地化和简单注入(inject)器

c# - 为什么在一种特定情况下,Bing map 上只显示我的 Pin 图的子集?

Visual Studio 中的 C# 结构突出显示

c# - 动态生成8位Unicode转字符

java - 如何在 Dagger 2.16 中为 worker 类实现 Dagger? ('android.arch.work:work-runtime')