asp.net-mvc Asp.net MVC ModelState.Clear

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1775170/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-07 23:57:38  来源:igfitidea点击:

Asp.net MVC ModelState.Clear

asp.net-mvcmodelstatepost-redirect-get

提问by Mr Grok

Can anyone give me a succinct definition of the role of ModelState in Asp.net MVC (or a link to one). In particular I need to know in what situations it is necessary or desirable to call ModelState.Clear().

谁能给我一个关于 ModelState 在 Asp.net MVC 中的角色的简洁定义(或一个链接)。我特别需要知道在什么情况下需要或需要调用ModelState.Clear().

Bit open ended huh... sorry, I think it might help if tell you what I'm acutally doing:

有点开放……抱歉,我认为如果告诉您我在做什么可能会有所帮助:

I have an Action of Edit on a Controller called "Page". When I first see the form to change the Page's details everything loads up fine (binding to a "MyCmsPage" object). Then I click a button that generates a value for one of the MyCmsPage object's fields (MyCmsPage.SeoTitle). It generates fine and updates the object and I then return the action result with the newly modified page object and expect the relevant textbox (rendered using <%= Html.TextBox("seoTitle", page.SeoTitle)%>) to be updated ... but alas it displays the value from the old model that was loaded.

我在名为“页面”的控制器上有一个编辑操作。当我第一次看到更改页面详细信息的表单时,一切都会正常加载(绑定到“MyCmsPage”对象)。然后我单击一个按钮,该按钮为 MyCmsPage 对象的一个​​字段 ( MyCmsPage.SeoTitle)生成一个值。它生成良好并更新对象,然后我使用新修改的页面对象返回操作结果,并期望<%= Html.TextBox("seoTitle", page.SeoTitle)%>更新相关的文本框(使用 呈现)……但可惜它显示了来自加载的旧模型的值。

I've worked around it by using ModelState.Clear()but I need to know why / how it has worked so I'm not just doing it blindly.

我已经通过使用解决了它,ModelState.Clear()但我需要知道它为什么/如何工作,所以我不是盲目地这样做。

PageController:

页面控制器:

