如何在ASP.NET Core 2 MVC中使用依赖注入设置单元可测试模型验证? [英] How do you setup unit testable model validation with dependency injection in ASP.NET Core 2 MVC?

查看:110
本文介绍了如何在ASP.NET Core 2 MVC中使用依赖注入设置单元可测试模型验证?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个ASP.NET Core 2 MVC应用程序.很多时候,我需要利用依赖关系来验证用户输入.我希望我的验证方法可以进行单元测试,并且希望能够将模拟的依赖项注入其中.这是我以前在MVC5中所做的非常成功的事情,但是无法解决ASP.NET Core 2的等效问题.

I am building an ASP.NET Core 2 MVC application. A lot of the time I need to make use of dependencies to validate user input. I want my validation methods to be unit testable, and I want to be able to inject mocked dependencies into them. This is something I have previously done in MVC5 to great success but cannot work out the ASP.NET Core 2 equivalent.

这就是我在MVC5中的做法:

This is how I would do it in MVC5:

// the view model to be validated
public class MyViewModel {
  public string Username { get; set; }
}

// the model validator that will have dependencies injected into it
public class MyViewModelValidator : ModelValidator
{
  private IUserService users;
  private MyViewModel model;

  public MyViewModelValidator(ModelMetadata metadata, ControllerContext controllerContext, IUserService users)
    : base(metadata, controllerContext)
    {
      this.users = users;
      this.model = base.Metadata.Model as MyViewModel;
    }

  public override IEnumerable<ModelValidationResult> Validate(object container)
  {
      List<ModelValidationResult> errors = new List<ModelValidationResult>();

      if (this.users.CheckExists(this.model.Username))
      {
          errors.Add(new ModelValidationResult() { MemberName = nameof(MyViewModel.Username), Message = "Username is not available" });
      }

      return errors;
  }
}

// this class works out which validator is required for a given model and 
// injects the appropriate dependencies that is resolves using unity in my
// in my case
public class ViewModelValidatorProvider : ModelValidatorProvider
{
  private IUnityContainer container;

  public ViewModelValidatorProvider() => this.container = DependencyResolver.Current.GetService<IUnityContainer>();

  public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
  {
    if (metadata.ModelType == typeof(MyViewModel))
      yield return new MyViewModelValidator(metadata, context, this.container.Resolve<IUserService>());
  }
}

// the provider is hooked up in the app start in Global.asax.cs file
public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    ModelValidatorProviders.Providers.Add(new ViewModelValidatorProvider());
  }
}

现在,我可以创建具有模拟依赖项的验证器实例,然后我就走了!遗憾的是,ASP.NET Core 2没有ModelValidator类,到目前为止,我发现的所有内容似乎都希望通过控制器注入依赖项或使用IValidatableObject s Validate()函数来解决它们.

Now I can just create an instance of the validator with mocked dependencies and away I go! Sadly ASP.NET Core 2 doesn't have the ModelValidator class and everything I have found so far seems to want to inject dependencies via the controller or to resolve them with in an IValidatableObjects Validate() function.

是否可以在MVC Core中执行此操作?

Is it possible to do this in MVC Core?

推荐答案

因此,在@Nkosi发表对问题的评论后,我沿着正确的道路开始(我认为),最终实现了基于类型过滤器.

So following the post @Nkosi left in a comment on the question I started down the right path (I think) and ended up implementing a validation system based on type filters.

首先,我有一个基本的验证器模型,需要在类型过滤器中实现:

To start I have a base validator model that we need to implement in our type filters:

    public abstract class BaseViewModelValidator<TModel> : IAsyncActionFilter
    where TModel : class
{
    public async virtual Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // get the model to validate
        if (context.ActionArguments["model"] is TModel model)
            await this.ValidateAsync(model, context.ModelState);
        else
            throw new Exception($"View model of type `{context.ActionArguments["model"].GetType()}` found, type of `{typeof(TModel)}` is required.");

        await next();
    }

    public abstract Task ValidateAsync(TModel model, ModelStateDictionary state);        
}

然后,因为将其用作命名属性而不是[TypeFilter(typeof(SomeActionFilter))]更好,所以我创建了一个TypeFilterAttribute,它包装了我的基本验证器的实现,如下所示:

Then, because it is much nicer to use it as a named attribute rather than [TypeFilter(typeof(SomeActionFilter))], I create a TypeFilterAttribute that wraps the implementation of my base validator like this:

public class DemoViewModelValidatorAttribute : TypeFilterAttribute
{
    public DemoViewModelValidatorAttribute() 
        : base(typeof(DemoViewModelValidator))
    {
    }

    internal class DemoViewModelValidator : BaseViewModelValidator<DemoViewModel>
    {
        private readonly ISomeService service;

        // dependencies are injected here (assuming you've registered them in the start up)
        public DemoViewModelValidator(ISomeService service) => this.service = service;

        public async override Task ValidateAsync(DemoViewModel model, ModelStateDictionary state)
        {
            if (await this.service.CheckSomethingAsync(model))
                state.AddModelError(nameof(model.SomeProperty), $"Whoops!!!");
        }
    }
}

然后您就可以对您的DemoViewModelValidator进行单元测试,以使您心满意足!希望有人觉得这有用!

You can then unit test your DemoViewModelValidator to your hearts content! Hopefully someone finds this useful!

这篇关于如何在ASP.NET Core 2 MVC中使用依赖注入设置单元可测试模型验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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