Asp.net MVC ModelState.Clear

任何人都可以给我一个在ASP.NET MVC中的模型状态(或链接到一个)的angular色简洁的定义。 特别是我需要知道在什么情况下调用ModelState.Clear()是必要或可取的。

位开放式的呵呵 …对不起,我想这可能会帮助,如果告诉你我正在做什么:

我有一个名为“页面”的控制器上的编辑动作。 当我第一次看到表单来改变页面的细节时,所有东西都加载正常(绑定到“MyCmsPage”对象)。 然后,我单击一个button,为MyCmsPage对象的一个​​字段( MyCmsPage.SeoTitle )生成一个值。 它生成罚款和更新的对象,然后返回与新修改的页面对象的行动结果,并期望相关的文本框(呈现使用<%= Html.TextBox("seoTitle", page.SeoTitle)%> )被更新。 ..但唉,它显示了旧的模型加载的价值。

我已经通过使用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:

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

我认为是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); } 

仍然视图呈现与原始模型。 奇怪的是,当我在视图中放置一个断点并检查模型时,它具有改变的值。 但是响应stream具有旧的值。

最后,我发现你做了同样的工作:

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

按预期工作。

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

更新:

  • 这不是一个错误。
  • 请停止从POST操作返回View() 。 如果操作成功,则使用PRG,并redirect到GET。
  • 如果你从一个POST动作返回一个View() ,那就做表单validation,然后按照MVC的devise方式使用内置的帮助器。 如果你这样做,那么你不应该使用.Clear()
  • 如果你正在使用这个动作返回一个SPA的 ajax,那么使用一个web api控制器,忘记ModelState因为你不应该使用它。

老答案:

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

通常情况下,您不需要清除MVC引擎为您维护的ModelState。 在尝试遵守MVCvalidation最佳实践时,手动清除可能会导致不良结果。

看来你正在试图为标题设置一个默认值。 这应该在模型对象被实例化(域层某处或对象本身 – 无参数ctor),get动作,以便它第一次或完全在客户端(通过ajax或其他)这样看起来就好像用户input了它,并返回了发布的表单集合。 一些如何在接收表单集合(在POST动作// Edit)中添加此值的方法导致了这种奇怪的行为,可能导致.Clear() 出现在您的身上。 相信我 – 你不想使用明确的。 尝试其他想法之一。

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

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

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

那么ModelState在validation方面基本上保持了模型的当前状态

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

 TryUpdateModel(); UpdateModel(); 

或者像ActionResult中的一个参数

 public ActionResult Create(Person person) 

ValueProviderResult :保存关于试图绑定到模型的细节。 恩。 尝试的价值,文化,原始价值

Clear()方法必须谨慎使用,因为它可能会导致不确定的结果。 而且你会失去像AttemptedValue这样的ModelState的一些很好的属性,MVC在后台使用它来重新填充表单值以防错误。

 ModelState["a"].Value.AttemptedValue 

我有一个例子,我想更新一个已经提交的表单的模型,并且不希望为了性能原因而redirect到Action。 以前的隐藏字段的值被保留在我更新的模型上 – 导致所有的问题!

几行代码很快就确定了我想删除的ModelState中的元素(在validation之后),所以新值的forms被使用:

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

好吧,我们很多人似乎都被这个咬了,虽然这种情况发生的理由是有道理的,我需要一种方法来确保我的模型的价值显示,而不是ModelState。

有些人build议使用ModelState.Remove(string key) ,但是key应该是什么也不明显,特别是对于嵌套模型。 这里有几个方法,我想出来协助这个。

RemoveStateFor方法将获取所需属性的ModelStateDictionary ,Model和expression式,并将其删除。 可以在视图中使用HiddenForModel ,只使用Model中的值创build一个隐藏的input字段,方法是先删除其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); } 

从这样的控制器调用:

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

或从这样的观点来看:

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

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

我想更新或重置一个值,如果它不完全validation,并遇到这个问题。

简单的回答,ModelState.Remove,是..有问题的..因为如果你使用助手你不知道名字(除非你坚持命名约定)。 除非你可能创build了一个函数,你的自定义助手和你的控制器都可以用它来获取一个名字。

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

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

明白了。 我的自定义ModelBinder没有被注册,并做到这一点:

 var mymsPage = new MyCmsPage(); NameValueCollection frm = controllerContext.HttpContext.Request.Form; myCmsPage.SeoTitle = (!String.IsNullOrEmpty(frm["seoTitle"])) ? frm["seoTitle"] : null; 

所以默认的模型绑定所做的事情一定是造成了这个问题。 不知道是什么,但我的问题至less是固定的,现在我的自定义模型联编程序正在注册。

一般来说,当你发现自己与框架标准实践相抗衡时,现在是重新考虑你的方法的时候了。 在这种情况下,ModelState的行为。 例如,当你在POST之后不想要模型状态时,考虑redirect到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 } 

这里是我如何连线的路线。 在创build路线之后,我把我的子代理(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())); } } }