【问题标题】:How to inject dependencies used for validation in .NET MVC3?如何在 .NET MVC3 中注入用于验证的依赖项?
【发布时间】:2012-01-11 11:52:15
【问题描述】:

我们有很多验证方法需要访问存储库/数据库才能完成工作。到目前为止,我们一直在使用服务定位器模式(尽管很少)在自定义 ValidationAttributes 中完成此操作:

public override bool IsValid(object value)
{
    // use custom service locator in our app's infrastructure
    var repos = DependencyInjector.Current.GetService<IXyzRepository>();
    ...
}

我知道这是 :( 作为一种反模式,我们希望使用更正确的方法。我们使用统一,我阅读了this post that says to use a build-up method。但是the link in the accepted answer 说文档已经过时(已退休内容)。

该解决方案不需要使用验证属性,我想它可以使用 IValidatableObject,但问题仍然存在:如何将依赖项注入模型。我们是否需要自定义模型绑定器来执行此操作?

另一种解决方案是在控制器中执行验证,其中依赖注入很容易。对我来说,这感觉很混乱。我希望模型在到达动作方法时得到验证。

我们有时也会使用 [RemoteAttribute] 在客户端上执行其中一些验证。目前,这些方法使用静态 Validator.TryValidateObject 方法构造视图模型并将验证委托给模型。

在不使用 SL 反模式的情况下,您如何完成需要注入依赖项才能工作的验证?

【问题讨论】:

  • 服务定位器不是反模式恕我直言。这是一种被滥用的模式,是解决无法进行构造函数注入问题的绝佳解决方案。

标签: asp.net-mvc-3 validation dependency-injection


【解决方案1】:

您是如何完成需要注入的验证的 依赖来完成它的工作,而不使用 SL 反模式?

我使用FluentValidation.NET 在我的应用程序中执行验证。它允许我 inject dependencies 进入我的验证器。它有一个非常好的integration with ASP.NET MVC。它还支持标准规则的自动客户端验证,就像使用 jquery unobtrusive validate 的数据注释一样:

  • NotNull/NotEmpty
  • 匹配(正则表达式)
  • InclusiveBetween(范围)
  • 信用卡
  • 电子邮件
  • EqualTo(跨属性相等比较)
  • 长度

我从未使用数据注释来执行验证。当您需要处理一些更复杂 验证场景(需要验证依赖属性甚至使用某些服务)时,它们绝对没用。我在上一句中用斜体表示复杂,因为我不认为需要validating that one of the 2 properties 是一个非常复杂的验证方案,但是,只需检查您必须编写的基础设施废话的数量,以便使用数据注释来实现它.看这段代码,你不再知道你在验证什么。

【讨论】:

  • 查看文档,我找不到有关如何使用此库在客户端上进行验证的任何信息。您是否使用它在客户端进行验证?如果是这样,你能用链接更新你的答案吗?
  • @olivehour,您可以像使用数据注释一样使用不显眼的 jquery 验证。当您使用 FV.NET 时,您注册了一个自定义模型元数据提供程序,HTML 帮助程序(例如 TextBoxFor)使用该模型元数据提供程序在输入字段上发出 HTML5 data-* 属性。它适用于标准规则。以下是客户端验证自动支持的标准规则列表:fluentvalidation.codeplex.com/…。我已更新我的答案,以包含指向显示与 MVC 集成的文档的链接。
  • 太棒了。再澄清一点,关于原始问题:当您需要在需要 DI 的客户端上执行验证时,您是否使用 RemoteAttribute?您是否能够将验证保存在一个地方,以便将相同的规则应用于服务器端和客户端验证?
  • @olivehour,是的,通过远程验证,您仍然可以在模型上使用 Remote 属性,然后通过 AJAX 调用控制器操作来执行验证。显然,这个控制器动作要做的就是查询服务层(已经注入到你的控制器中)来执行验证。 FV 的服务器端验证器 => 对注入其中的服务层方法的查询也会这样做。但是整个业务验证规则显然会在服务层方法内部。
【解决方案2】:

将您的验证注入您的模型。

当您的验证故事变得更加复杂时,验证属性可能会变得难以使用。呸!

我喜欢将实体框架与 Code First 结合使用。那时我可以完全控制我的模型。我也像@Darin Dimitrov 一样使用 FluentValidation,我真的很喜欢它的易用性和简单的语法。

这是你如何组合起来的。 我假设你已经组装了你的接口或合同。

这将是您模型的基本界面……

using System.ComponentModel;
using FluentValidation.Results;

public interface IAbstractBase : IDataErrorInfo
{
    bool IsValid { get; }
    ValidationResult SelfValidate();
}

您的业务层中的对应物看起来像这样......

using System;
using System.Linq;
using FluentValidation.Results;
using Contracts;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public abstract class AbstractBase : IAbstractBase
{
    #region IDataErrorInfo

    public abstract ValidationResult SelfValidate();

    [NotMapped]
    public bool IsValid
    {
        get
        {
            return SelfValidate().IsValid;
        }
    }

    [NotMapped]
    public string Error
    {
        get
        {
            var results = SelfValidate().Errors.Select(s => string.Format("● {0}{1}", s.ErrorMessage, Environment.NewLine)).ToArray();
            return string.Join("", results);
        }
    }

    [NotMapped]
    public IList<ValidationFailure> Errors
    {
        get
        {
            var results = SelfValidate().Errors;
            return results;
        }
    }

    [NotMapped]
    public string this[string columnName]
    {
        get
        {
            var validationResults = SelfValidate();
            if (validationResults == null) return string.Empty;
            var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0);
            return columnResults != null ? columnResults.ErrorMessage : string.Empty;
        }
    }
    #endregion
}

