119

Asp.net MVC での ModelState の役割の簡潔な定義 (またはそのリンク) を教えてください。特に、どのような状況で を呼び出す必要があるか、または望ましいかを知る必要がありますModelState.Clear()

少しオープンに終わりました...申し訳ありませんが、私が実際に何をしているのか教えていただけると助かります:

「ページ」と呼ばれるコントローラーに編集アクションがあります。ページの詳細を変更するフォームを最初に表示すると、すべてが正常に読み込まれます (「MyCmsPage」オブジェクトにバインドされます)。次に、MyCmsPage オブジェクトのいずれかのフィールド ( MyCmsPage.SeoTitle) の値を生成するボタンをクリックします。それはうまく生成され、オブジェクトを更新し、新しく変更されたページオブジェクトでアクション結果を返し、関連するテキストボックス (を使用してレンダリング<%= Html.TextBox("seoTitle", page.SeoTitle)%>) が更新されることを期待します...しかし、残念ながら、ロードされた古いモデルからの値が表示されます。

を使用して回避しましたModelState.Clear()が、なぜ/どのように機能したかを知る必要があるため、盲目的にやっているだけではありません。

ページコントローラー:

[AcceptVerbs("POST")]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
    // add the seoTitle to the current page object
    page.GenerateSeoTitle();

    // why must I do this?
    ModelState.Clear();

    // return the modified page object
     return View(page);
 }

Aspx:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyCmsPage>" %>
....
        <div class="c">
            <label for="seoTitle">
                Seo Title</label>
            <%= Html.TextBox("seoTitle", page.SeoTitle)%>
            <input type="submit" value="Generate Seo Title" name="submitButton" />
        </div>
4

10 に答える 10

140

MVCのバグだと思います。今日、私はこの問題に何時間も苦労しました。

これを考えると:

public ViewResult SomeAction(SomeModel model) 
{
    model.SomeString = "some value";
    return View(model); 
}

ビューは元のモデルでレンダリングされ、変更は無視されます。だから私は、同じモデルを使用するのが好きではないかもしれないと思ったので、次のように試しました:

public ViewResult SomeAction(SomeModel model) 
{
    var newModel = new SomeModel { SomeString = "some value" };
    return View(newModel); 
}

それでもビューは元のモデルでレンダリングされます。奇妙なことに、ビューにブレークポイントを設定してモデルを調べると、値が変更されています。しかし、応答ストリームには古い値が含まれています。

最終的に、私はあなたがしたのと同じ回避策を発見しました:

public ViewResult SomeAction(SomeModel model) 
{
    var newModel = new SomeModel { SomeString = "some value" };
    ModelState.Clear();
    return View(newModel); 
}

期待どおりに動作します。

これは「機能」ではないと思いますよね?

于 2010-04-20T21:54:50.703 に答える
49

アップデート:

  • これはバグではありません。
  • View()POST アクションから戻るのはやめてください。代わりにPRGを使用し、アクションが成功した場合は GET にリダイレクトします。
  • POST アクションから を返す場合View()、フォームの検証のためにそれを行い、組み込みのヘルパーを使用してMVC が設計されている方法でそれを行います。このようにすれば、使用する必要はありません.Clear()
  • このアクションを使用してSPAModelStateの ajax を返す場合は、Web API コントローラーを使用し、いずれにしても使用すべきではないため忘れてください。

古い答え:

MVC の ModelState は、主にモデル オブジェクトの状態を、主にそのオブジェクトが有効かどうかに関連して記述するために使用されます。このチュートリアルでは、多くのことを説明する必要があります。

通常、ModelState は MVC エンジンによって維持されるため、クリアする必要はありません。手動でクリアすると、MVC 検証のベスト プラクティスに従おうとするときに、望ましくない結果が生じる可能性があります。

