如何在ASP.NET Core 2 MVC中使用依赖注入设置单元可测试模型验证? [英] How do you setup unit testable model validation with dependency injection in 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 IValidatableObject
s 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屋!