10

描述

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

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

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

  1. 向 WEB 发出 Web 请求
  2. WEB 发布 DTO
    • DTO 通过自定义 ActionFilter 自动验证
    • 自动收集验证错误
  3. (验证OK)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. 不要处理外部依赖验证,让外部依赖抛出异常——这可能是这个问题最糟糕的解决方案

你有什么建议?

4

4 回答 4

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()
        });
    }
}

这可能看起来比DataAnnotations而且还好,这是因为它是,但它并不大。我认为它在课堂上更形象(我现在有一些非常丑陋的带有DataAnnotations属性的 DTO 类——你甚至再也看不到属性了)。而且这个应用程序中匿名代表的力量几乎是值得一书的(所以我正在发现)。

基类:

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
}

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

}

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

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

我认为这种方法会让我得到我想要的,也许你也一样。

我想如果你和我一起加入这件事,我们会很“靠自己”,但这可能是值得的。我正在阅读MVC 2 中的新验证功能,但如果没有自定义修改,它仍然不符合上述愿望清单。

希望这可以帮助。

于 2009-11-16T12:17:23.370 回答
2

S#arp 架构有一个 [DomainSignature] 方法标识符,与类级别验证器 [HasUniqueDomainSignature] 一起使用将完成这项工作。请参阅下面的示例代码:

[HasUniqueDomainSignature]
public class User : Entity
{
    public User()
    {
    }

    public User(string login, string email) : this()
    {
        Login = login;
        Email = email;
    }

    [DomainSignature]
    [NotNullNotEmpty]
    public virtual string Login { get; set; }

    [DomainSignature]
    public virtual string Email { get; set; }

}

仔细看看http://www.sharparchitecture.net/

于 2009-12-02T16:51:37.127 回答
1

我遇到了同样的问题,在几天又几天地尝试找到解决方法之后,我最终将我的 DTO、DAL 和 BL 合并到一个库中。我将表示层分开。不确定这是否适合您。对我来说,我认为我改变数据存储的机会非常小,因此不需要单独的层。

我还为我的所有 DTO 验证实现了 Microsoft 验证应用程序块。他们有一个“自我验证”方法,可以让您执行复杂的验证。

于 2009-12-08T19:30:31.817 回答
1

产生的解决方案

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

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

[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)

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

public interface IExternalValidator<T>
{
    bool IsValid(T instance);
}

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

于 2011-05-04T19:53:12.080 回答