[Architecture] : Oneself Validation
動機
我們在設計Domain Object的時候,
驗證資料內容是一定有的功能,這個功能可以確保使用者輸入資料的正確性。
因為Domain Object定義在BLL,理論上驗證資料內容也是要限制在BLL。
但是在實務上因為總總的限制,驗證資料內容這個功能總是會散落到別層去,甚至出現每一層都有驗證函式在流竄。
例如 : 在PL層檢查學生的學號有沒有重覆。
本文介紹一個簡單的 Oneself Validation架構,
主要是採用System.ComponentModel.DataAnnotations命名空間內.NET 4.0新增的物件。
客製化自訂的ValidationAttribute來做資料驗證。
並且定義BLL層裡的邊界介面實作,如何注入到自訂的ValidationAttribute內。
來達到將驗證資料的物件及函式封裝在BLL層,並且讓其他層呼叫起來也不會有太大的負擔。
相關資料
Silverlight實例教程 - Validation驗證系列匯總 : http://www.cnblogs.com/jv9/archive/2010/09/27/1836394.html
結構
參與者
UserReportPage
-User資料展示介面。
-驗證User屬性正確與否。
-實務上可以是WinForm、WPF、Silverlight、ASP.NET、APS.NET MVC等等各種平台。
*不同平台的顯示物件承接ValidationException顯示的方式各有不同(可以參考相關資料)。
User
-要被驗證的物件
ValidationAttribute
-.NET內建的DataAnnotations類別。
StudentNumberValidation
-自訂的DataAnnotations類別。
-複寫IsValid驗證學號是否重覆,並且使用帶入的ValidationContext來取得IUserRepository。
Validator
-用來驗證物件屬性上ValidationAttribute的物件。
-建構子帶入的ValidationContext,用來承接外部IUserRepository,傳遞給StudentNumberValidation。
UserRepositoryFactory
-生成IUserRepository。
-使用例如Spring.Net、Provider Pattern來反射生成。
-也可結合Context架構的模式取得IUserRepository。
IUserRepository
-資料物件進出BLL邊界的介面。
-單純的新增、修改、刪除、查詢。
SqlUserRepository
-資料物件進出BLL邊界的介面IUserRepository的實作。
範例程式
*範例只包含關鍵片段的程式碼。
*主要演示IUserRepository如何傳遞。
public class UserReportPage
{
public void CreateUser(string studentNumber)
{
// 生成User
User user = new User();
user.StudentNumber = studentNumber;
// 生成IUserRepository
IUserRepository userRepository = UserRepositoryFactory.Create();
// Validate - 驗證失敗會丟出ValidationException,或是改用Validator.TryValidateXXXX就可以只取值不丟出例外。
Validator.ValidateProperty(studentNumber, new ValidationContext(user, null, new Dictionary<object, object>() { { new object(), userRepository } }) { MemberName = "StudentNumber" });
}
}
public class StudentNumberValidation : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
#region Require
if (value == null) throw new ArgumentNullException();
if (validationContext == null) throw new ArgumentNullException();
#endregion
// Argument
string studentNumber = value as string;
IUserRepository userRepository = null;
foreach (object item in validationContext.Items.Values)
{
userRepository = item as IUserRepository;
if (userRepository != null) break;
}
// Require
if (validationContext.MemberName != "StudentNumber") throw new ArgumentException("validationContext.MemberName");
if (studentNumber == null) throw new ArgumentNullException("studentNumber");
if (userRepository == null) throw new ArgumentNullException("userRepository");
// Valid
if (userRepository.IsExist(studentNumber) == true) return new ValidationResult("學號已存在");
// Return
return ValidationResult.Success;
}
}
public class UserRepositoryFactory
{
public static IUserRepository Create()
{
IUserRepository userRepository = null; // 使用例如Spring.Net、Provider Pattern來反射生成。
return userRepository;
}
}
public interface IUserRepository
{
// Methods
bool IsExist(string studentNumber);
}
public class User
{
// Properties
[StudentNumberValidation]
public string StudentNumber { get; set; }
}
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。