Asp.net MVC ModelState

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

有点开放哈... 对不起,我想如果告诉你我实际上在做什么可能会有帮助:

我有一个动作的编辑控制器称为“页面”。当我第一次看到用于更改页面细节的表单时,所有东西都能正常加载(绑定到一个“ MyCmsPage”对象)。然后单击一个按钮,该按钮为 MyCmsPage 对象的一个字段(MyCmsPage.SeoTitle)生成一个值。它生成 fine 并更新对象,然后我返回新修改的页面对象的操作结果,并期望相关的文本框(使用 <%= 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>
121990 次浏览

更新:

  • 这不是窃听器。
  • 请停止从 POST 操作返回 View()。如果操作成功,则使用 PRG并重定向到 GET。
  • 如果 从 POST 操作返回 View(),那么这样做是为了进行表单验证,并按照使用内置助手的 MVC 是设计好的的方式进行。如果你这样做,那么你应该不需要使用 .Clear()
  • 如果您使用这个操作来返回 SPA的 ajax,那么使用 web api 控制器并忘记 ModelState,因为您无论如何都不应该使用它。

Old answer:

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

通常您不需要清除 ModelState,因为它是由 MVC 引擎为您维护的。当试图坚持 MVC 验证最佳实践时,手动清除它可能会导致不想要的结果。

似乎您正在尝试为标题设置默认值。这应该在模型对象被实例化时完成(在对象本身的某个地方或领域层——无参数的 ctor) ,在 get 操作中,它第一次或完全在客户端(通过 ajax 或其他方式)出现在页面上,这样它看起来就像用户输入了它,然后返回发布的表单集合。在接收表单集合时(在 POST 操作//编辑中)添加这个值的方法会导致这种奇怪的行为,这可能会导致 .Clear() 出现了为您工作。相信我,你不会想用安全套的。试试别的办法。

ModelState 基本上保持了模型在验证方面的当前状态

ModelErrorCollection: 表示模型试图绑定值时的错误。 前男友。

TryUpdateModel();
UpdateModel();

或者类似于 ActionResult 中的参数

public ActionResult Create(Person person)

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

Clear ()方法必须谨慎使用,因为它可能导致未检查的结果。并且您将丢失 ModelState 的一些很好的属性,比如 AttemtedValue,这是 MVC 在后台使用的,以便在出现错误时重新填充表单值。

ModelState["a"].Value.AttemptedValue

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

var mymsPage = new MyCmsPage();


NameValueCollection frm = controllerContext.HttpContext.Request.Form;


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

因此,缺省模型绑定所做的某些事情肯定导致了这个问题。不确定是什么,但是我的问题至少已经解决了,因为我的自定义模型绑定器已经被注册了。

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

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

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

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

Given this:

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);
}

Works as expected.

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

我有一个实例,我想要更新一个已提交表单的模型,并且由于性能原因不想“重定向到操作”。之前隐藏字段的值被保留在我的更新模型中——导致了各种各样的问题!.

几行代码很快就识别出了 ModelState 中我想删除的元素(在验证之后) ,因此新的值在表单中使用:-

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

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

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 习俗 helper and your controller can use to get a name.

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

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

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


}

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

有些人建议使用 ModelState.Remove(string key),但是 key应该是什么并不明显,特别是对于嵌套模型。下面是我想出来的一些方法来帮助解决这个问题。

The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it. HiddenForModel can 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).

/// <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 属性的名称。

嗯,这似乎工作在我的剃刀页面,甚至从来没有做一个往返的.cs 文件。 This is old html way. It might be useful.

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