[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:

ASP:

<%@ 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>

回答by Tim Scott

I think is a bug in MVC. I struggled with this issue for hours today.

我认为是MVC中的一个错误。我今天在这个问题上挣扎了几个小时。

Given this:

鉴于这种:

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

The view renders with the original model, ignoring the changes. So I thought, maybe it does not like me using the same model, so I tried like this:

视图使用原始模型渲染,忽略更改。所以我想,也许它不喜欢我使用相同的模型,所以我是这样尝试的:

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

And still the view renders with the original model. What's odd is, when I put a breakpoint in the view and examine the model, it has the changed value. But the response stream has the old values.

并且视图仍然使用原始模型呈现。奇怪的是,当我在视图中放置断点并检查模型时,它的值发生了变化。但是响应流具有旧值。

Eventually I discovered the same work around that you did:

最终我发现了你所做的同样的工作:

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

Works as expected.

按预期工作。

I don't think this is a "feature," is it?

我不认为这是一个“功能”,是吗?

回答by Matt Kocaj

Update:

更新:

  • This is not a bug.
  • Please stop returning View()from a POST action. Use PRGinstead and redirect to a GET if the action is a success.
  • If you arereturning a View()from a POST action, do it for form validation, and do it the way MVC is designedusing the built in helpers. If you do it this way then you shouldn't need to use .Clear()
  • If you're using this action to return ajax for a SPA, use a web api controller and forget about ModelStatesince you shouldn't be using it anyway.
  • 这不是一个错误。
  • 请停止View()从 POST 操作返回。如果操作成功,请改用PRG并重定向到 GET。
  • 如果您返回View()从POST动作,连续做表单验证,并做到这一点的方式MVC设计使用内置的助手。如果你这样做,那么你不应该需要使用.Clear()
  • 如果您使用此操作为SPA返回 ajax ,请使用 web api 控制器并忘记,ModelState因为无论如何您都不应该使用它。

Old answer:

旧答案:

ModelState in MVC is used primarily to describe the state of a model object largely with relation to whether that object is valid or not. This tutorialshould explain a lot.

MVC 中的 ModelState 主要用于描述模型对象的状态,主要与该对象是否有效有关。本教程应该解释很多。

Generally you should not need to clear the ModelState as it is maintained by the MVC engine for you. Clearing it manually might cause undesired results when trying to adhere to MVC validation best practises.

通常您不需要清除 ModelState,因为它是由 MVC 引擎为您维护的。在尝试遵守 MVC 验证最佳实践时,手动清除它可能会导致不希望的结果。

It seems that you are trying to set a default value for the title. This should be done when the model object is instantiated (domain layer somewhere or in the object itself - parameterless ctor), on the get action such that it goes down to the page the 1st time or completely on the client (via ajax or something) so that it appears as if the user entered it and it comes back with the posted forms collection. Some how your approach of adding this value on the receiving of a forms collection (in the POST action // Edit) is causing this bizarre behaviour that might result in a .Clear()appearingto work for you. Trust me - you don't want to be using the clear. Try one of the other ideas.

您似乎正在尝试为标题设置默认值。这应该在实例化模型对象(某处或对象本身的域层 - 无参数构造函数)时完成,在 get 操作中,以便它第一次进入页面或完全在客户端(通过 ajax 或其他方式)这样它看起来就好像用户输入了它并且它与发布的表单集合一起返回。您在接收表单集合时添加此值的方法(在 POST 操作 // 编辑中)如何导致这种奇怪的行为,这可能会导致.Clear()看起来对您有用。相信我 - 你不想使用明文。尝试其他想法之一。

回答by Carl Saunders

If you want to clear a value for an individual field then I found the following technique useful.

如果您想清除单个字段的值,那么我发现以下技术很有用。

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

Note:Change "Key" to the name of the field that you want to reset.

注意:将“Key”更改为要重置的字段的名称。

回答by JOBG

Well the ModelState basically holds the current State of the model in terms of validation, it holds

那么 ModelState 基本上持有模型在验证方面的当前状态,它持有

ModelErrorCollection:Represent the errors when the model try to bind the values. ex.

ModelErrorCollection:表示模型尝试绑定值时的错误。前任。

TryUpdateModel();
UpdateModel();

or like a parameter in the ActionResult

或像 ActionResult 中的参数

public ActionResult Create(Person person)

ValueProviderResult: Hold the details about the attempted bind to the model. ex. AttemptedValue, Culture, RawValue.

ValueProviderResult:保存有关尝试绑定到模型的详细信息。前任。AttemptedValue、文化、RawValue

Clear() method must be use with caution because it can lead to unspected results. And you will lose some nice properties of the ModelState like AttemptedValue, this is used by MVC in the background to repopulate the form values in case of error.

Clear() 方法必须谨慎使用,因为它可能会导致无法预料的结果。并且您将丢失 ModelState 的一些不错的属性,例如 AttemptedValue,MVC 在后台使用它来重新填充表单值以防万一。

ModelState["a"].Value.AttemptedValue

回答by stevieg

I had an instance where I wanted to update the model of a sumitted form, and did not want to 'Redirect To Action' for performanace reason. Previous values of hidden fields were being retained on my updated model - causing allsorts of issues!.

我有一个实例,我想更新提交表单的模型,但出于性能原因不想“重定向到操作”。隐藏字段的先前值保留在我更新的模型中 - 导致各种问题!。

A few lines of code soon identified the elements within ModelState that I wanted to remove (after validation), so the new values were used in the form:-

几行代码很快就确定了我想要删除的 ModelState 中的元素(验证后),因此新值用于以下形式:-

while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
    ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}

回答by Tobias J

Well lots of us seem to have been bitten by this, and although the reason this happens makes sense I needed a way to ensure that the value on my Model was shown, and not ModelState.

好吧,我们中的很多人似乎都被这个问题所困扰,尽管发生这种情况的原因是有道理的,但我需要一种方法来确保显示我的模型上的值,而不是 ModelState。

Some have suggested ModelState.Remove(string key), but it's not obvious what keyshould be, especially for nested models. Here are a couple methods I came up with to assist with this.

有些人建议ModelState.Remove(string key),但不清楚key应该是什么,特别是对于嵌套模型。这是我想出的几种方法来帮助解决这个问题。

The RemoveStateFormethod will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it. HiddenForModelcan be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).

RemoveStateFor方法将采用ModelStateDictionary、模型和所需属性的表达式,并将其删除。HiddenForModel可以在您的视图中使用,通过首先删除其 ModelState 条目,仅使用模型中的值创建隐藏输入字段。(这可以很容易地扩展到其他辅助扩展方法)。

/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
    Expression<Func<TModel, TProperty>> expression)
{
    RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
    return helper.HiddenFor(expression);
}

