2014年9月5日 星期五

ASP.NET MVC - 下拉選單的日期選擇器 - 年齡限制

前面七篇有關 ASP.NET MVC 的下拉選單日期選擇器已經完成了基本的功能,而現在這邊要講的是比較進階一些而且也算是另外附加的功能,有些線上的服務在做申請的時候會有年齡的限制,例如限制需要在 18 歲以上、18 ~ 65 歲之間或是不能超過 70 歲等等,ASP.NET MVC 裡的 DataAnnotation 有提供一個 RangeAttribute 類別,不過 Range 主要是針對數值型資料來進行驗證,無法針對輸入的日期資料進行驗證,所以這邊就必須要另外自行去建立一個新的 Validation Attribute 類別。

這一篇就要來說明如何讓已經完成的下拉選單日期選擇器再增加年齡限制的驗證,雖然說是年齡限制,不過也可以應用在年資、使用年限等資料的驗證。

 


這次我比較偷懶,我直接引用別人已經開發完成的功能來使用,參考的內容如下:

How to implement Custom user defined Age Range Validation / Data Annotations rules in MVC 4 application. - DotNet - awesome

http://dotnetawesome.blogspot.tw/2014/07/how-to-implement-custom-user-defined-age-range-validation-mvc4.html

image

Live Demo

image

雖然原始參考內容是給一般的日期輸入介面使用,不過還是一樣可以適用於我們的下拉選單日期選擇器,至於怎麼套用在我們既有的網站與功能上面呢?就看以下的幾個步驟說明。

 

Step.1

先將參考文章所提供的 AgeRangeValidation.cs 原始碼給複製下來,然後在網站裡建立一個新資料夾「Infrastructure」,在這個資料夾下建立新的類別,類別名稱並不會沿用 AgeRangeValidation,而是會依照慣例的命名原則將類別名稱命名為「AgeRangeAttribute」,

image

public class AgeRangeAttribute : ValidationAttribute, IClientValidatable
{
    public int MinAge { get; set; }
    public int MaxAge { get; set; }
 
    protected override ValidationResult IsValid(
        object value, 
        ValidationContext validationContext)
    {
        //THIS IS FOR SERVER SIDE VALIDATION
 
        // if value not supplied then no error return
        if (value == null)
        {
            return null;
        }
 
        int age = 0;
        age = DateTime.Now.Year - Convert.ToDateTime(value).Year;
        if (age >;= MinAge && age <= MaxAge)
        {
            return null; // Validation success
        }
        else
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            // error 
        }
    }
 
    public IEnumerable<;ModelClientValidationRule> GetClientValidationRules(
        ModelMetadata metadata, 
        ControllerContext context)
    {
        //THIS IS FOR SET VALIDATION RULES CLIENT SIDE
 
        var rule = new ModelClientValidationRule()
        {
            ValidationType = "agerangevalidation",
            ErrorMessage = FormatErrorMessage(metadata.DisplayName)
        };
        rule.ValidationParameters["minage"] = MinAge;
        rule.ValidationParameters["maxage"] = MaxAge;
        yield return rule;
    }
}

 

Step.2

再來是處理前端的部分,將原始參考文章所提供的前端驗證原始碼複製下來,然後放到 site.js 裡,

// Here I will add code for client side validation for our custom validation.
$.validator.unobtrusive.adapters.add(
    "agerangevalidation",
    ["minage", "maxage"],
    function (options) {
        options.rules["agerangevalidation"] = options.params;
        options.messages["agerangevalidation"] = options.message;
    });
$.validator.addMethod("agerangevalidation", function (value, elements, params) {
    if (value) {
        var valDate = new Date(value);
        var lessThenMinAge = (new Date().getFullYear() - valDate.getFullYear()) < parseInt(params.minage);
        var greaterThenMaxAge = (new Date().getFullYear() - valDate.getFullYear()) >; parseInt(params.maxage);
        if (lessThenMinAge || greaterThenMaxAge) {
            return false;
        }
    }
    return true;
});

image

 

Step.3

接著就是到 DemoModel 類別裡去使用 AgeRangeAttribute,分別指定年齡區間的最小值、最大值以及驗證錯誤發生時顯示的訊息內容,

image

到這邊為止我們只有在原本的下拉選單日期選擇器去增加類別、程式,並沒有去對原本的程式做任何的修改,有修改的只有從原始參考文章所複製過來的程式碼。

 

Step.4

這個步驟就是直接執行來看結果,第一個操作就是選一個未滿 18 歲的生日來做測試,結果如下:

