blog

ASP.NET MVCにおける4つのバリデーション・プログラミング手法

ASP.NET MVCはModelバインディングを使用してターゲットActionのパラメータリストを生成しますが、ターゲットActionメソッドの実際の実装では、バインドされたパラメータの妥当性を確認...

Nov 18, 2017 · 20 min. read
シェア

ASP.NET MVCはModelバインディングを使用してターゲットActionのパラメータリストを生成しますが、ターゲットActionメソッドの実際の実行では、バインドされたパラメータの妥当性を確認するために、Modelバインディングのパラメータの検証を行う必要があります。要約すると、バインドされたパラメータの検証を実行するために使用できる4つの異なるプログラミングモデルがあります。

I. 拘束パラメータの手動検証

特定のActionメソッドの定義では、パラメータが正常に手動検証の実装にバインドされているプログラミングの最も直接的な方法の1つであることは間違いありませんが、対応するActionメソッドのパラメータ検証ロジックを実装する方法を示すために簡単な例を通じて、次の、および失敗した場合には、クライアントへのエラーメッセージ応答の検証を渡します。ASP.NET MVCアプリケーションでは、検証されるデータ型として次のPersonクラスが定義され、そのName、Gender、Ageプロパティはそれぞれ人の名前、性別、年齢を表します。

public class Person  
{  
    [DisplayName("名前")]  
    public string Name { get; set; }  
 
    [DisplayName("性別")]  
    public string Gender { get; set; }  
 
    [DisplayName("年齢")]  
    public int? Age { get; set; }  
} 

次に、HomeController を次のように定義します。GET リクエスト用の Action メソッド Index で Person オブジェクトを作成し、対応する View でモデルとしてレンダリングします。POST リクエストをサポートする別の Index メソッドには Person 型のパラメータがあり、 Action メソッドで Validate メソッドを呼び出すことで検証されます。バリデーションに成功すると、"Input data passed validation" という内容の ContentResult が返されます。

public class HomeController : Controller  
{  
    [HttpGet]  
    public ActionResult Index()  
    {  
        return View(new Person());  
    }  
 
    [HttpPost]  
    public ActionResult Index(Person person)  
    {  
        Validate(person);  
 
        if (!ModelState.IsValid)  
        {  
            return View(person);  
        }  
        else 
        {  
            return Content("入力データはバリデーションに合格した");  
        }  
    }  
 
    private void Validate(Person person)  
    {  
        if (string.IsNullOrEmpty(person.Name))  
        {  
            ModelState.AddModelError("Name", "'Name'は必須フィールドである");  
        }  
 
        if (string.IsNullOrEmpty(person.Gender))  
        {  
            ModelState.AddModelError("Gender", "'Gender'は必須フィールドである");  
        }  
        else if (!new string[] { "M", "F" }.Any(  
            g => string.Compare(person.Gender, g, true) == 0))  
        {  
            ModelState.AddModelError("Gender",   
            "有効な'Gender'は'M','F'のいずれかでなければならない")。;  
        }  
 
        if (null == person.Age)  
        {  
            ModelState.AddModelError("Age", "'Age'は必須フィールドである");  
        }  
        else if (person.Age > 25 || person.Age < 18)  
        {  
            ModelState.AddModelError("Age", "有効な'年齢'は18から25の間でなければならない");  
        }  
    }  
} 

上のコード・スニペットに示されているように、パラメータである Person オブジェクトの 3 つのプロパティが Validate メソッドで 1 行ずつ検証され、提供されたデータが検証に合格しなかった場合は、現在の ModelState の AddModelError メソッドが呼び出され、指定された検証エラー・メッセージが ModelError に変換されて保存されます。具体的に使用される検証ルールは以下のとおりです。

  • Person オブジェクトの Name、Gender、および Age プロパティは必須フィールドであり、Null にすることはできません。
  • Gender属性の値は "M "か "F "でなければなりません。
  • Age属性は、18歳から25歳までの年齢を表します。

以下は、ActionメソッドIndexに対応するViewの定義です。このViewは、人物の情報を編集するためのフォームを含む、Person型のModelを強く型付けしたViewです。HtmlHelper<TModel>の拡張メソッドEditorForModelを直接呼び出すと、編集モードのフォームにモデルとしてPersonオブジェクトが表示されます。

