代码之家  ›  专栏  ›  技术社区  ›  Ben

如何在ASP.NET Core 2 MVC中设置带有依赖项注入的单元可测试模型验证?

  •  0
  • Ben  · 技术社区  · 8 年前

    我正在构建一个ASP.NET核心2 MVC应用程序。很多时候我需要利用依赖项来验证用户输入。我希望我的验证方法是可单元测试的,并且我希望能够向它们注入模拟的依赖项。这是我以前在MVC5中所做的一件非常成功的事情,但是我无法计算出与ASP.NET核心2等效的东西。

    这就是我在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核心2没有 ModelValidator 类和到目前为止我发现的所有内容似乎都希望通过控制器注入依赖项或在 IValidatableObject S Validate() 功能。

    在MVC核心中可以这样做吗?

    1 回复  |  直到 8 年前
        1
  •  1
  •   Ben    8 年前

    因此,在帖子@nkosi左边对这个问题发表评论之后,我开始走上正确的道路(我认为),最终实现了一个基于 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 这样包装了我的基本验证器的实现:

    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 心满意足!希望有人会发现这很有用!