1

ここで深刻なn00b警告。憐れんでください!

これで、Nerd Dinner MVCチュートリアルを終了し、 NerdDinnerプログラムを一種の大まかなテンプレートとして使用してVB.NETアプリケーションをASP.NETMVCに変換しているところです。

「IsValid/GetRuleViolations()」パターンを使用して、ビジネスルールに違反する無効なユーザー入力または値を識別しています。LINQ to SQLを使用しており、CustomerRepositoryクラスを介してデータベースへの変更を保存しようとすると、検証を実行してアプリケーション例外をスローできる「OnValidate()」フックを利用しています。

とにかく、フォームの値が検証メソッドに到達するまでに、無効な型がすでにデフォルト値または既存の値に変換されていることを除いて、すべてがうまく機能します。(私は整数の「StreetNumber」プロパティを持っていますが、これはDateTimeやその他の非文字列でも問題になると思います。)

ここで、Html.ValidationMessageがStreetNumberフィールドの横に表示されているため、UpdateModel()メソッドが例外をスローし、値を変更していると推測していますが、検証メソッドは元の入力を認識しません。これには2つの問題があります。

  1. Html.ValidationMessageは何かが間違っていることを通知しますが、Html.ValidationSummaryに対応するエントリがありません。無効なキャストまたは何もないよりも良い何かを示す例外メッセージをそこに表示することさえできれば。

  2. Customer部分クラスにある検証メソッドは、元のユーザー入力を認識しないため、問題がエントリの欠落なのか無効なタイプなのかわかりません。検証ロジックを1つの場所で適切に維持し、フォーム値にアクセスする方法がわかりません。

もちろん、ユーザー入力を処理するロジックをビューに書き込むこともできますが、それはMVCで行うべきこととは正反対のようです。

新しい検証パターンが必要ですか、それとも元のフォーム値をモデルクラスに渡して処理する方法はありますか?


CustomerControllerコード

    // POST: /Customers/Edit/[id]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(int id, FormCollection formValues)
    {
        Customer customer = customerRepository.GetCustomer(id);

        try
        {
            UpdateModel(customer);

            customerRepository.Save();

            return RedirectToAction("Details", new { id = customer.AccountID });
        }
        catch
        {
            foreach (var issue in customer.GetRuleViolations())
                ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }
        return View(customer);
    }

4

1 に答える 1

2

これは良い出発点になります:Scott Gu-ASP.NET MVC 2:モデルの検証

しかし、ドメインオブジェクトにデータバインドするのは悪い習慣だと思います。

私の個人的なアプローチは、 DataAnnotations検証属性を散りばめたPOCOプレゼンテーションモデルを使用して検証を行うことです(これにより、後でクライアント側の検証を簡単に接続できます)。独自のValidationAttributesを作成して、データバインディング検証にフックすることもできます。

POCOオブジェクトへのデータバインディング後に有効な場合は、より複雑なビジネス検証を行うサービスを渡します。その検証に合格したら、それを別のサービス(または同じサービス)に渡します。このサービスは、値をドメインオブジェクトに転送し、保存します。

私はLinqtoSQL GetRuleViolationsパターンに精通していないので、自分のステップの1つを適切なパターンに自由に置き換えてください。

ここで説明するように頑張ります。

POCOプレゼンテーションモデル:

public class EditCustomerForm
{
    [DisplayName("First name")]
    [Required(ErrorMessage = "First name is required")]
    [StringLength(60, ErrorMessage = "First name cannot exceed 60 characters.")]
    public string FirstName { get; set; }

    [DisplayName("Last name")]
    [Required(ErrorMessage = "Last name is required")]
    [StringLength(60, ErrorMessage = "Last name cannot exceed 60 characters.")]
    public string LastName { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [RegularExpression(@"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
                       @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                       @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$",
                       ErrorMessage = "Email appears to be invalid.")]
    public string Email { get; set; }
}

コントローラロジック

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    var editCustomerForm = CustomerService.GetEditCustomerForm(id);

    return View(editCustomerForm);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, EditCustomerForm editCustomerForm)
{
    try
    {
        if (Page.IsValid)
        {
            //Complex business validation that validation attributes can't handle
            //If there is an error, I get this method to throw an exception that has
            //the errors in it in the form of a IDictionary<string, string>
            CustomerService.ValidateEditCustomerForm(editCustomerForm, id);

            //If the above method hasn't thrown an exception, we can save it
            //In this method you should map the editCustomerForm back to your Cusomter domain model
            CustomerService.SaveCustomer(editCustomerForm, id)

            //Now we can redirect
            return RedirectToAction("Details", new { id = customer.AccountID });
    }
    //ServiceLayerException is a custom exception thrown by
    //CustomerService.ValidateEditCusotmerForm or possibly .SaveCustomer
    catch (ServiceLayerException ex)
    {
        foreach (var kvp in ex.Errors)
            ModelState.AddModelError(kvp.Key, kvp.Value);
    }
    catch (Exception ex) //General catch
    {
        ModelState.AddModelError("*", "There was an error trying to save the customer, please try again.");
    }

    return View(editCustomerForm);
}

泥のように澄んでいますか?さらに詳しい説明が必要な場合は、お知らせください:-)

繰り返しますが、これは私のアプローチです。私が最初にリンクしたScottGuの記事をたどって、そこから行くこともできます。

HTH、
チャールズ

于 2010-04-21T23:48:35.877 に答える