@model Person  
<html> 
<head> 
    <title>人事情報を編集する</title> 
</head> 
<body> 
    @using (Html.BeginForm())  
    {   
        @Html.EditorForModel()  
        <input type="submit" value="を保存する。> 
    }  
</body> 
</html> 

プログラムを実行すると、直接、人物の基本情報を編集するためのページが表示され、不正なデータを入力して送信すると、対応する検証情報が図1の形式で表示されます。

II.ValidationAttribute機能の使用

アクションメソッドで入力パラメーターとビジネスロジックに対してバリデーションロジックを定義することは推奨されるプログラミングアプローチではありません。ほとんどの場合、同じデータ型は異なるアプリケーションシナリオで同じ検証ルールを持ちます。検証ルールをデータ型に関連付け、フレームワーク自身にデータ検証を実装させることで、エンド開発者はビジネスロジックの実装により集中することができます。これは実際にASP.NET MVCのモデル検証システムでサポートされているデフォルトのプログラミングスタイルです。データ型を定義するとき、ValidationAttribute機能を型とそのデータメンバに適用して、デフォルトの検証ルールを定義することができます。

System.ComponentModel.DataAnnotations "名前空間は、特定の ValidationAttribute 属性タイプのセットを定義します。メンバにバリデーションを適用できます。これらの定義済みの ValidationAttribute は、この章の焦点ではありません。

通常のバリデーションは、上記の定義済みの ValidationAttribute 機能で実現できますが、多くの場合、特殊なバリデーションを解決するために、カスタムの ValidationAttribute 機能を作成する必要があります。例えば、上記のデモ例の Person オブジェクトのバリデーションでは、Gender 属性で指定された性別属性の値が "M/m" および "F/f" のいずれかである必要があります。ValidationAttribute フィーチャを作成する必要があります。

値は指定された範囲内になければならない」といった検証ルールのために DomainAttribute 機能を定義します。次のコード・スニペットに示すように、DomainAttribute には IEnumerable<string> 型の読み取り専用属性があり、コンストラクタで初期化される有効な値のリストを提供します。具体的な検証は書き直された IsValid メソッドで実装され、検証される値がこのリストにある場合、検証は成功したとみなされ、True が返されます。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,   AllowMultiple = false)]  
public class DomainAttribute : ValidationAttribute  
{  
    public IEnumerable<string> Values { get; private set; }  
 
    public DomainAttribute(string value)  
    {  
        this.Values = new string[] { value };  
    }  
 
    public DomainAttribute(params string[] values)  
    {  
        this.Values = values;  
    }  
 
    public override bool IsValid(object value)  
    {  
        if (null == value)  
        {  
            return true;  
        }  
        return this.Values.Any(item => value.ToString() == item);  
    }  
 
    public override string FormatErrorMessage(string name)  
    {  
        string[] values = this.Values.Select(value => string.Format("'{0}'",  value)).ToArray();  
        return string.Format(base.ErrorMessageString, name,string.Join(",",   values));  
    }  
} 

ASP.NET MVC は、パラメータバインディング中に対象のパラメータ型またはデータメンバに適用された ValidationAttribute 機能を自動的に抽出し、それらを使用して提供されたデータを検証するため、上記の例で示したように Action メソッドで検証を実装する必要はなくなり、パラメータ型を定義するときに対応する ValidationAttribute 機能を適用するだけでよくなります。代わりに、対応する ValidationAttribute 機能を適用してパラメータ型の Person を定義し、検証ルールを対応するデータ・メンバに関連付けるだけです。

以下は、関連する ValidationAttribute 機能が属性メンバに適用された Person タイプの定義です。RequiredAttribute フィーチャは、3 つの属性すべてに適用されて必須データ・メンバとなり、DomainAttribute フィーチャと RangeAttribute フィーチャは、Gender 属性と Age 属性に適用されて有効な属性値の範囲を制限します。