タイトルのデフォルト値を設定しようとしているようです。これは、モデルオブジェクトがインスタンス化されたときに(どこかまたはオブジェクト自体のドメインレイヤー-パラメーターなしのctor)、getアクションで、最初にページに移動するか、クライアント上で完全に(ajaxなどを介して)実行する必要があります。ユーザーが入力したかのように表示され、投稿されたフォーム コレクションが返されます。.Clear() フォーム コレクションの受信時にこの値を追加するというアプローチ (POST アクション // 編集) が、この奇妙な動作を引き起こしている可能性があります。私を信じてください-あなたはクリアを使いたくありません. 他のアイデアの 1 つを試してください。

于 2009-11-21T11:11:21.297 に答える
17

個々のフィールドの値をクリアしたい場合は、次の手法が役立つことがわかりました。

ModelState.SetModelValue("Key", new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture));

注: 「キー」をリセットするフィールドの名前に変更します。

于 2010-03-19T00:35:53.803 に答える
6

ModelStateは基本的に、検証の観点からモデルの現在の状態を保持します。

ModelErrorCollection:モデルが値をバインドしようとしたときのエラーを表します。元。

TryUpdateModel();
UpdateModel();

またはActionResultのパラメータのように

public ActionResult Create(Person person)

ValueProviderResult:モデルへのバインドの試行に関する詳細を保持します。元。AttemptedValue、Culture、RawValue

Clear()メソッドは、予期しない結果につながる可能性があるため、注意して使用する必要があります。また、AttemptedValueなどのModelStateの優れたプロパティが失われます。これは、エラーが発生した場合にフォーム値を再入力するためにバックグラウンドでMVCによって使用されます。

ModelState["a"].Value.AttemptedValue
于 2009-11-21T15:57:13.307 に答える
6

送信されたフォームのモデルを更新したいインスタンスがありましたが、パフォーマンス上の理由から「アクションにリダイレクト」したくありませんでした。非表示フィールドの以前の値が更新されたモデルに保持されていたため、さまざまな問題が発生していました!.

数行のコードによって、(検証後に) 削除したい ModelState 内の要素がすぐに特定されたため、新しい値が次の形式で使用されました。

while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
    ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}
于 2010-10-29T11:01:06.927 に答える
0

一般に、フレームワークの標準的な慣行と戦っていることに気付いたときは、アプローチを再考するときです。この場合、ModelState の動作。たとえば、POST 後にモデルの状態が必要ない場合は、get へのリダイレクトを検討してください。

[HttpPost]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
    if (ModelState.IsValid) {
        SomeRepository.SaveChanges(page);
        return RedirectToAction("GenerateSeoTitle",new { page.Id });
    }
    return View(page);
}

public ActionResult GenerateSeoTitle(int id) {
     var page = SomeRepository.Find(id);
     page.GenerateSeoTitle();
     return View("Edit",page);
}

文化のコメントに答えるために編集されました:

これは、多文化 MVC アプリケーションを処理するために使用するものです。まず、ルート ハンドラーのサブクラス:

public class SingleCultureMvcRouteHandler : MvcRouteHandler {
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var culture = requestContext.RouteData.Values["culture"].ToString();
        if (string.IsNullOrWhiteSpace(culture))
        {
            culture = "en";
        }
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
        return base.GetHttpHandler(requestContext);
    }
}

public class MultiCultureMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var culture = requestContext.RouteData.Values["culture"].ToString();
        if (string.IsNullOrWhiteSpace(culture))
        {
            culture = "en";
        }
        var ci = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
        return base.GetHttpHandler(requestContext);
    }
}

public class CultureConstraint : IRouteConstraint
{
    private string[] _values;
    public CultureConstraint(params string[] values)
    {
        this._values = values;
    }

    public bool Match(HttpContextBase httpContext,Route route,string parameterName,
                        RouteValueDictionary values, RouteDirection routeDirection)
    {

        // Get the value called "parameterName" from the 
        // RouteValueDictionary called "value"
        string value = values[parameterName].ToString();
        // Return true is the list of allowed values contains 
        // this value.
        return _values.Contains(value);

    }

}

public enum Culture
{
    es = 2,
    en = 1
}