image

再選一個年齡大於 65 歲的生日,結果如下:

image

符合條件

image

 

好啦!就這樣結束這一篇了…… 嗎?

 

不過這不太符合我們一開始所提出的需求喔,我們一開始所提出的需求是「限制需要在 18 歲以上、18 ~ 65 歲之間或是不能超過 70 歲」,而這個 AgeRangeAttribute 只有滿足了年齡區間限制的驗證,然而只想要針對年齡最小值或是年齡最大值限制的驗證,就與 AgeRangeAttribute 的職責有所不同,所以就必須要另外建立 ValidationAttribute 類別來負責處理,於是另外建立「MinAgeAttribute」「MaxAgeAttribute」這兩個類別,用來驗證最小年齡與最大年齡限制的驗證。

 

這邊先提一下,要自訂 DataAnnotation 的 Validation Attribute 類別,須繼承 ValidationAttribute 類別並且覆寫 IsValid 方法,而如果也需要提供前端驗證的話,就必須要繼承 IClientValidatable 並實作 GetClientValidationRules 方法。

實作內容如下:

最小年齡限制

MinAgeAttribute.cs

public class MinAgeAttribute : ValidationAttribute, IClientValidatable
{
    private readonly int _MinAge;
    public MinAgeAttribute(int minAge)
    {
        _MinAge = minAge;
    }
 
    protected override ValidationResult IsValid(
        object value,
        ValidationContext validationContext)
    {
        if (value == null)
        {
            return null;
        }
 
        var birthDate = Convert.ToDateTime(value);
 
        var age = DateTime.Now.Year - Convert.ToDateTime(value).Year;
        var m = DateTime.Now.Month - birthDate.Month;
 
        if (m < 0 || (m == 0 && DateTime.Now.Day < birthDate.Day))
        {
            age--;
        }
 
        return age >= _MinAge 
            ? null 
            : new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }
 
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
        ModelMetadata metadata, 
        ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ValidationType = "minagevalidation",
            ErrorMessage = FormatErrorMessage(metadata.DisplayName)
        };
 
        rule.ValidationParameters["minage"] = _MinAge;
        yield return rule;
    }
}

前端

// For MinAge Validation
$.validator.unobtrusive.adapters.add(
    "minagevalidation",
    ["minage"],
    function (options) {
        options.rules["minagevalidation"] = options.params;
        if (options.message) {
            options.messages["minagevalidation"] = options.message;
        }
    }
);
 
$.validator.addMethod("minagevalidation", function (value, elements, params) {
    if (value) {
        var birthDate = new Date(value);
        var today = new Date();
        var age = today.getFullYear() - birthDate.getFullYear();
        var m = today.getMonth() - birthDate.getMonth();
        if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
            age--;
        }
        return age >= parseInt(params.minage, 10);
    }
    return true;
});

使用

image

執行

image

 

最大年齡限制

MaxAgeAttribute.cs

public class MaxAgeAttribute : ValidationAttribute, IClientValidatable
{
    private readonly int _MaxAge;
    public MaxAgeAttribute(int maxAge)
    {
        _MaxAge = maxAge;
    }
 
    protected override ValidationResult IsValid(
        object value,
        ValidationContext validationContext)
    {
        if (value == null)
        {
            return null;
        }
 
        var birthDate = Convert.ToDateTime(value);
 
        var age = DateTime.Now.Year - Convert.ToDateTime(value).Year;
        var m = DateTime.Now.Month - birthDate.Month;
 
        if (m < 0 || (m == 0 && DateTime.Now.Day < birthDate.Day))
        {
            age--;
        }
 
        return age <= _MaxAge
            ? null
            : new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }
 
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
        ModelMetadata metadata,
        ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ValidationType = "maxagevalidation",
            ErrorMessage = FormatErrorMessage(metadata.DisplayName)
        };
 
        rule.ValidationParameters["maxage"] = _MaxAge;
        yield return rule;
    }
 
}

前端

// For MaxAge Validation
$.validator.unobtrusive.adapters.add(
    "maxagevalidation",
    ["maxage"],
    function (options) {
        options.rules["maxagevalidation"] = options.params;
        if (options.message) {
            options.messages["maxagevalidation"] = options.message;
        }
    }
);
 
$.validator.addMethod("maxagevalidation", function (value, elements, params) {
    if (value) {
        var birthDate = new Date(value);
        var today = new Date();
        var age = today.getFullYear() - birthDate.getFullYear();
        var m = today.getMonth() - birthDate.getMonth();
        if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
            age--;
        }
        return age <= parseInt(params.maxage, 10);
    }
    return true;
});

