1

MVC検証の基礎(エンティティフレームワークを使用)

シナリオ:

私は以下のようなモデルクラスを持っています(Entity Framework EF.x DbContext Generatorを介して自動生成されます)。

(現在、ビューモデルはありません)。

public partial class Activity
{
    public int Id { get; set; }

    public byte Progress { get; set; }
    public decimal ValueInContractCurrency { get; set; }
    public System.DateTime ForecastStart { get; set; }
    public System.DateTime ForecastEnd { get; set; }

    public int DepartmentId { get; set; }   
    public int OwnerId { get; set; }
    public int StageId { get; set; }
    public int StatusId { get; set; }

    public virtual Department Department { get; set; }
    public virtual Owner Owner { get; set; }
    public virtual Stage Stage { get; set; }
    public virtual Status Status { get; set; }
}

強く型付けされたビューで空白のフォームを送信すると、次の検証メッセージが表示されます。

Progressフィールドは必須です。

ValueInContractCurrencyフィールドは必須です。

ForecastStartフィールドは必須です。

ForecastEndフィールドは必須です。

つまり、dbテーブルのすべてのフィールド。

これらを入力して再度送信すると、コントローラーが呼び出されます。その後、IsValidがfalseであるため、コントローラーはビューページに戻ります。

次に、画面が次の検証メッセージとともに再表示されます。

StageIdフィールドは必須です。

DepartmentIdフィールドは必須です。

StatusIdフィールドは必須です。

OwnerIdフィールドは必須です。

つまり、dbテーブルのすべての外部キーフィールド(これらもすべて選択ボックスです)。

これらを入力すると、フォームは正常に送信され、データベースに保存されます。

質問:

  1. [必須]属性を使用していない場合、検証はどこから行われますか?これはエンティティフレームワークと関係がありますか?

  2. フォームがクライアント側ですべてをすぐに検証しないのはなぜですか。外部キー(または選択ボックス)は、空であるため明らかに無効であるにもかかわらず、IsValid()によってのみチェックされるという違いは何ですか?

  3. ユーザーがフォームを2回送信する必要がなく、すべての検証メッセージが一度に表示されるように、すべてを1つのステップ(空のフィールドの場合)で検証するにはどうすればよいですか?クライアント側の検証をオフにする必要がありますか?

([Required]属性を外部キーフィールドに追加しようとしましたが、違いは見られませんでした(おそらく、IsValidにのみ影響します)。Html.EnableClientValidation()も呼び出してみましたが、違いはありませんでした。 )。

4 ..最後に、検証に[MetadataType [MetadataType(typeof(...)]]を使用している人を見てきましたが、ビューモデルがある場合はなぜそうするのですか、そうでない場合のみですか?

明らかに私はここでいくつかの基本を見逃しているので、さらに、属性に関する別のエッセイではなく、MVC検証プロセスがJavaScript /コントローラー呼び出しを含むステップバイステップでどのように正確に機能するかについての詳細なチュートリアルを知っている人がいれば、私は行うことができますそれへのリンクもあります:c)


ミステリーマンの詳細:

次のようなソリューションのセットアップ:

.NET4

MVC3

EF5

EF5.xDbコンテキストジェネレーター

EF.x Dbコンテキストジェネレータファイル(.ttファイル)を関連付けるためにedmxデザインサーフェスで使用される「コード生成アイテムの追加」

コントローラは次のようになります。

    // GET: /Activities/Create
    public ActionResult Create()
    {
        ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
        ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
        ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
        ViewBag.StageId = new SelectList(new List<string>());
        ViewBag.StatusId = new SelectList(db.Status.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
        return View();
    } 


    // POST: /Activities/Create
    [HttpPost]
    public ActionResult Create(Activity activity)
    {
        if (ModelState.IsValid)
        {
            db.Activities.Add(activity);
            db.SaveChanges();
            return RedirectToAction("Index");  
        }

        ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
        ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
        ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
        ViewBag.StageId = new SelectList(db.Stages, "Id", "Number");
        ViewBag.StatusId = new SelectList(db.Status, "Id", "Name");
        return View(activity);
    }

ビューは次のようになります。

<!-- this refers to  the EF.x DB Context class shown at the top of this post -->
@model RDMS.Activity  

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>


@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Activity</legend>


        <div class="editor-label">
            @Html.LabelFor(model => model.StageId, "Stage")
        </div>
        <div class="editor-field">
            @Html.DropDownList("StageId", String.Empty)
            @Html.ValidationMessageFor(model => model.StageId)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.Progress)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Progress)
            @Html.ValidationMessageFor(model => model.Progress)
        </div>

    <!-- ETC...-->

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
4

3 に答える 3

1

[必須]属性を使用していない場合、検証はどこから行われますか?これはエンティティフレームワークと関係がありますか?

MVC(EFではない)にはデフォルトの検証プロバイダーがあり、次の2つをチェックします。

  • 提供された値のタイプ(intプロパティの文字列)=>(わかりませんが、次のようなもの)yyy is not valid for field xxx

  • 値型の「チェックヌル」属性(プロパティに対応する空のフィールドを許可すると文句を言い、intプロパティの空のフィールドを受け入れint?ます)。=>The xxx field is required