そして、これがルートを配線する方法です。ルートを作成した後、サブエージェント (example.com/subagent1、example.com/subagent2 など) の前にカルチャ コードを追加します。カルチャだけが必要な場合は、ルート ハンドラとルートからサブエージェントを削除するだけです。

    public static void RegisterRoutes(RouteCollection routes)
    {

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.IgnoreRoute("Content/{*pathInfo}");
        routes.IgnoreRoute("Cache/{*pathInfo}");
        routes.IgnoreRoute("Scripts/{pathInfo}.js");
        routes.IgnoreRoute("favicon.ico");
        routes.IgnoreRoute("apple-touch-icon.png");
        routes.IgnoreRoute("apple-touch-icon-precomposed.png");

        /* Dynamically generated robots.txt */
        routes.MapRoute(
            "Robots.txt", "robots.txt",
            new { controller = "Robots", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
             "Sitemap", // Route name
             "{subagent}/sitemap.xml", // URL with parameters
             new { subagent = "aq", controller = "Default", action = "Sitemap"},  new[] { "aq3.Controllers" } // Parameter defaults
        );

        routes.MapRoute(
             "Rss Feed", // Route name
             "{subagent}/rss", // URL with parameters
             new { subagent = "aq", controller = "Default", action = "RSS"},  new[] { "aq3.Controllers" } // Parameter defaults
        );

        /* remap wordpress tags to mvc blog posts */
        routes.MapRoute(
            "Tag", "tag/{title}",
            new { subagent = "aq", controller = "Default", action = "ThreeOhOne", id = UrlParameter.Optional},  new[] { "aq3.Controllers" }
        ).RouteHandler = new MultiCultureMvcRouteHandler(); ;

        routes.MapRoute(
            "Custom Errors", "Error/{*errorType}",
            new { controller = "Error", action = "Index", id = UrlParameter.Optional},  new[] { "aq3.Controllers" }
        );

        /* dynamic images not loaded from content folder */
        routes.MapRoute(
            "Stock Images",
            "{subagent}/Images/{*filename}",
            new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional, culture = "en"},  new[] { "aq3.Controllers" }
        );

        /* localized routes follow */
        routes.MapRoute(
            "Localized Images",
            "Images/{*filename}",
            new { subagent = "aq", controller = "Image", action = "Show", id = UrlParameter.Optional},  new[] { "aq3.Controllers" }
        ).RouteHandler = new MultiCultureMvcRouteHandler();

        routes.MapRoute(
            "Blog Posts",
            "Blog/{*postname}",
            new { subagent = "aq", controller = "Blog", action = "Index", id = UrlParameter.Optional},  new[] { "aq3.Controllers" }
        ).RouteHandler = new MultiCultureMvcRouteHandler();

        routes.MapRoute(
            "Office Posts",
            "Office/{*address}",
            new { subagent = "aq", controller = "Offices", action = "Address", id = UrlParameter.Optional }, new[] { "aq3.Controllers" }
        ).RouteHandler = new MultiCultureMvcRouteHandler();

        routes.MapRoute(
             "Default", // Route name
             "{controller}/{action}/{id}", // URL with parameters
             new { subagent = "aq", controller = "Home", action = "Index", id = UrlParameter.Optional }, new[] { "aq3.Controllers" } // Parameter defaults
        ).RouteHandler = new MultiCultureMvcRouteHandler();

        foreach (System.Web.Routing.Route r in routes)
        {
            if (r.RouteHandler is MultiCultureMvcRouteHandler)
            {
                r.Url = "{subagent}/{culture}/" + r.Url;
                //Adding default culture 
                if (r.Defaults == null)
                {
                    r.Defaults = new RouteValueDictionary();
                }
                r.Defaults.Add("culture", Culture.en.ToString());

                //Adding constraint for culture param
                if (r.Constraints == null)
                {
                    r.Constraints = new RouteValueDictionary();
                }
                r.Constraints.Add("culture", new CultureConstraint(Culture.en.ToString(), Culture.es.ToString()));
            }
        }

    }
于 2015-02-18T15:50:30.137 に答える
0

最後にそれを手に入れました。登録されていなかった私のカスタムModelBinderはこれを行います:

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

myCmsPage.SeoTitle = (!String.IsNullOrEmpty(frm["seoTitle"])) ? frm["seoTitle"] : null;

したがって、デフォルトのモデル バインディングが行っていた何かが問題を引き起こしているに違いありません。よくわかりませんが、カスタム モデル バインダーが登録されたので、少なくとも問題は解決しました。

于 2009-11-22T19:53:35.910 に答える