Asp.net MVC ModelState.Clear

Posted

技术标签:

【中文标题】Asp.net MVC ModelState.Clear【英文标题】: 【发布时间】:2010-12-19 00:16:15 【问题描述】:

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

Bit open end huh...对不起,如果告诉你我在做什么可能会有所帮助:

我在一个名为“页面”的控制器上有一个编辑操作。当我第一次看到更改页面详细信息的表单时,一切都加载正常(绑定到“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>

【问题讨论】:

Noob AspMVC,如果它想缓存旧数据,那么再次将模型提供给用户有什么意义:@ 我有同样的问题,非常感谢兄弟 【参考方案1】:

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

按预期工作。

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

【讨论】:

刚刚做了几乎和你一样的事情。不过,发现这不是错误。这是设计使然:A Bug? EditorFor and DisplayFor don't display same value 和 ASP.NET MVC’s Html Helpers Render the Wrong Value 伙计,我已经和它打了 2 个小时了。感谢您发布此答案! 这仍然是事实,包括我在内的很多人都因此而浪费了很多时间。错误或设计,我不在乎,这是“意外”。 我同意@Proviste,我希望将来删除这个“功能” 我在这上面只花了四个小时。丑陋。【参考方案2】:

更新:

这不是错误。 请停止从 POST 操作返回 View()。如果操作成功,请改用 PRG 并重定向到 GET。 如果您正在从 POST 操作返回 View(),请执行此操作以进行表单验证,并使用内置帮助程序以 MVC is designed 的方式执行此操作。如果你这样做,那么你不需要使用.Clear() 如果您使用此操作为 SPA 返回 ajax,请使用 Web api 控制器并忘记 ModelState,因为无论如何您都不应该使用它。

旧答案:

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

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

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

【讨论】:

确实帮助我重新考虑了我的服务层(叹了口气,但谢谢),但与网络上的很多东西一样,它在很大程度上倾向于使用 ModelState 进行验证。 在问题中添加了更多信息,以说明我为什么对 ModelState.Clear() 特别感兴趣以及我的查询原因 我并没有真正购买这个论点来 stop 从 [HttpPost] 函数返回 View(...)。如果您通过 ajax 发布内容,然后使用生成的 PartialView 更新文档,则 MVC ModelState 已显示为不正确。我发现的唯一解决方法是在控制器方法中清除它。 @AaronHudon PRG 已经很成熟了。 如果我使用 AJAX 调用进行 POST,我可以重定向到 GET 操作并返回 OP 想要的模型填充视图,所有这些都是异步的吗?【参考方案3】:

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

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

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

【讨论】:

我不知道为什么这对我来说效果不同(也许是 MVC4)?但之后我还必须做 model.Key = "" 。这两行都是必需的。 我想赞扬你删除评论@PeterGluck。这比清除完整的模型状态要好(因为我想保留某些字段的错误)。【参考方案4】:

嗯,ModelState 基本上保存了模型在验证方面的当前状态,它保存

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

TryUpdateModel();
UpdateModel();

或者像ActionResult中的一个参数

public ActionResult Create(Person person)

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

Clear() 方法必须谨慎使用,因为它可能导致意想不到的结果。而且你会丢失 ModelState 的一些不错的属性,比如 AttemptedValue,MVC 在后台使用它来重新填充表单值以防出错。

ModelState["a"].Value.AttemptedValue

【讨论】:

嗯...这可能就是我从外观上得到问题的地方。我检查了 Model.SeoTitle 属性的值,它已经改变,但尝试的值没有。看起来好像它在粘贴值,就好像页面上有错误,即使没有错误(检查了 ModelState Dictionary 并且没有错误)。【参考方案5】:

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

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

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

    ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));

【讨论】:

【参考方案6】:

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

有些人建议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 属性的名称。

【讨论】:

非常好!密切关注 ExpressionHelper 功能。【参考方案7】:

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

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

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

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

【讨论】:

我需要这样做;请参阅我在下面发布的方法,这些方法帮助我 Remove() 正确的密钥。【参考方案8】:

最后明白了。我的自定义 ModelBinder 未注册并执行此操作:

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

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

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

【讨论】:

好吧,我没有使用自定义 ModelBinder 的经验,到目前为止,默认的 ModelBinder 适合我的需求 =)。【参考方案9】:

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

    

【讨论】:

您建议使用 POST REDIRECT 做法是非常正确的,事实上我几乎对每个发布操作都这样做。但是我有一个非常特殊的需求:我在页面顶部有一个过滤器表单,最初它是使用 get 提交的。但是我遇到了一个日期字段未绑定的问题,然后发现 GET 请求不携带文化(我的应用程序使用法语),所以我不得不将请求切换到 POST 以成功绑定我的日期。然后出现了这个问题,我有点卡住了她.. @SouhaiebBesbes 查看我的更新,展示我如何处理文化。 @SouhaiebBesbes 可能更简单一点的是将您的文化存储在 TempData 中。见***.com/questions/12422930/…【参考方案10】:

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

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

【讨论】:

以上是关于Asp.net MVC ModelState.Clear的主要内容,如果未能解决你的问题,请参考以下文章

Asp.net mvc和asp.net有啥区别?

七天学会ASP.NET MVC ——ASP.NET MVC 数据传递

ASP.NET MVC

ASP.NET MVC 5、ASP.NET Core MVC 5 有啥区别?

ASP.NET MVC 和 Angularjs 与 ASP.NET MVC 和 Reactjs

七天学会ASP.NET MVC ——ASP.Net MVC 数据处理