使用

image

執行

image

 

完整的前端程式內容 - site.js

;
$(function () {
 
    DateDropDownList();
});
 
function DateDropDownList() {
 
    var digitalMonthNames = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
 
    var $NormalDateDDL = $('input.Date-DropDownList');
 
    $NormalDateDDL.dateDropDowns({
        dateFormat: 'yy-MM-DD',
        monthNames: digitalMonthNames,
        taiwanCalendarYear: $NormalDateDDL.data("taiwancalendaryear") === 'True',
        yearStart: $NormalDateDDL.data("yearstart"),
        yearEnd: $NormalDateDDL.data("yearend"),
        yearOption: $NormalDateDDL.data("yearoption"),
        monthOption: $NormalDateDDL.data("monthoption"),
        dayOption: $NormalDateDDL.data("dayoption")
    });
 
    var targetID = $NormalDateDDL.attr('id');
 
    $.validator.setDefaults({
        ignore: [targetID]
    });
 
    // For AgeRange Validation.
    $.validator.unobtrusive.adapters.add(
        "agerangevalidation",
        ["minage", "maxage"],
        function (options) {
            options.rules["agerangevalidation"] = options.params;
            if (options.message) {
                options.messages["agerangevalidation"] = options.message;
            }
        });
 
    $.validator.addMethod("agerangevalidation", function (value, elements, params) {
        if (value) {
            var valDate = new Date(value);
            var lessThenMinAge = (new Date().getFullYear() - valDate.getFullYear()) < parseInt(params.minage);
            var greaterThenMaxAge = (new Date().getFullYear() - valDate.getFullYear()) > parseInt(params.maxage);
            if (lessThenMinAge || greaterThenMaxAge) {
                return false;
            }
        }
        return true;
    });
 
    // For MinAge Validation
    $.validator.unobtrusive.adapters.add(
        "minagevalidation",
        ["minage"],
        function (options) {
            options.rules["minagevalidation"] = options.params;
            if (options.message) {
                options.messages["minagevalidation"] = options.message;
            }
        }
    );
 
    $.validator.addMethod("minagevalidation", function (value, elements, params) {
        if (value) {
            var birthDate = new Date(value);
            var today = new Date();
            var age = today.getFullYear() - birthDate.getFullYear();
            var m = today.getMonth() - birthDate.getMonth();
            if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
                age--;
            }
            return age >= parseInt(params.minage, 10);
        }
        return true;
    });
 
    // For MaxAge Validation
    $.validator.unobtrusive.adapters.add(
        "maxagevalidation",
        ["maxage"],
        function (options) {
            options.rules["maxagevalidation"] = options.params;
            if (options.message) {
                options.messages["maxagevalidation"] = options.message;
            }
        }
    );
 
    $.validator.addMethod("maxagevalidation", function (value, elements, params) {
        if (value) {
            var birthDate = new Date(value);
            var today = new Date();
            var age = today.getFullYear() - birthDate.getFullYear();
            var m = today.getMonth() - birthDate.getMonth();
            if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
                age--;
            }
            return age <= parseInt(params.maxage, 10);
        }
        return true;
    });
}

 


之後再找個時間把下拉選單日期選擇器的原始碼做個整理,然後再放到 GitHub 上。

 

自訂資料驗證類別的重點

MSDN - System.ComponentModel.DataAnnotation.ValidationAttribute 類別

MSDN - System.Web.Mvc.IClientValidatable 介面

ASP.NET MVC 3 で独自の検証属性を作成して、クライアントサイド検証を行う - しばやん雑記

 

參考連結

How to implement Custom user defined Age Range Validation / Data Annotations rules in MVC 4 application. - DotNet - awesome

Calculate age in JavaScript - Stack Overflow

 

系列文章

ASP.NET MVC - 下拉選單的日期選擇器 Part.1

ASP.NET MVC - 下拉選單的日期選擇器 Part.2

ASP.NET MVC - 下拉選單的日期選擇器 Part.3 - Editor Templates

ASP.NET MVC - 下拉選單的日期選擇器 Part.4 - Editor Templates

ASP.NET MVC - 下拉選單的日期選擇器 Part.5 - Editor Templates

ASP.NET MVC - 下拉選單的日期選擇器 Part.6 - @helper ?

ASP.NET MVC - 下拉選單的日期選擇器 part.7 - Validation

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。