WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

EDM にデータアノテーション属性を付与

by WebSurfer 2017年5月21日 15:10

Microsoft のチュートリアル「10 行でズバリ!! ASP.NET MVC を構成する各コンポーネントとネーミング ルール (C#)」のように、既存の SQL Server データベースから Entity Data Model (EDM) を生成して ASP.NET MVC アプリで使用する場合、ユーザー入力を検証するためのデータアノテーション属性をどのように付与できるかを書きます。

Visual Studio で生成した EDM

上の画像は、SQL Server 2008 Express のデータベース AdventureWorksLT をベースに、Visual Studio 2015 + EF6 で生成した EDM (Model1.edmx) です。(注:Visual Studio 2010 + EF4 で生成した EDM は内容が異なりますが、データアノテーション属性を付与する方法は同じです)

生成した EDM をベースに、上に紹介した「10 行でズバリ!!」チュートリアルと同様に、Visual Studio のスキャフォールディング機能を利用して、Address テーブルを表示・編集する ASP.NET MVC アプリの Controller と View を一式自動生成する場合を例に考えます。

自動生成された EDM で Address テーブルを表す Model のコードは、上の画像で示すように Address.cs にあります。そのコードは以下の通りです(説明に不要な部分は省略しています)。

namespace AdventureWorksLT
{
    using System;
    using System.Collections.Generic;
    
    public partial class Address
    {
        //・・・コンストラクタ(省略)・・・
        
        public int AddressID { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public string StateProvince { get; set; }
        public string CountryRegion { get; set; }
        public string PostalCode { get; set; }
        public System.Guid rowguid { get; set; }
        public System.DateTime ModifiedDate { get; set; }
    
        //・・・ナビゲーションプロパティ(省略)・・・
    }
}

これに手を加えればよさそうに思えますが、それは NG です。何故なら、コードのコメントに書いてあるように、"このファイルを手動で変更すると、アプリケーションで予期しない動作が発生する可能性があります。このファイルに対する手動の変更は、コードが再生成されると上書きされます" ということだからです。

ではどうするかですが、TechNet の記事「[C#] #21. データ アノテーション検証コントロールでの検証」の「Entity Framework でのデータ アノテーション検証コントロールの使用」のセクションに書いてある方法を取るのがよさそうです。

TechNet の記事を読めばこれ以降の説明は不要かもしれませんが、記事がリンク切れになったりすると困るので、上の Address クラスの場合を例に方法を書いておきます。

具体的には以下のようなコードを EDM とは別の場所にクラスファイルを追加してそれに含めます。上の画像のソリューションエクスプローラの AddressMetaData.cs がそのクラスファイルです。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace AdventureWorksLT
{
    [MetadataType(typeof(AddressMetaData))]
    public partial class Address
    {

    }

    public class AddressMetaData
    {
        [Display(Name = "住所1")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object AddressLine1 { get; set; }

        [Display(Name = "住所2")]
        public object AddressLine2 { get; set; }

        [Display(Name = "街")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        [StringLength(10, ErrorMessage = "{0} は {1} 以内。")]
        public object City { get; set; }

        [Display(Name = "州")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object StateProvince { get; set; }

        [Display(Name = "国名")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object CountryRegion { get; set; }

        [Display(Name = "郵便番号")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object PostalCode { get; set; }

        [Display(Name = "GUID")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object rowguid { get; set; }

        [Display(Name = "更新日")]
        [Required(ErrorMessage = "{0} は必須入力です")]
        public object ModifiedDate { get; set; }
    }
}

上のコードで AddressMetaData が TechNet の記事で言うメタデータクラスです。それにプロキシプロパティを定義して必要なデータアノテーション属性を付与しています。

TechNet の記事にも書いてありますが、プロキシプロパティの型は自動生成された Address クラスのプロパティの型と同じである必要はないそうです(同じでも OK ですが)。上のコードでは TechNet の記事にならってプロキシプロパティの型は Object 型にしています。

自動生成された Address クラスは partial として定義されているところに注目してください。partial なので、上のコードのように別のクラスファイルに Address クラスを定義してそれに MetadataType 属性を付与することができます。(自動生成された Address クラスに手を加える必要はありません)

MetadataType 属性の引数に typeof(AddressMetaData) を設定することにより、メタデータクラス AddressMetaData を Address クラスに関連付けています。

以上の設定により、データアノテーション属性による検証が、クライアント側とサーバー側の両方で行われるようになります。

さらに、Display 属性も追加していますので、表示名が Display 属性の Name プロパティで設定した通りになります。

Tags: ,

MVC

DataType 属性による検証

by WebSurfer 2016年3月8日 17:21

データアノテーション検証でのデフォルトのエラーメッセージと DataType 属性による検証についておぼえておいた方がよさそうなことを備忘録として書きます。

エラーメッセージ

上の画像は、下のサンプルコードの Model のパブリックプロパティに付与したデータアノテーション属性によるエラーメッセージです。

(注1:上に表示されているエラーメッセージは全てクライアント側での検証結果によるものです。)

(注2:BrithDate については 0001 で何故かクライアント側での検証はパスします。ただし、サーバー側で検証がかかって、コントローラーで ModelState.IsValid は false になります。エラーメッセージは上記と違って「値 '0001' は BirthDate に対して無効です」となります)

なお、自分が検証に使った環境は Vista SP2 32-bit, .NET 4, Visual Studio 2010 プロフェッショナル, MVC4 のインターネットアプリケーションのテンプレート使用、ASP.NET 開発サーバー利用、IE9 です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace Mvc4App2.Models
{
  public class DefaultErrorMessage
  {
    [Required]
    public string Name { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [RegularExpression(@"^(?=.*\d)(?=.*[A-Z])[A-Z0-9]{4,8}$")]
    public string PassWord { get; set; }

    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [DataType(DataType.PhoneNumber)]
    public string Phone { get; set; }

    [DataType(DataType.Date)]
    public DateTime BirthDate { get; set; }

    [DataType(DataType.Url)]
    public string Url { get; set; }

    [StringLength(10)]
    [DataType(DataType.MultilineText)]
    public string Message { get; set; }
  }
}

各属性の ErrorMessage プロパティに何も設定しなくても上の画像のようにデフォルトでエラーメッセージが出ます。

しかし、正規表現がそのまま出たり(「フィールド PassWord は正規表現 '^(?=.*\d)(?=.*[A-Z])[A-Z0-9]{4,8}$' と一致する必要があります。」とか言われても一般ユーザーには訳がわからない)、一部はローカル化されておらず英文のままだったりとユーザーフレンドリーとは言えないようです。

さらに、MSDN フォーラムのスレッドASP.NET MVC5 でVaridateメッセージが英語で初めて知りましたが、自動的にはローカル化されず全部英語になってしまうこともあるようです。

また、DataType 属性については、付与しても検証は行われずエラーメッセージは出ないものがありますので注意が必要です。上の例では Password, PhoneNumber, MultilineText では検証が行われていません。

Password と MultilineText にはエラーメッセージが出ていますが、それは DataType 属性によるものではなく、追加で付与した RegularExpression 属性と StringLength 属性によるものです。

一方、EmailAddress, Date, Url は検証がかかります。クライアント側での検証はブラウザ依存になると思いますので自分が試した IE9 以外で同じ結果になるかは分かりませんが(未検証・未確認です)。なので、DataType 属性による検証に頼らず、自分で StringLength 属性や RegularExpression 属性等を使ってきちんと検証したほうがよさそうです。

ただし、DataType 属性を付与することによるメリットは検証以外にもあります。それは、View のコードに全て Html.EditorFor を利用しても、DataType 属性によって生成される html ソースの input 要素の type 属性が変わってくる(MultilineText の場合は input 要素でなく、textarea 要素になる)ことです。ちなみに、上の Model の DataType 属性の設定では以下のようになります。

<input ... name="Name" type="text" value="" />
            
<input ... name="PassWord" type="password" value="" />
            
<input ... name="Email" type="email" value="" />
            
<input ... name="Phone" type="tel" value="" />
            
<input ... name="BirthDate" type="date" value="0001/01/01" />
            
<input ... name="Url" type="url" value="" />

<textarea ... id="Message" name="Message"></textarea>

それゆえ、Password と MultilineText にいては DataType 属性を付与するのがよさそうです。他のデータアノテーション属性による検証とバッティングすることもなさそうですし。

以上のような訳で、DataType 属性による検証やデフォルトのエラーメッセージでは期待した結果を得るのは難しいので、

  1. DataType 属性による検証は利用せず Range, RegularExpression, Required, StringLength 属性などを組み合わせて使う。
  2. DataType 属性を使用するのは、Password や MultilineText のように、レンダリングされる html ソースの input 要素の type 属性を "password" にしたいとか、input 要素でなく textarea 要素にしたい場合に限る。
  3. エラーメッセージは自分で各データアノテーション属性の ErrorMessage プロパティに設定する。(先の記事「コレクションのデータアノテーション検証」参照)
  4. Model のコード内にエラーメッセージをハードコーディングしたくない場合は自分でリソースファイル (.resx) を作って使う。(先の記事「データアノテーション検証の多言語対応」参照)
  5. エラーメッセージを多言語化する場合は自分でリソースファイルを追加してサテライトアセンブリを作って使う。(同じく、先の記事「データアノテーション検証の多言語対応」参照)

というようにするのが正解だと思いました。

-------- 2016/6/17 追記 --------

ErrorMessage プロパティの設定は行わず、デフォルトの日本語のエラーメッセージを利用したいのであれば、Azure など特にサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れないようにしてください。

Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えます。

そして、リソースマネージャが実行時に、Thread.CurrentUICulture などで得られる CultureInfo(現在の要求を処理しているスレッドのカルチャ情報)を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。

Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば英語 OS では英語のリソースから UI に表示されるテキストを取得します。(日本語のリソースがあっても使われません)

Tags: ,

MVC

クライアント側検証の無効化

by WebSurfer 2015年2月17日 18:40

先の記事 コレクションのデータアノテーション検証 でユーザー入力の検証の話を書きましたが、デフォルトで有効になっているクライアント側での検証を無効にするにはどうすればいいかという話を書きます。

ユーザー入力の検証

これは、Visual Studio 2010 で MVC4 アプリを インターネットアプリケーション テンプレートで作った場合の話で、Visual Studio 2013 とか MVC5 とかでは違うかもしれませんのでご注意ください。

アプリケーションルート直下の web.config での以下の定義がされており、クライアント側での検証がアプリケーション全体で有効に設定されています。

<appSettings>
  ・・・中略・・・
  <add key="ClientValidationEnabled" value="true" />
  ・・・中略・・・
</appSettings>

クライアント側での検証を無効にしたいことがあるとすると多分特定の View だけでしょうから、その場合は当該 View のコードに下記を追加すれば OK です。

@{
  Html.EnableClientValidation(false);
}

クライアント側検証には EnableClientValidation の他に EnableUnobtrusiveJavaScript が使われていますが、stackoverflow の記事 によると、後者は Ajax にも使われていて、false にすると Ajax が動かなくなるとのことなので注意してください。

以上で話は終わりなのですが、それだけでは記事としてはちょっと面白くないので、上記に関連して調べたことを備忘録として書いておきます。(笑)

MVC4 の新機能として JavaScript / CSS ファイルの縮小化と結合処置の自動化機能があり、その機能を利用するため、App_Start フォルダの BundleConfig.cs ファイルに以下の定義がされています。

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));

    // ・・・中略・・・
  }
}

アプリケーションルート直下の Scripts フォルダには jquery-1.7.1.js, jquery.validate.js その他必要な jQuery 関係の外部スクリプトファイルが自動的に配置されます。

Global.asax には以下のコードが記述されており、アプリケーション起動時に外部スクリプトファイルのパスが ASP.NET の BundleCollection オブジェクトに登録されるようになっています。(詳しくは @IT の記事 Visual Studio 2012の新機能とASP.NET 4.5のコア機能 (3/4) を見てください)

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
      // ・・・中略・・・

      BundleConfig.RegisterBundles(BundleTable.Bundles);

      // ・・・中略・・・
  }
}    

BundleCollection オブジェクトに登録した外部スクリプトファイルを参照するには、View で Scripts.Render メソッド を使用します。

具体的には _Layout.cshtml(Web Forms アプリのマスターページに相当)の @Scripts.Render("~/bundles/jquery") と View の @Scripts.Render("~/bundles/jqueryval") で以下の外部スクリプトファイルへの参照が HTML ソースに定義されます。

<script src="/Scripts/jquery-1.7.1.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

(注)上記は <compilation debug="true" ... /> の場合です。debug="false" の場合はファイルの縮小化と結合処置の自動化機能が働いて以下のようになります。

<script src="/bundles/jquery?v=1A_Q..."></script>
<script src="/bundles/jqueryval?v=-tc2Q..."></script>

以上ような設定において、例えば Model にアノテーション属性を以下のように付与したとします。

public class Parent
{
  //・・・中略・・・

  [Required(ErrorMessage = "{0} は必須")]
  [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
  [Display(Name = "Parent Name")]
  public string Name { get; set; }

  //・・・中略・・・
}

そして、View のコードを以下のように記述したとします。

<div class="editor-label">
  @Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
  @Html.EditorFor(model => model.Name)
  @Html.ValidationMessageFor(model => model.Name)
</div>

そうすると、クライアント側検証が有効の場合(Html.EnableClientValidation(true) の時)は、ASP.NET がレンダリングする HTML ソースは以下のようになります。

<div class="editor-label">
  <label for="Name">Parent Name</label>
</div>
<div class="editor-field">
  <input 
    class="text-box single-line" 
    data-val="true" 
    data-val-length="Parent Name は 5 文字以内" 
    data-val-length-max="5" 
    data-val-required="Parent Name は必須" 
    id="Name" 
    name="Name" 
    type="text" 
    value="" />
  <span 
    class="field-validation-valid" 
    data-valmsg-for="Name" 
    data-valmsg-replace="true">
  </span>
</div>

上記のように input 要素に付与された data-val* 属性に従って、参照された外部スクリプトに含まれる Unobtrusive(控えめな)JavaScript がクライアント側での検証を行うようになります。

一方、クライアント側検証を無効にした場合(Html.EnableClientValidation(false) の時)は ASP.NET がレンダリングする HTML ソースは以下のようになります。(注:外部スクリプトファイルへの参照は変わりません)

<div class="editor-label">
  <label for="Name">Parent Name</label>
</div>
<div class="editor-field">
  <input 
    class="text-box single-line" 
    id="Name" 
    name="Name" 
    type="text" 
    value="" />
</div>

クライアント側での検証はかかりませんがサーバー側で検証は行われ、検証結果が NG の場合は input 要素の直後に以下の span 要素が追加され、エラーメッセージ(上の画像の赤い文字)が表示されます。

<span class="field-validation-error">
  Parent Name は必須
</span>

Tags: ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2017年6月  >>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

View posts in large calendar