4

ASP.NET MVC でビューとアクションを管理しやすいチャンクに分割する方法を理解するのに少し苦労しています。検索を試みましたが、まだ賢明ではありません。

この特定の側面を理解しようとするために、同じページのログイン フォームと登録フォームの例を使用して状況を理解しようとする小さなテスト プロジェクトを作成しました。これに対する私のビューモデルは以下のようになります:

public class LoginOrRegisterModel
{
    public LoginModel Login { get; set; }
    public RegisterModel Register { get; set; }

    public LoginOrRegisterModel()
    {
        this.Login = new LoginModel();
        this.Register = new RegisterModel();
    }
}

public class LoginModel
{
    [Required]
    public string UserName { get; set; }
    [Required]
    public string Password { get; set; }
}

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }
    [Required]
    public string Password { get; set; }
}

次に、メインのアクションについて考え始めました。

    public ActionResult Index()
    {
        return View(new LoginOrRegisterModel());
    }

...そして表示...

@model MvcSandbox.Models.LoginOrRegisterModel
@{
    ViewBag.Title = "Index";
}

<h2>Login Or Register</h2>

@Html.Partial("Login", model: Model.Login)
@Html.Partial("Register", model: Model.Register)

...部分的なビューで...

@model MvcSandbox.Models.LoginModel
@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<h2>Register</h2>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

LoginOrRegisterModel のプロパティが null でないことを確認する必要があることがわかりました。それ以外の場合はエラーが発生しました: The model item passed into the dictionary is of type 'MvcSandbox.Models.LoginOrRegisterModel', but this dictionary requires model item of type 「MvcSandbox.Models.LoginModel」。

ここまでは問題ありませんが、どちらのフォームも同じフィールド名と ID を持ち、何もしないインデックス ページにポスト バックするため、現時点ではあまり役に立ちません。

HTML ソース:

<h2>Login Or Register</h2>

<h2>Login</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
    <button>Login</button>
</form>

<h2>Register</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
    <button>Register</button>
</form>

とにかく、投稿ごとにロジックを分けておきたかったので、私がやろうとしたことは、各フォーム投稿を異なるアクションにすることでした。そして、これは私がひどく間違っていると思うところです。

基本的に、検証が失敗した場合、ページをロードするときにモデルの状態を実際にバックアップして構築するために何かをする必要があると考えましたが、以下にあるものにたどり着き、どのようなアプローチをとるべきかについて迷っています代わりは。

public class MembershipController : Controller
{
    //
    // GET: /Membership/

    public ActionResult Index()
    {
        if (TempData["ModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);

        return View(new LoginOrRegisterModel());
    }

    [HttpPost]
    public ActionResult Login(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["ModelState"] = ModelState;

        return RedirectToAction("Index");
    }

    [HttpPost]
    public ActionResult Register(RegisterModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["ModelState"] = ModelState;

        return RedirectToAction("Index");
    }
}

...景色とともに...

@model MvcSandbox.Models.LoginModel
@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<h2>Register</h2>
@using (Html.BeginForm("Register", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

問題は、各モデルに同じ名前のフィールドがあり、どちらが送信されたかに関係なく両方のフォームに検証が表示され、HtmlFieldPrefix を使用しようとしたときに検証がまったく得られないように見えるためです。

モデルの状態と検証について頭を悩ませることなく、アクションとビューを管理および保守可能なチャンクに分割する方法についてのアドバイスをいただければ幸いです。

アップデート:

以下のコードを改善するように見える部分的なアクションを使用するように、アプローチを少し変更しました。

    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Login()
    {
        if (TempData["LoginModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["LoginModelState"]);

        return View(new LoginModel());
    }

    [HttpPost]
    public ActionResult Login(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["LoginModelState"] = ModelState;

        return RedirectToAction("Index");
    }

    public ActionResult Register()
    {
        if (TempData["RegisterModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["RegisterModelState"]);

        return View(new RegisterModel());
    }

    [HttpPost]
    public ActionResult Register(RegisterModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["RegisterModelState"] = ModelState;

        return RedirectToAction("Index");
    }

私のインデックスビューは次のとおりです。

@{
    ViewBag.Title = "Index";
}

<h2>Login Or Register</h2>

@Html.Action("Login")
@Html.Action("Register")

ログインして登録します。

@model MvcSandbox.Models.LoginModel

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel

<h2>Register</h2>
@using (Html.BeginForm("Register", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

今私にとって、これは以前のアプローチよりも利点があり、実際にはもう少しうまくいくように見え、LoginOrRegisterModel の非常に率直に言って恐ろしく厄介なアイデアの必要性を取り除きます。物事がより複雑になり、UI がリファクタリングされ、モデルやコード、ビューだけでなく、潜在的に多くのリファクタリングが行われるようになりました。

デフォルトのモデルバインダーを置き換えて、記述子に基づいたある種のモデルバインディングを持ち、コントローラーアクションをある種のコマンドプロセッサーとして機能させて、投稿されたパーシャルに基づいて正しいハンドラーを起動するような印象を受けます以下のMystere Manが述べているように、リダイレクトに起因する更新の問題を解決してください。

4

2 に答える 2

3

モデルの状態が失われるため、リダイレクトは使用しないでください。モデルの状態を TempData に渡すことでそれを修正しようとしていることは知っていますが、問題は、TempData がその後の 1 回のアクセスに対してのみ有効であることです。ユーザーが F5 キーを押すか更新ボタンを押すと、モデルの状態が失われ、事態はさらに混乱します。

一般に、TempData は、アラートやメッセージをユーザーに 1 回表示する場合などにのみ使用します。

このような部分ビューを使用することは、特に子モデルを別のフォームに投稿しようとする場合に、常に面倒です。私が行う方法は、Partials の代わりに EditorTemplates を使用し、複合ビュー モデルを両方のメソッドに投稿することです。

public ActionResult Login(LoginOrRegisterModel model)
{
   if (ModelState.IsValid) {
      //  access only the Login properties, do same for Register
   }

   return View("LoginOrRegister", model)
}

あなたの見解では

...
@Html.EditorFor(m => m.LoginModel)
@Html.EditorFor(m => m.RegisterModel)

~/Views/Membership/EditorTemplates/LoginModel.cshtml (および RegisterModel.cshtml) 内

@model MvcSandbox.Models.LoginModel
// Not sure why you were setting the title in a partial view, 
// particularly when you had two of them on a single page

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

これの利点は、親モデルを正しい子モデルに正しくバインドし、その時点から必要なものにアクセスできることです。

于 2013-05-12T19:04:24.360 に答える
0

確か、単一の「ページ」に複数のモデルとパーシャルを配置し、個別のアクションに投稿できます。これは、このシナリオで使用したアプローチです。また、これを「ポップアップ」ダイアログで使用しましたが、正常に動作します。

モデルに関しては、基本的に「親」ビューに複合モデル(LoginOrRegisterModel)が必要であり、次に「子」ビューで同じモデルを使用する(それぞれに必要なビットを使用するだけ)か、別の子モデルを次のように使用する必要があります親モデル (LoginModel、RegisterModel) からのプロパティ。これらは両方とも機能する有効なアプローチですが、2 番目のオプション (2 つの異なる子モデル) を使用すると、より適切に分離できると思います。

投稿に関しては、AJAX を使用して個別のフォーム投稿を実行し、エラーが発生した場合にコントローラーの個々の投稿アクションから部分ビューを返します。リダイレクトを使用してページ全体を再レンダリングしようとはしません。これは、既に見つかった種類の問題のためです。

于 2013-05-12T12:41:45.000 に答える