public class Person  
{  
    [DisplayName("名前")]  
    [Required(ErrorMessageResourceName = "Required",    ErrorMessageResourceType = typeof(Resources))]  
    public string Name { get; set; }  
 
    [DisplayName("性別")]  
    [Required(ErrorMessageResourceName = "Required",   ErrorMessageResourceType = typeof(Resources))]  
    [Domain("M", "F", "m", "f", ErrorMessageResourceName = "Domain",  ErrorMessageResourceType = typeof(Resources))]  
    public string Gender { get; set; }  
 
    [DisplayName("年齢")]  
    [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources))]  
    [Range(18, 25, ErrorMessageResourceName = "Range",  ErrorMessageResourceType = typeof(Resources))]  
    public int? Age { get; set; }  
} 

3つのValidationAttribute機能で使用されるエラーメッセージは、図2に示すように、すべてプロジェクトのデフォルト・リソース・ファイルで定義されています。

ASP.NET MVCは、バインドされたパラメータの検証を自動化するために、バインドされたパラメータタイプに適用されたValidationAttribute機能を自動的に抽出するので、特定のActionメソッドでパラメータを手動で検証する必要はありません。次のコードスニペットに示すように、Validate メソッドは Action メソッド Index で明示的に呼び出されなくなりましたが、アプリケーションを実行して不正なデータを含むフォームを送信すると、図 1 に示すような出力が得られます。

public class HomeController : Controller  
{  
    //其他成员  
    [HttpPost]  
    public ActionResult Index(Person person)  
    {  
        if (!ModelState.IsValid)  
        {  
            return View(person);  
        }  
        else 
        {  
            return Content("入力データはバリデーションに合格した");  
        }  
    }  
} 

#p#

IValidatableObjectインタフェースを実装したデータ型とします。

ValidationAttribute機能によってデータ型に直接バリデーションルールを定義し、パラメータバインディング時にASP.NET MVCにパラメータをバリデーションさせることに加えて、データ型に直接バリデーション操作を定義することもできます。検証操作がデータ型に直接実装されるということは、対応するデータオブジェクトに「自己検証」機能があるということです。これらの自己検証型は、"System.ComponentModel.DataAnnotations" 名前空間で定義されているインターフェース IValidatableObject を実装しています。

public interface IValidatableObject  
{  
    IEnumerable<ValidationResult> Validate(  ValidationContext validationContext);  
} 

上のコード・スニペットに示すように、IValidatableObject インターフェースには独自のメソッド Validate があり、自己に対する検証が実装されています。上記のデモ例で定義したデータ型 Person の場合、次のような形で自己検証型として定義できます。

public class Person: IValidatableObject  
{  
    [DisplayName("名前")]  
    public string Name { get; set; }  
 
    [DisplayName("性別")]  
    public string Gender { get; set; }  
 
    [DisplayName("年齢")]  
    public int? Age { get; set; }  
 
