[Architecture] Oneself Validation

[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

 

結構

[Application Architecture]   Oneself Validation架構


參與者

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; }
}
期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。