根据外部因素验证对象(即数据存储唯一性)

Rob*_*nik 10 validation asp.net-mvc xval data-annotations

描述

我的解决方案有这些项目:

  • DAL =修改后的实体框架
  • DTO =能够验证自己的数据传输对象
  • BL =业务层服务
  • WEB =演示Asp.net MVC应用程序

DAL,BL和WEB都参考了很棒的DTO.
该过程通常以这种方式执行:

  1. 向WEB发出Web请求
  2. WEB获得DTO发布
    • DTO通过自定义ActionFilter自动验证
    • 验证错误是自动收集的
  3. (验证是可以的)WEB调用BL提供DTO
  4. BL通过使用DTO调用DAL(可以通过它们或只是使用它们)

那么DTO验证问题......

我的DTO能够根据自己的状态(属性值)验证自己.但是现在我遇到了问题,但事实并非如此.我需要他们使用BL(以及因此DAL)进行验证.

我的现实生活中的例子:用户注册和WEB获得验证的用户DTO.有问题的部分是username验证.应根据数据存储检查其唯一性.
我该怎么做?

还有其他信息表明所有DTO都实现了IoC和TDD 的接口(即UserDTO实现IUser).两者都是DTO项目的一部分.

不可能尝试

  1. 我无法在DTO中引用BL,因为我将获得循环引用.
    Compilation error
  2. 我无法创建一个额外的DTO.Val项目,它将引用部分DTO类并在那里实现它们的验证(它们引用BL + DTO).
    Partial classes can't span assemblies.

可能的尝试

  1. 创建一个特殊的ActionFilter,可以根据外部条件验证对象.这个将在WEB项目中创建,因此可以看到将在此处使用的DTO和BL.
  2. 将DTO放在BL中并将DTO接口保持为其他项目引用的实际DTO,并重构所有代码以使用接口而不是具体类.
  3. 不处理外部依赖验证,并让外部依赖项抛出异常 - 可能此问题的最糟糕的解决方案

你会建议什么?

Mat*_*caj 6

我会建议一个实验,我在过去一周左右只进行了试验.

基于这种灵感,我创建的DTO与该DataAnnotations方法的验证方式略有不同.样本DTO:

public class Contact : DomainBase, IModelObject
{
    public int ID { get; set; }
    public string Name { get; set; }
    public LazyList<ContactDetail> Details { get; set; }
    public DateTime Updated { get; set; }


    protected override void ConfigureRules()
    {
        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "name" },
            Description = "A Name is required but must not exceed 300 characters in length and some special characters are not allowed",
            validator = () => this.Name.IsRequired300LenNoSpecial()
        });

        base.AddRule(new ValidationRule()
        {
            Properties = new string[] { "updated" },
            Description = "required",
            validator = () => this.Updated.IsRequired()
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能看起来比工作更好DataAnnotations,这是因为它是,但它并不大.我认为它在课堂上更具代表性(我现在有一些非常难看的DTO课程,带有DataAnnotations属性 - 你甚至不能再看到这些属性).在这个应用程序中匿名代表的力量几乎是值得预订的(所以我发现).

基类:

public partial class DomainBase : IDataErrorInfo
{
    private IList<ValidationRule> _rules = new List<ValidationRule>();

    public DomainBase()
    {
        // populate the _rules collection
        this.ConfigureRules();
    }

    protected virtual void ConfigureRules()
    {
        // no rules if not overridden
    }

    protected void AddRule(ValidationRule rule)
    {
        this._rules.Add(rule);
    }





    #region IDataErrorInfo Members

    public string Error
    {
        get { return String.Empty; }    // Validation should call the indexer so return "" here
    }                                   // ..we dont need to support this property.

    public string this[string columnName]
    {
        get
        {
            // get all the rules that apply to the property being validated
            var rulesThatApply = this._rules
                .Where(r => r.Properties.Contains(columnName));

            // get a list of error messages from the rules
            StringBuilder errorMessages = new StringBuilder();
            foreach (ValidationRule rule in rulesThatApply)
                if (!rule.validator.Invoke())   // if validator returns false then the rule is broken
                    if (errorMessages.ToString() == String.Empty)
                        errorMessages.Append(rule.Description);
                    else
                        errorMessages.AppendFormat("\r\n{0}", rule.Description);

            return errorMessages.ToString();
        }
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

ValidationRule 和我的验证功能:

public class ValidationRule
{
    public string[] Properties { get; set; }
    public string Description { get; set; }
    public Func<bool> validator { get; set; }
}


/// <summary>
/// These extention methods return true if the validation condition is met.
/// </summary>
public static class ValidationFunctions
{
    #region IsRequired

    public static bool IsRequired(this String str)
    {
        return !str.IsNullOrTrimEmpty();
    }

    public static bool IsRequired(this int num)
    {
        return num != 0;
    }

    public static bool IsRequired(this long num)
    {
        return num != 0;
    }

    public static bool IsRequired(this double num)
    {
        return num != 0;
    }

    public static bool IsRequired(this Decimal num)
    {
        return num != 0;
    }

    public static bool IsRequired(this DateTime date)
    {
        return date != DateTime.MinValue;
    }

    #endregion


    #region String Lengths

    public static bool IsLengthLessThanOrEqual(this String str, int length)
    {
        return str.Length <= length;
    }

    public static bool IsRequiredWithLengthLessThanOrEqual(this String str, int length)
    {
        return !str.IsNullOrTrimEmpty() && (str.Length <= length);
    }

    public static bool IsRequired300LenNoSpecial(this String str)
    {
        return !str.IsNullOrTrimEmpty() &&
            str.RegexMatch(@"^[- \r\n\\\.!:*,@$%&""?\(\)\w']{1,300}$",
                RegexOptions.Multiline) == str;
    }

    #endregion

}
Run Code Online (Sandbox Code Playgroud)

如果我的代码看起来很乱,那是因为我过去几天只是在研究这种验证方法.我需要这个想法来满足一些要求:

  • 我需要支持IDataErrorInfo接口,以便我的MVC层自动验证
  • 我需要能够支持复杂的验证方案(我猜你的问题的全部要点):我希望能够验证同一对象上的多个属性(即StartDate和FinishDate); 来自不同/多个/相关对象的属性,就像我在对象图中一样; 甚至还有其他我还没有想到的事情.
  • 我需要支持应用于多个属性的错误的想法
  • 作为我的TDD和DDD之旅的一部分,我希望我的域对象能够描述比我的服务层方法更多的"域",因此将这些复杂的条件放在模型对象(而不是DTO)中似乎实现了这一点.

我想这种方法可以让我得到我想要的东西,也许你也是.

我想象一下,如果你和我一起加入我们的行列,我们就会"独自一人",但这可能是值得的.我正在阅读MVC 2中新验证功能,但如果没有自定义修改,它仍然不符合上述愿望清单.

希望这可以帮助.


Rob*_*nik 1

结果解决方案

我最终使用了控制器操作过滤器,它能够根据无法从对象本身获取的外部因素来验证对象。

我创建了一个过滤器,它采用要检查的操作参数的名称和将验证该特定参数的验证器类型。当然,这个验证器必须实现某些接口才能使其可重用。

[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)
Run Code Online (Sandbox Code Playgroud)

验证器需要实现这个简单的接口

public interface IExternalValidator<T>
{
    bool IsValid(T instance);
}
Run Code Online (Sandbox Code Playgroud)

对于看似复杂的问题,这是一个简单而有效的解决方案。