/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
    Expression<Func<TModel, TProperty>> expression)
{
    var key = ExpressionHelper.GetExpressionText(expression);

    modelState.Remove(key);
}

Call from a controller like this:

从这样的控制器调用:

ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);

or from a view like this:

或者从这样的角度来看:

@Html.HiddenForModel(m => m.MySubProperty.MySubValue)

It uses System.Web.Mvc.ExpressionHelperto get the name of the ModelState property.

它用于System.Web.Mvc.ExpressionHelper获取 ModelState 属性的名称。

回答by Gerard ONeill

I wanted to update or reset a value if it didn't quite validate, and ran into this problem.

如果一个值没有完全验证,我想更新或重置它,并遇到了这个问题。

The easy answer, ModelState.Remove, is.. problematic.. because if you are using helpers you don't really know the name (unless you stick by the naming convention). Unless perhaps you create a function that both your customhelper and your controller can use to get a name.

简单的答案 ModelState.Remove 是.. 有问题的.. 因为如果您使用助手,您实际上并不知道名称(除非您遵守命名约定)。除非您创建了一个函数,您的自定义助手和控制器都可以使用该函数来获取名称。

This feature should have been implemented as an option on the helper, where by default is does notdo this, but if you wanted the unaccepted input to redisplay you could just say so.

这个特性应该作为帮助器的一个选项来实现,默认情况下是不这样做的,但是如果你想重新显示未被接受的输入,你可以这么说。

But at least I understand the issue now ;).

但至少我现在明白这个问题;)。

回答by B2K

Generally, when you find yourself fighting against a framework standard practices, it is time to reconsider your approach. In this case, the behavior of ModelState. For instance, when you don't want model state after a POST, consider a redirect to the get.

通常,当您发现自己与框架标准实践作斗争时,是时候重新考虑您的方法了。在这种情况下,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);
}

EDITED to answer culture comment:

编辑回答文化评论:

Here is what I use to handle a multi-cultural MVC application. First the route handler subclasses:

这是我用来处理多文化 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
}

And here is how I wire up the routes. After creating the routes, I prepend my subagent (example.com/subagent1, example.com/subagent2, etc) then the culture code. If all you need is the culture, simply remove the subagent from the route handlers and routes.

这是我连接路线的方式。创建路由后,我将我的子代理(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()));
            }
        }

    }

回答by Mr Grok

Got it in the end. My Custom ModelBinder which was not being registered and does this :

最后得到了。我的自定义 ModelBinder 未注册并执行以下操作:

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

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

So something that the default model binding was doing must have been causing the problem. Not sure what, but my problem is at least fixed now that my custom model binder is being registered.

因此,默认模型绑定所做的某些事情一定是导致问题的原因。不确定是什么,但现在我的自定义模型绑定器正在注册,我的问题至少得到了解决。

回答by JustJohn

Well, this seemed to work on my Razor Page and never even did a round trip to the .cs file. This is old html way. It might be useful.

好吧,这似乎适用于我的 Razor Page,甚至从未往返 .cs 文件。这是旧的 html 方式。它可能有用。

<input type="reset" value="Reset">