首页 文章

Asp.net MVC ModelState.Clear

提问于
浏览
106

任何人都可以给我一个关于ModelState在Asp.net MVC中的角色的简洁定义(或者链接到一个) . 特别是我需要知道在什么情况下调用 ModelState.Clear() 是必要或可取的 .

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

我在控制器上有一个名为"Page"的编辑操作 . 当我第一次看到表单来更改Page 's details everything loads up fine (binding to a 1241199 object). Then I click a button that generates a value for one of the MyCmsPage object' s字段( MyCmsPage.SeoTitle )时 . 它生成正常并更新对象然后我返回动作结果与新修改的页面对象,并期望相关的文本框(使用 <%= Html.TextBox("seoTitle", page.SeoTitle)%> 呈现)更新...但是它显示了已加载的旧模型的值 .

我通过使用_1241203来解决它,但我需要知道它为什么/如何工作所以我不只是盲目地做 .

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>

9 回答

  • 6

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

    按预期工作 .

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

  • 4

    Update:

    • 这不是错误 .

    • 请停止从POST操作返回 View() . 如果操作成功,请改用PRG并重定向到GET .

    • 如果从POST操作返回 View() ,请执行表单验证,并使用内置帮助程序MVC is designed进行操作 . 如果你这样做那么你不应该使用 .Clear()

    • 如果您正在使用此操作返回SPA的ajax,请使用web api控制器并忘记 ModelState ,因为您无论如何都不应该使用它 .

    Old answer:

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

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

    您似乎正在尝试为 Headers 设置默认值 . 这应该在模型对象被实例化时(在某个地方或在对象本身 - 无参数的ctor中),在get动作上进行,以便它第一次或完全在客户端(通过ajax或其他)下载到页面这样看起来好像用户输入了它,然后它返回了已发布的表单集合 . 一些如何在接收表单集合时添加此值的方法(在POST操作//编辑中)导致这种奇怪的行为可能导致 .Clear() 似乎适合您 . 相信我 - 你不想使用clear . 尝试其他一个想法 .

  • 6

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

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

    Note: 将"Key"更改为要重置的字段的名称 .

  • 0

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

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

    TryUpdateModel();
    UpdateModel();
    

    或者像ActionResult中的参数

    public ActionResult Create(Person person)
    

    ValueProviderResult :保留有关尝试绑定到模型的详细信息 . 恩 . AttemptedValue, Culture, RawValue .

    必须谨慎使用Clear()方法,因为它可能导致意外结果 . 并且您将失去ModelState的一些不错的属性,如AttemptedValue,MVC在后台使用它来在出错时重新填充表单值 .

    ModelState["a"].Value.AttemptedValue
    
  • 17

    我有一个实例,我想更新一个总结形式的模型,并且不想因为性能原因而“重定向到行动” . 隐藏字段的先前值保留在我更新的模型上 - 导致各种问题!

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

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

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

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

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

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

  • 37

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

    简单的答案,ModelState.Remove,是..有问题..因为如果你使用帮助器,你真的不知道名称(除非你坚持命名约定) . 除非您创建一个自定义帮助程序和控制器都可以用来获取名称的函数 .

    此功能应作为帮助程序的一个选项实现,默认情况下不执行此操作,但如果您希望重新显示未接受的输入,则可以这样说 .

    但至少我现在理解这个问题;) .

  • 129

    最后得到它 . 我的自定义ModelBinder未注册并执行此操作:

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

    因此默认模型绑定所做的事情必定导致问题 . 不确定是什么,但我的问题至少已经修复,因为我的自定义模型 Binders 正在注册 .

  • 0

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

相关问题