基于外部因素validation对象(即数据存储唯一性)

描述

我的解决scheme有这些项目:

  • DAL =修改的entity framework
  • DTO =能够validation自己的数据传输对象
  • BL =业务层服务
  • WEB =演示文稿Asp.net MVC应用程序

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

  1. networking请求被发送到WEB
  2. WEB获取DTO发布
    • DTO通过自定义ActionFilter自动validation
    • validation错误是自动收集的
  3. (确认无误)WEB调用BL提供DTO
  4. BL通过使用DTO调用DAL(可以通过它们或仅使用它们)

DTOvalidation问题,然后…

我的DTO可以根据自己的状态(属性值)进行validation。 但是现在我遇到了一个问题,但事实并非如此。 我需要他们validation使用BL(因此DAL)。

我真实的例子 :用户注册和WEB获得一个用户DTO得到validation。 有问题的部分是usernamevalidation。 它的独特性应该针对数据存储进行检查。
我该怎么做?

还有其他的信息,所有的DTO实现一个接口(即User DTO实现IUser ),用于IoC目的和TDD。 两者都是DTO项目的一部分。

不可能的尝试

  1. 我不能在DTO中引用BL,因为我会得到循环引用。
    Compilation error
  2. 我不能创build一个额外的DTO.Val项目来引用部分DTO类并在那里实现它们的validation(他们会参考BL + DTO)。
    Partial classes can't span assemblies.

可能的尝试

  1. 创build一个特殊的ActionFilter来validation对象的外部条件。 这个将在WEB项目中创build,从而看到将在这里使用的DTO和BL。
  2. 将DTO放在BL中,并将DTO接口保持为其他项目引用的实际DTO,并重构所有代码以使用接口而不是具体的类。
  3. 不要处理外部依赖validation,并让外部依赖项抛出exception – 可能这个问题的最糟糕的解决scheme

你会build议什么?

我会build议一个实验,我只在上个星期左右进行试验。

基于这种灵感,我创build了与DataAnnotations方法有点不同的DTO。 样品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看起来更多的工作,这是真的,但它并不是很大。 我认为这更能体现在课堂上(我现在有一些非常难看的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 } 

ValidationRule和我的validation函数:

 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 } 

如果我的代码看起来很乱,那是因为在过去的几天里我一直在使用这个validation方法。 我需要这个想法来满足一些要求:

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

我认为这种方法会使我得到我想要的,也许你也是如此。

我想象一下,如果你跟我一起跳上这个我们会相当“自己”的人,但这可能是值得的。 我正在阅读有关MVC 2新的validationfunction,但它仍然不符合上述愿望清单,没有自定义修改。

希望这可以帮助。

S#arp体系结构有一个[DomainSignature]方法标识符,与类级别validation器[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/

我有这个完全相同的问题,并试图寻找工作几天,几天来,我结束了我的DTO,DAL和BL合并成一个库。 我保持我的表示层分开。 不知道这是否是你的select。 对于我来说,我认为我不断改变数据存储的机会非常小,所以不需要单独的层。

我也为我所有的DTOvalidation实施了Microsoftvalidation应用程序块。 他们有一个“自我validation”的方法,可以让你执行复杂的validation。

产生的解决scheme

我结束了使用控制器操作filter,能够validation对象不能从对象本身获得的外部因素。

我创build了filter,该filter使用action参数的名称来检查validation程序types,以validation该特定参数。 当然,这个validation器必须实现一定的接口,使其全部可重用。

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

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

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

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

Interesting Posts