这是您的模型的基类。确保在模型中实现抽象方法。它应该是这样的。

public class MyModel : AbstractBase, IMyModel
{
    private AbstractValidator<IMyModelValidator> _myModelValidator;
    public MyModel():this(new MyModelValidator()){};
    public MyModel(AbstractValidator<IMyModelValidator> myModelValidator){
         _myModelValidator = myModelValidator;
    };

    public int MyModelId { get; set; }
    public string Name { get; set; }
    public DateTime CreatedDate { get; set; }

    public override ValidationResult SelfValidate()
    {
        return _myModelValidator.Validate(this);
    }
}

您的验证器类将如下所示。

 public class MyModelValidator : AbstractValidator<IMyModelValidator>
    {
        private IMyModelProvider _myModelProvider;
        public MyModelValidator(IMyModelProvider myModelProvider){ _myModelProvider = myModelProvider;};
        private void SetRules()
        {
            RuleFor(x => x.Name).NotEmpty().WithMessage("Please specify a project name.");
            RuleFor(x => x.Name.Length).LessThanOrEqualTo(100).WithMessage("The project name must be less than or equal to 100 characters.");
        }

        public override ValidationResult Validate(IMyModel instance)
        {
            SetRules();
            return base.Validate(instance);
        }
    }

使用控制器中的以下调用将验证结果从模型传递到控制器中的视图。

 TryValidateModel(your model here);

在你的控制器中调用它之后,调用你的 model.IsValid 属性。

确保您注册了所有内容,您应该一切顺利。我假设您可以填写缺失的部分。

大图是这样的:

【讨论】:

  • 谢谢。我知道 EF 4.1 CF 在 SaveChanges 期间使用数据注释验证器自动进行验证。我正在考虑发布另一个关于 FV.NET 是否也可以插入那里的问题。你知道 EF 4.1 是否有类似的验证提供程序挂钩吗?
  • @olivehour - 当然可以。请记住,在 EF 4.1 中,您拥有更多控制权。因此,在您的 EF 4.1 提供程序中 - 让我们说一个更新方法 - 您将在 SaveChanges 之前的 if 语句中调用 IsValid 属性。效果很好。如果您将强类型传递给您的控制器,您可以在它到达控制器之前使用绑定器构建它。从那里检查它是否有效,如果它通过您的 IsValid 条件,则将其传递给您的持久层。
  • 听起来您是在建议存储库 impl 依赖于 FV.NET。我猜想更多的是考虑一些可以作为约定添加到 DbModelBuilder 的东西。
  • 其实这个仓库对 FV.NET 没有依赖。请记住 MyModel 对象图 - 如果它是构建的 - 在它到达持久层时已经附加了验证器。
  • 我也会调查一下。看起来工作量很大,但会试一试。如果我有任何问题,您介意将此讨论移到稍后再聊吗?
猜你喜欢
  • 1970-01-01
  • 2020-12-20
  • 2020-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多