谁能给我一个关于 ModelState 在 Asp.net MVC 中角色的简洁定义(或一个链接)。特别是我需要知道在什么情况下需要或需要调用 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);
}
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>
我认为是 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);
}
按预期工作。
我不认为这是一个“功能”,是吗?
更新:
这不是错误。
请停止从 POST 操作返回 View()。如果操作成功,请改用 PRG 并重定向到 GET。
如果您从 POST 操作返回 View(),请执行此操作以进行表单验证,并按照 MVC 使用内置帮助程序设计的方式执行此操作。如果你这样做,那么你不应该使用 .Clear()
如果您使用此操作为 SPA 返回 ajax,请使用 Web api 控制器并忘记 ModelState,因为无论如何您都不应该使用它。
老答案:
MVC 中的 ModelState 主要用于描述模型对象的状态,主要与该对象是否有效有关。 This tutorial 应该解释很多。
通常,您不需要清除 ModelState,因为它由 MVC 引擎为您维护。在尝试遵守 MVC 验证最佳实践时,手动清除它可能会导致不希望的结果。
您似乎正在尝试为标题设置默认值。这应该在模型对象被实例化(某处或对象本身中的域层 - 无参数 ctor)时,在 get 操作上完成,以便它第一次下到页面或完全在客户端上(通过 ajax 或其他东西)这样看起来就好像用户输入了它并返回了已发布的表单集合。您在接收表单集合时添加此值的一些方法(在 POST 操作 // 编辑中)会导致这种奇怪的行为,可能会导致 .Clear()
出现 为您工作。相信我——你不想使用透明的。尝试其他想法之一。
如果您想清除单个字段的值,那么我发现以下技术很有用。
ModelState.SetModelValue("Key", new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture));
注意:将“Key”更改为要重置的字段的名称。
好吧,我们中的很多人似乎都被这一点所困扰,尽管发生这种情况的原因是有道理的,但我需要一种方法来确保显示我的模型上的值,而不是模型状态。
有些人建议使用 ModelState.Remove(string key)
,但 key
应该是什么并不明显,尤其是对于嵌套模型。这是我想出的几种方法来帮助解决这个问题。
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);
}
从这样的控制器调用:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
或从这样的角度来看:
@Html.HiddenForModel(m => m.MySubProperty.MySubValue)
它使用 System.Web.Mvc.ExpressionHelper
来获取 ModelState 属性的名称。
那么 ModelState 基本上就验证而言持有模型的当前状态,它持有
ModelErrorCollection:表示模型尝试绑定值时的错误。前任。
TryUpdateModel();
UpdateModel();
或像 ActionResult 中的参数
public ActionResult Create(Person person)
ValueProviderResult:保存有关尝试绑定到模型的详细信息。前任。尝试值、文化、原始值。
必须谨慎使用 Clear() 方法,因为它可能导致意想不到的结果。并且您将丢失 ModelState 的一些不错的属性,例如 AttemptedValue,MVC 在后台使用它来重新填充表单值以防出错。
ModelState["a"].Value.AttemptedValue
我有一个实例,我想更新提交表单的模型,并且出于性能原因不想“重定向到操作”。我更新的模型中保留了以前的隐藏字段值 - 导致各种问题!
几行代码很快就确定了我想要删除的 ModelState 中的元素(在验证之后),因此新值以如下形式使用:-
while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}
如果它没有完全验证,我想更新或重置一个值,并遇到了这个问题。
简单的答案 ModelState.Remove 是.. 有问题的.. 因为如果您使用助手,您并不真正知道名称(除非您遵守命名约定)。除非您创建了一个函数,您的自定义助手和控制器都可以使用它来获取名称。
这个特性应该作为辅助函数的一个选项实现,默认情况下不这样做,但如果你想重新显示未接受的输入,你可以这么说。
但至少我现在明白了这个问题;)。
Remove()
找到了正确的密钥。
最后得到了它。我的自定义 ModelBinder 未注册并执行此操作:
var mymsPage = new MyCmsPage();
NameValueCollection frm = controllerContext.HttpContext.Request.Form;
myCmsPage.SeoTitle = (!String.IsNullOrEmpty(frm["seoTitle"])) ? frm["seoTitle"] : null;
因此,默认模型绑定所做的事情一定是导致问题的原因。不知道是什么,但我的问题至少已经解决了,因为我的自定义模型绑定器正在注册。
通常,当您发现自己与框架标准实践作斗争时,是时候重新考虑您的方法了。在这种情况下,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()));
}
}
}
好吧,这似乎在我的 Razor 页面上有效,甚至从未往返于 .cs 文件。这是旧的html方式。它可能有用。
<input type="reset" value="Reset">