    public IEnumerable<ValidationResult> Validate( ValidationContext validationContext)  
    {  
        Person person = validationContext.ObjectInstance as Person;  
        if (null == person)  
        {  
            yield break;  
        }  
        if(string.IsNullOrEmpty(person.Name))  
        {  
            yield return new ValidationResult("'Name'これは必須フィールド", new string[]{"Name"});  
        }  
 
        if (string.IsNullOrEmpty(person.Gender))  
        {  
            yield return new ValidationResult("'Gender'これは必須フィールド", new string[] { "Gender" });  
        }  
        else if (!new string[]{"M","F"}.Any( g=>string.Compare(person.Gender,g, true) == 0))  
        {  
            yield return new ValidationResult("有効な'Gender'は'M','F'のいずれかでなければならない。,   new string[] { "Gender" });  
        }  
 
        if (null == person.Age)  
        {  
            yield return new ValidationResult("'Age'これは必須フィールド",    new string[] { "Age" });  
        }  
        else if (person.Age > 25 || person.Age < 18)  
        {  
            yield return new ValidationResult("'Age'18歳から25歳でなければならない。,    new string[] { "Age" });  
        }              
    }  
} 

上記のコード・スニペットに示すように、Person 型に IValidatableObject インターフェースを実装します。実装された Validate メソッドでは、検証された Person オブジェクトが検証コンテキストから取得され、そのプロパティ・メンバが 1 つずつ検証されます。データ・メンバが検証に合格しなかった場合、エラー・メッセージとデータ・メンバ名が ValidationResult オブジェクトにカプセル化され、メソッドは最終的に ValidationResult 型の要素のコレクションを返します。残りのコードに変更を加えずに、アプリケーションを直接実行し、不正なデータ入力でフォームを送信しても、図1に示すような出力が得られます。

IV.データ型にIDataErrorInfoインターフェイスを実装させる方法

上記のように、データ型がIValidatableObjectインターフェイスを実装し、実装されたValidateメソッドで特定の検証ロジックを定義することで、ASP.NET MVCによってデータ型が認識され、バインドされたデータオブジェクトの検証を実行するメソッドが自動的に呼び出されます。データ型が IDataErrorInfo インターフェースを実装している場合も、同様の自動検証を行うことができます。

IDataErrorInfo インターフェイスは "System.ComponentModel" ネームスペースで定義されており、エラーメッセージをカスタマイズする標準的な方法を提供します。次のコード スニペットに示すように、IDataErrorInfo には 2 つのメンバがあり、それ自体に基づくエラー メッセージを取得する読み取り専用の属性 Error と、指定されたデータ メンバのエラー メッセージを返す読み取り専用のインデックスがあります。

public interface IDataErrorInfo  
{  
    string Error { get; }  
    string this[string columnName] { get; }  
} 

また、上で示した例では、検証が必要なデータ型 Person が再定義されています。以下のコード・スニペットに示すように、Person に IDataErrorInfo インターフェースを実装します。この実装のインデックスでは、インデックス・パラメータの columnName を属性名とみなし、それに基づいて上記のルールに従って対応する属性メンバにバリデーションが適用され、バリデーションに失敗した場合は対応するエラー・メッセージが返されます。他のコードに変更を加えず、プログラムを直接実行し、不正なデータ入力があった場合にフォームを送信しても、図 1 のような出力が得られます。

public class Person : IDataErrorInfo  
{  
    [DisplayName("名前")]  
    public string Name { get; set; }  
 
    [DisplayName("性別")]  
    public string Gender { get; set; }  
 
    [DisplayName("年齢")]  
    public int? Age { get; set; }  
 
    [ScaffoldColumn(false)]  
    public string Error { get; private set; }  
 
    public string this[string columnName]  
    {  
        get   
        {  
            switch (columnName)  
            {  
                case "Name":  
                    {   
                        if(string.IsNullOrEmpty(this.Name))  
                        {  
                            return "'名前」は必須フィールドである。;  
                        }  
                        return null;  
                    }  
                case "Gender":  
                    {  
                        if (string.IsNullOrEmpty(this.Gender))  
                        {  
                            return "'性別」は必須フィールドである。;  
                        }  
                        else if (!new string[] { "M", "F" }.Any(  
                            g => string.Compare(this.Gender, g, true) == 0))  
                        {  
                            return "'性別」は「M」、「F」のいずれかでなければならない。;  
                        }  
                        return null;  
                    }  
                case "Age":  
                    {  
                        if (null == this.Age)  
                        {  
                            return "'年齢」は必須フィールドである。;  
                        }  
                        else if (this.Age > 25 || this.Age < 18)  
                        {  
                            return "'年齢は "18歳から25歳でなければならない";  
                        }  
                        return null;  
                    }  
                default: return null;  
                          
            }  
        }  
    }  
} 
Read next

360 With WiFiは2013年に「エコネット・イノベーション・スター」を受賞している。

2013年11月28日、イー・ガードインターナショナル主催のイー・ガードインターネットイノベーション会議が北京国際コンベンションセンターで開催され、"イノベーションはあなたの周りにある "をテーマに、ハードウェア、インタラクティブエンターテインメント、マーケティング、アプリケーションの4つの主要な視点から業界イノベーションの議論が行われました。360WiFiは、インターネットハードウェアの分野で卓越した技術革新が評価され、「イノベーションの星」賞を受賞しました。

Nov 16, 2017 · 1 min read