根據外部因素驗證對象(即數據儲存唯一性)
描述
我的解決方案有這些項目:
- DAL = 修改後的實體框架
- DTO = 能夠自我驗證的數據傳輸對象
- BL = 業務層服務
- WEB = 展示 Asp.net MVC 應用程序
DAL、BL 和 WEB 都參考了 DTO,這很棒。
該過程通常以這種方式執行:
- 向 WEB 發出 Web 請求
- WEB 發布 DTO
- DTO 通過自定義 ActionFilter 自動驗證
- 自動收集驗證錯誤
- (驗證OK)WEB呼叫BL提供DTO
- BL 使用 DTO 呼叫 DAL(可以通過它們或僅使用它們)
DTO驗證問題然後……
我的 DTO 能夠根據自己的狀態(屬性值)驗證自己。但是現在,當情況並非如此時,我遇到了一個問題。我需要他們使用 BL(以及因此 DAL)進行驗證。
我的真實範例:使用者註冊並且 WEB 獲得了一個經過驗證的使用者 DTO。有問題的部分是
username驗證。應根據數據儲存檢查其唯一性。我該怎麼做?
還有其他資訊表明,所有 DTO 都為 IoC 目的和 TDD實現了一個介面(即
UserDTO 實現)。IUser兩者都是DTO 項目的一部分。不可能的嘗試
- 我不能在 DTO 中引用 BL,因為我會得到循環引用。
Compilation error2. 我無法創建一個額外的 DTO.Val 項目來引用部分 DTO 類並在那裡實現它們的驗證(他們會引用 BL + DTO)。
Partial classes can't span assemblies.可能的嘗試
- 創建一個特殊
ActionFilter的,可以根據外部條件驗證對象。這將在WEB 項目中創建,因此可以看到將在此處使用的 DTO 和 BL。- 將 DTO 放在 BL 中,並將 DTO 介面保留為其他項目引用的實際 DTO,並重構所有程式碼以使用介面而不是具體類。
- 不要處理外部依賴驗證,讓外部依賴拋出異常——這可能是這個問題最糟糕的解決方案
你有什麼建議?
我會建議一個我在過去一周左右才嘗試過的實驗。
基於這個靈感,我正在創建 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 中的新驗證功能,但如果沒有自定義修改,它仍然不符合上述願望清單。
希望這可以幫助。
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; }}