この2番目の動作は、global.asaxで非アクティブ化できます(プロパティ名はかなり明確です):

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

クライアント側の検証が有効になっている場合、これらの検証、およびDataAnnotations(RequiredStringLength...)に関連する検証では、コントローラーに移動する前に、クライアント側で検証エラーが発生します。サーバーでのラウンドトリップを回避するため、役に立たないわけではありません。しかしもちろん、クライアントの検証だけに頼ることはできません。

フォームがクライアント側ですべてをすぐに検証しないのはなぜですか。外部キー(または選択ボックス)は、空であるため明らかに無効であるにもかかわらず、IsValid()によってのみチェックされるという違いは何ですか?

うーん、私は満足のいく答えが得られていないことを認めなければなりません...それで私はこれをより有能なものに任せました。これらはModelState.IsValidでエラーと見なされます。これは、ClientSide検証に合格すると、ModelBindingに移動するためです(モデルバインディングはPOSTされた値を確認し、対応するHttpPostメソッドの引数を確認し(ユーザーActionResult Createの場合)、バインドを試みます)これらの引数を持つPOST値。あなたの場合、バインディングはActivity activity引数を参照します。また、POSTフィールドでは(たとえば)何も取得しませんStageId。StageIdはnull許容ではないため、ModelStateディクショナリにエラーとして配置します。 =>ModelStateはもう有効ではありません。

Requiredしかし、属性があっても、クライアント側の検証でキャッチされない理由はわかりません。

ユーザーがフォームを2回送信する必要がなく、すべての検証メッセージが一度に表示されるように、すべてを1つのステップ(空のフィールドの場合)で検証するにはどうすればよいですか?クライアント側の検証をオフにする必要がありますか?

クライアントの検証のみを信頼することはできないため、クライアントの検証をオフにする必要があります。ただし、前述のように、クライアントの検証は、サーバーへの無駄なラウンドトリップを回避するために問題ありません。

最後に、検証に[MetadataType(typeof(...)]]を使用している人を見てきましたが、ビューモデルがある場合はなぜそうするのですか、そうでない場合のみですか?

これは、ViewModelを持っていない場合のみですが、Modelクラスで作業します。edmxが変更されるたびにエンティティクラスが(T4で)生成されるため、ModelFirstまたはDatabaseFirstを使用する場合にのみ役立ちます。次に、クラスにカスタムデータアノテーションを配置する場合、各クラス(ファイル)の生成後に手動で元に戻す必要がありますが、これはばかげています。したがって、これ[MetadataType(typeof()]]は、「基本クラスファイル」が再生成された場合でも、クラスに注釈を追加する方法です。

これが少し役立つことを願っています。

ちなみに、検証に興味がある場合は、FluentValidationをご覧ください。これは非常に素晴らしいです...流暢な検証(あなたは推測しましたか?)ライブラリ。

于 2012-10-25T19:20:35.620 に答える
1

必要な検証を取得する理由は、プロパティが値型であるためです(つまり、nullにすることはできません)。nullにすることはできないため、フレームワークでは値を入力する必要があります(そうしないと、奇妙な例外をスローする必要があります)。

この問題はいくつかの方法で現れます。私はこれをスラッシュドットで何度も何度も見ました。なぜそんなに多くの人がこの問題に陥るのかはわかりませんが、それはかなり一般的です。通常、これにより、デフォルトのコンストラクターがスローされないことを示す奇妙な例外が発生しますが、何らかの理由でここでは発生しませんでした。

この問題は、ViewBagを使用し、ViewBagのアイテムにモデルのプロパティと同じ名前を付けることに起因します。ページが送信されると、モデルバインダーは同様の名前のアイテムによって混乱します。

これらを変更して、最後にリストを追加します。

ViewBag.DepartmentList = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerList = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractList = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageList = new SelectList(new List<string>());
ViewBag.StatusList = new SelectList(db.Status
        .Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");

そして、強く型付けされたバージョンのDropDownListForを使用するようにビューを変更します。

@Html.DropDownList(x => x.StageId, ViewBag.StageList, string.Empty)
... and so on

もう1つの注目すべき項目。上記の例では、ある種のグローバルデータコンテキストまたはさらに悪いことにシングルトンを使用していないことを願っています。それは悲惨であり、データの破損を引き起こす可能性があります。

dbがコンストラクターで新しく作成したコントローラーのメンバーである場合、それは問題ありませんが、理想的ではありません。より良いアプローチは、usingステートメントでラップされた各アクションメソッドで新しいコンテキストを作成するか(接続が閉じられてすぐに破棄される)、コントローラーにIDisposableを実装してDisposeを明示的に呼び出すことです。

さらに良いアプローチは、コントローラーでこれを行うのではなく、ビジネスレイヤーで行うことですが、それはあなたがさらに進むまで待つことができます。

于 2012-10-26T03:18:43.270 に答える
0
  1. フィールドがNULL可能でない場合、EFはそれが必須であると考えます。
  2. 原因外部キーはnull許容ではないため、ナビゲーションプロパティも必要です。
  3. すべての検証を一度に取得するには、検証後にコントローラーでエンティティモードに変換されるViewModelを使用する必要があります。mvcでの属性検証の詳細については、こちらをご覧ください。
于 2012-10-25T18:55:23.850 に答える