You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core 2 MVC中实现可单元测试的依赖注入式模型验证

在ASP.NET Core 2 MVC中实现带可注入依赖的可测试模型验证器

问题描述

我正在开发ASP.NET Core 2 MVC应用,需要借助依赖项验证用户输入,而且希望验证逻辑能做单元测试,还能注入模拟依赖。之前在MVC5里我用自定义验证器和提供器的方式实现了,但到Core里找不到对应的ModelValidator类,现在搜到的方案要么是在控制器里注入依赖,要么是在IValidatableObject的Validate方法里手动解析依赖,都不是我想要的。

先贴一下我MVC5里的实现,方便大家理解需求:

// 待验证的视图模型
public class MyViewModel 
{ 
    public string Username { get; set; } 
} 

// 带依赖注入的模型验证器
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; 
    } 
} 

// 匹配模型与验证器的提供器,用Unity注入依赖
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>()); 
    } 
} 

// 启动时注册提供器
public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
        ModelValidatorProviders.Providers.Add(new ViewModelValidatorProvider()); 
    } 
} 

想问问在ASP.NET Core 2里能不能实现类似的功能?


解决方案

当然可以!ASP.NET Core MVC里用IModelValidator和IModelValidatorProvider这两个接口替代了MVC5里的ModelValidator和ModelValidatorProvider,而且原生支持依赖注入,完全不需要第三方容器,就能实现可测试的验证逻辑。

下面一步步来实现:

1. 编写自定义IModelValidator

这个类负责具体的验证逻辑,直接通过构造函数注入依赖,非常直观:

public class MyViewModelValidator : IModelValidator
{
    private readonly IUserService _userService;

    // 构造函数注入业务服务
    public MyViewModelValidator(IUserService userService)
    {
        _userService = userService;
    }

    public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
    {
        var model = context.Model as MyViewModel;
        if (model == null)
        {
            yield break;
        }

        // 调用依赖服务验证用户名是否存在
        if (_userService.CheckExists(model.Username))
        {
            yield return new ModelValidationResult
            {
                MemberName = nameof(MyViewModel.Username),
                Message = "Username is not available"
            };
        }
    }
}

2. 实现IModelValidatorProvider

这个类的作用是给指定模型分配对应的验证器,而且可以利用Core的DI容器来解析验证器的依赖:

public class ViewModelValidatorProvider : IModelValidatorProvider
{
    private readonly IServiceProvider _serviceProvider;

    // 注入IServiceProvider来获取所需的验证器实例
    public ViewModelValidatorProvider(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void CreateValidators(ModelValidatorProviderContext context)
    {
        // 只给MyViewModel添加自定义验证器
        if (context.ModelMetadata.ModelType == typeof(MyViewModel))
        {
            var validator = _serviceProvider.GetRequiredService<MyViewModelValidator>();
            context.Validators.Add(validator);
        }
    }
}

3. 在Startup里注册服务

打开Startup.cs,在ConfigureServices方法里把我们的验证器、提供器还有业务服务都注册到DI容器:

public void ConfigureServices(IServiceCollection services)
{
    // 注册你的业务服务,这里用Scoped生命周期,根据实际情况调整
    services.AddScoped<IUserService, UserService>();

    // 注册自定义验证器
    services.AddScoped<MyViewModelValidator>();

    // 注册自定义验证器提供器
    services.AddTransient<IModelValidatorProvider, ViewModelValidatorProvider>();

    // 注册MVC服务
    services.AddMvc();
}

4. 单元测试验证器

现在你可以轻松用模拟依赖来测试验证逻辑了,比如用Moq框架:

[TestClass]
public class MyViewModelValidatorTests
{
    [TestMethod]
    public void Validate_WhenUsernameExists_ReturnsValidationError()
    {
        // 准备模拟服务
        var mockUserService = new Mock<IUserService>();
        mockUserService.Setup(s => s.CheckExists("takenUsername")).Returns(true);

        // 初始化验证器
        var validator = new MyViewModelValidator(mockUserService.Object);
        var testModel = new MyViewModel { Username = "takenUsername" };
        
        // 创建验证上下文
        var metadataProvider = new EmptyModelMetadataProvider();
        var modelMetadata = metadataProvider.GetMetadataForType(typeof(MyViewModel));
        var actionContext = new ActionContext();
        var validationContext = new ModelValidationContext(actionContext, modelMetadata, new Dictionary<object, object>(), testModel);

        // 执行验证
        var validationResults = validator.Validate(validationContext).ToList();

        // 断言结果
        Assert.AreEqual(1, validationResults.Count);
        Assert.AreEqual(nameof(MyViewModel.Username), validationResults[0].MemberName);
        Assert.AreEqual("Username is not available", validationResults[0].Message);
    }
}

方案优势

  • 完全基于ASP.NET Core原生DI,不需要第三方容器,减少额外依赖
  • 验证器的依赖注入符合构造函数注入的最佳实践,代码更清晰
  • 单元测试非常方便,直接实例化验证器并传入模拟依赖即可,不需要启动整个Web环境

内容的提问来源于stack exchange,提问作者Ben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:56:24