ASP.NET MVCおよびViewState
-
16-09-2019 - |
質問
今、私はこのようないくつかの質問を見てきましたが、それは私が尋ねたいことではないので、叫ぶすべての叫び声のために、私は謝罪します:)。
ASP.NET MVCにはほとんど触れませんでしたが、私が理解していることから、ViewState/ControlStateはありません...それで、私の質問は、コントロールの状態を保持するための代替手段は何ですか? Old School ASPに戻ります。そこでは、コントロールの状態で隠されたフォーム入力を作成することにより、ASP.NET ViewState/ControlStateが行うことをシミュレートできますか、またはMVCを使用して、Ajaxを常に想定し、すべての状態を維持し、Ajaxを作成しますか更新する呼び出し?
この質問にはいくつかの答えがあります、 ASP.NET MVCでViewStateを維持しますか?, 、しかし、私が答えで探しているものではありません。
更新:これまでのすべての回答に感謝します。私が探していないものと私が探しているものをクリアするためだけに:
探していない:
- セッションソリューション
- クッキーソリューション
- MVCでWebformsを模倣しようとしていません
私が何を探していたのか:
- データがコントロールにリバウンドされていない場合、Postback上の状態を保持する方法。初期ページのロードでグリッドのみをバインドするシナリオでWebFormsを考えてください。私が言ったように、私はWebformsを模倣しようとはしていません。MVCが提供するメカニズムを疑問に思うだけです。
解決
コンベンションは、あまりにも多くのフープを飛び越えることなく、すでに利用可能です。トリックは、ビューに渡すモデルに基づいてテキストボックスの値を配線することです。
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult CreatePost()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(FormCollection formCollection)
{
try
{
// do your logic here
// maybe u want to stop and return the form
return View(formCollection);
}
catch
{
// this will pass the collection back to the ViewEngine
return View(formCollection);
}
}
次に起こるのは、ViewEngineがフォームコレクションを取得し、コレクション内のキーをHTMLヘルパーを使用して、ビューにあるID名/値と一致させることです。例えば:
<div id="content">
<% using (Html.BeginForm()) { %>
Enter the Post Title: <%= Html.TextBox("Title", Model["Title"], 50) %><br />
Enter the Post Body: <%= Html.TextArea("Body", Model["Body"]) %><br />
<%= Html.SubmitButton() %>
<% } %>
</div>
TextboxとTextareaにはタイトルとボディのIDがあることに注意してください。さて、ビューのモデルオブジェクトから値をどのように設定しているかに注目してください。フォームコレクションで合格したため(フォームコレクションで強く入力するようにビューを設定する必要があります)、アクセスできるようになりました。または、強くタイピングすることなく、viewdata ["title"](私は思う)を単に使用することができます。
poof あなたの魔法のビューステート。この概念は、構成に関する慣習と呼ばれます。
これで、上記のコードは、フォームコレクションを使用して、最も単純で生の形式になっています。フォームコレクションではなく、ViewModelを使用し始めると、物事は面白くなります。モデル/ViewModelの独自の検証を追加して、コントローラーをカスタム検証エラーを自動的にバブルアップさせることができます。それは別の日の答えです。
Postオブジェクトの代わりにPostFormViewModelを使用することをお勧めしますが、それぞれが所有しています。いずれにせよ、アクションメソッドにオブジェクトを要求することにより、呼び出すことができるisValid()メソッドを取得するようになりました。
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(Post post)
{
// errors should already be in the collection here
if (false == ModelState.IsValid())
return View(post);
try
{
// do your logic here
// maybe u want to stop and return the form
return View(post);
}
catch
{
// this will pass the collection back to the ViewEngine
return View(post);
}
}
そして、あなたの強いタイプのビューを調整する必要があります:
<div id="content">
<% using (Html.BeginForm()) { %>
Enter the Post Title: <%= Html.TextBox("Title", Model.Title, 50) %><br />
Enter the Post Body: <%= Html.TextArea("Body", Model.Body) %><br />
<%= Html.SubmitButton() %>
<% } %>
</div>
さらに一歩進んで、コントローラーに設定したモデルステートから直接エラーを表示できます。
<div id="content">
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) { %>
Enter the Post Title:
<%= Html.TextBox("Title", Model.Title, 50) %>
<%= Html.ValidationMessage("Title") %><br />
Enter the Post Body:
<%= Html.TextArea("Body", Model.Body) %>
<%= Html.ValidationMessage("Body") %><br />
<%= Html.SubmitButton() %>
<% } %>
</div>
このアプローチで興味深いのは、検証の概要やビュー内の個々の検証メッセージを設定していないことに気付くことです。私はDDDの概念を練習するのが好きです。つまり、私の検証メッセージ(および要約)は私のドメインで制御され、コレクションの形で渡されることを意味します。次に、彼のコレクション(エラーが存在する場合)をループし、現在のModelState.Adderrorsコレクションに追加します。残りはビュー(投稿)を返すときに自動です。
たくさんのコンベンションがあります。これらのパターンをより詳細にカバーすることを強くお勧めします。
そして、その順序で、最初のものはMVCフレームワーク全体の生のナッツとボルトをカバーします。後者は、Microsoftの公式RELM以外の高度なテクニックをカバーしており、いくつかの外部ツールを使用して、あなたの生活をずっと楽にします(Castle Windsor、MOQなど)。
他のヒント
ビューはMVCパターンでは馬鹿げているはずであり、コントローラーが提供するものを表示するだけで(明らかに私たちはそこに何らかのロジックになりますが、前提はそうではありません)。結果として、コントロールは責任を負いません彼らの状態、それは毎回コントローラーから来るでしょう。
Steven SandersonのBook Pro Asp.Net MVCは、このパターンとこの実装を把握するのに十分なほど、Apressによる推奨をお勧めできません。
Web形式では、ViewStateでコントロール値が維持されるため、(理論的に)各ポストバックで再活性化する必要はありません。値は(理論的にも)フレームワークによって維持されます。
ASP.NET MVCでは、パラダイムに従う場合、フォーム要素で状態を維持する必要はありません。フォーム要素の値は、コントローラーがそれらに基づいて行動できるポスト(検証、データベースの更新など)で利用できます。投稿が処理されると表示される任意のフォーム要素については、あなた(開発者)がそれらを初期化する責任があります - フレームワークは自動的にそれを行いません。
とはいえ、私はあなたのコントローラーがリダイレクトに続いて別のコントローラーにデータを渡すことを可能にするTempDataと呼ばれるメカニズムについて読みました。実際にはセッション変数(またはそのように構成する場合はCookie)ですが、次のリクエストの後に自動的にクリーンアップされます。
答えは、実際にあなたが状態を維持しようとしている制御の種類に依存します。基本的なHTMLコントロールの場合、モデルで状態を維持するのは非常に簡単です。これを行うには、強くタイプされたビューを作成する必要があります。
したがって、プロパティを備えたユーザーモデルがある場合:ユーザー名、フルネーム、電子メール、ビューで以下を実行できます。
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) { %>
<fieldset>
<legend>User details</legend>
<%= Html.AntiForgeryToken() %>
<p>
<label for="Username">Username:</label>
<%= Html.Textbox("Username", Model.Username, "*") %>
</p>
<p>
<label for="FullName">FullName:</label>
<%= Html.Textbox("FullName", Model.FullName, "*") %>
</p>
<p>
<label for="Email">Email:</label>
<%= Html.Textbox("Email", Model.Email, "*") %>
</p>
<p>
<input type+"submit" value="Save user" />
</p>
</fieldset>
<% } %>
次に、このビューを表示する2つのコントローラーアクションがあります。1つはGET用、もう1つは投稿用です。
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult User()
{
return View(new User())
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult User([Bind(Include = "Username,FullName,Email")]User user)
{
if (!ModelState.IsValid()) return View(user);
try
{
user.save()
// return the view again or redirect the user to another page
}
catch(Exception e)
{
ViewData["Message"] = e.Message;
return View(user)
}
}
これはあなたが探しているものですか?それとも、リクエスト間のフォームで表示されていないモデルの状態を維持したいですか?
覚えておくべき重要なことは、コードがリクエストの期間中にサーバーで実行されることです。リクエスト間で渡すことができる情報は、基本的なHTMLフォームデータ、URLパラメーター、およびセッション情報です。
他の人が言及したように、MVCフレームワークの操作を完全に理解するために、Steve SandersanのPro ASP.NET MVCフレームワークを強くお勧めします。
のような隠されたフィールド:
<% using (Html.BeginForm<SomeController>(c=>c.SomeAction(null))) {%> <%= Html.Hidden("SomeField", Model.SomeField)%> <%= Html.Hidden("AnotherField", Model.AnotherField)%>
特定のモデルを設定し、明示的なフィールドを持っていません(uhiddenフィールドが与えられます)。以下の例では、モデルはコントローラーによって最後の投稿から受信された値で満たされているため、これにより、ステータスに基づいてフィルタリングできるJSオプションがあります。
Some Filter: <% using( Html.BeginForm<SomeController>( c => c.SomeAction(model.SomeField, model.AnotherField, model.YetAnotherField, null, model.SomeOtherField) )) { %> <%= Html.DropDownList("status", Model.StatusSelectList)%> <input type="submit" value="Filter" class="button" /> <% } %>
- 拡張メソッドを使用してフィールドを作成します。送信フォームに失敗した検証メッセージを表示しているときに、フィールドを投稿値で満たしたい場合は、フィールドを作成します。
- ASP.NET MVC 2で、彼らは隠されたフィールドにインスタンスを保存する方法を導入しました...エンコード +(私は思う)署名
- 上記のすべてがそれをしない場合はtempdata(セッションを通過 - 次のリクエストでクリーニングされます)
- あなたが述べたように、AJAXを使用する場合、状態はすでにクライアントサイトの以前にロードされたフィールドにあります。完全な投稿を行う必要がある場合は、JSで必要なフィールドを更新してください。
上記はすべて、さまざまなシナリオで使用できるさまざまな独立したオプションです。 IE Cookie、セッション、DBに保管されているものについては言及していません(再開可能なマルチステップウィザードなど)、アクションに渡されたパラメーターについては言及しませんでした。それらをすべて支配するための1つの単一のメカニズムはありません、そして、あるべきではありません。
これを行う最良の方法は、元のモデルを隠されたフィールドにシリアル化し、それを脱上化してポストでモデルを更新することだと思います。これは、ViewStateアプローチとやや同様です。自分だけで実装する必要があります。私はこれを使用します:
最初に、物事を簡単にするいくつかの方法が必要です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using LuvDaSun.Extensions;
using System.Web.UI;
namespace LuvDaSun.Web.Mvc
{
public static class HtmlHelperExtensions
{
static LosFormatter _losFormatter = new LosFormatter();
public static string Serialize(this HtmlHelper helper, object objectInstance)
{
var sb = new StringBuilder();
using (var writer = new System.IO.StringWriter(sb))
{
_losFormatter.Serialize(writer, objectInstance);
}
return sb.ToString();
}
}
[AttributeUsage(AttributeTargets.Parameter)]
public class DeserializeAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new DeserializeModelBinder();
}
}
public class DeserializeModelBinder : IModelBinder
{
static LosFormatter _losFormatter = new LosFormatter();
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.IsArray)
{
var type = bindingContext.ModelType.GetElementType();
var serializedObjects = (string[])bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string[]));
var deserializedObjects = Array.CreateInstance(bindingContext.ModelType.GetElementType(), serializedObjects.Length);
for (var index = 0; index < serializedObjects.Length; index++)
{
var serializedObject = serializedObjects[index];
var deserializedObject = _losFormatter.Deserialize(serializedObject);
deserializedObjects.SetValue(deserializedObject, index);
}
return deserializedObjects;
}
else
{
var serializedObject = (string)bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string));
var deserializedObject = _losFormatter.Deserialize(serializedObject);
return deserializedObject;
}
}
}
}
それから私のコントローラーには、このようなものがあります(製品を更新するため)
public ActionResult Update(string productKey)
{
var model = _shopping.RetrieveProduct(productKey);
return View(model);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Update([Deserialize]Shopping.IProduct _model, FormCollection collection)
{
UpdateModel(model);
model.Save();
return RedirectAfterPost();
}
そして、シリアル化されたオブジェクトをフォームに保持する隠されたフィールドが必要です。
<%
using (Html.BeginRouteForm("Product", FormMethod.Post, new { id = UniqueID, }))
{
%>
<%= Html.Hidden("Model", Html.Serialize(Model)) %>
<h1>
Product bewerken</h1>
<p>
<label for="<%=UniqueID %>_Name">
Naam:</label>
<input id="<%=UniqueID %>_Name" name="Name" type="text" value="<%= Html.AttributeEncode(Model.Name) %>"
class="required" />
<br />
</p>
<p>
Omschrijving:<br />
<textarea id="<%= UniqueID %>_Description" name="Description" cols="40" rows="8"><%= Html.Encode(Model.Description) %></textarea>
<br />
</p>
<p>
<label for="<%=UniqueID %>_Price">
Prijs:</label>
<input id="<%= UniqueID %>_Price" name="Price" type="text" value="<%= Model.Price.ToString("0.00") %>"
class="required" />
<br />
</p>
<ul class="Commands">
<li><a href="" class="ClosePopup">Annuleren</a></li>
<li>
<input type="submit" value="Opslaan" /></li>
</ul>
<%
}
%>
<script type="text/javascript">
jQuery('#<%= UniqueID %>').validate();
</script>
ご覧のとおり、隠されたフィールド(モデル)がフォームに追加されます。元のオブジェクトのシリアル化情報が含まれています。フォームが投稿されると、隠されたフィールドも(頻繁に)投稿され、コンテンツはカスタムモデルバインダーによって元のオブジェクトに偏りがあり、コントローラーによって更新および保存されます。
シリアル化しているオブジェクトは、シリアル化可能な属性で装飾する必要があるか、オブジェクトを文字列に変換できるTypeConverterが必要であることに注意してください。
Losformatter(限られたオブジェクトのシリアル化)は、WebFormsのViewStateで使用されます。また、シリアル化データのencryptionnを提供します。
挨拶...
ajaxの呼び出しは私たちがしていることです。グリッド全般について話している場合は、チェックしてください jqgrid そして、彼らがAJAXの実装をどのように推奨するか。