Asp.net MVC ModelState.Clear

¿Alguien puede darme una definición sucinta del papel de ModelState en Asp.net MVC (o un enlace a uno). En particular, necesito saber en qué situaciones es necesario o deseable llamar a ModelState.Clear() .

El bit abierto terminó eh … lo siento, creo que podría ser útil si te cuento lo que estoy haciendo realmente:

Tengo una Acción de Edición en un Controlador llamada “Página”. Cuando veo por primera vez el formulario para cambiar los detalles de la página, todo se carga bien (vinculando a un objeto “MyCmsPage”). Luego hago clic en un botón que genera un valor para uno de los campos del objeto MyCmsPage.SeoTitle ( MyCmsPage.SeoTitle ). Genera bien y actualiza el objeto y luego devuelve el resultado de la acción con el objeto de página recientemente modificado y espera que se actualice el cuadro de texto relevante (representado con ). .. pero desgraciadamente muestra el valor del modelo anterior que se cargó.

He trabajado alrededor usando ModelState.Clear() pero necesito saber por qué y cómo ha funcionado, así que no lo estoy haciendo a ciegas.

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" %> .... 

Creo que es un error en MVC. Luché con este problema durante horas hoy.

Dado este:

 public ViewResult SomeAction(SomeModel model) { model.SomeString = "some value"; return View(model); } 

La vista se representa con el modelo original, ignorando los cambios. Entonces pensé, tal vez no me gusta usar el mismo modelo, así que lo intenté así:

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

Y aún la vista se renderiza con el modelo original. Lo que es extraño es que cuando pongo un punto de interrupción en la vista y examino el modelo, tiene el valor cambiado. Pero el flujo de respuesta tiene los valores anteriores.

Eventualmente descubrí el mismo trabajo que tú:

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

Funciona como se esperaba

No creo que esta sea una “característica”, ¿o sí?

Actualizar:

  • Esto no es un error.
  • Deje de devolver View() desde una acción POST. Use PRG en su lugar y redirija a un GET si la acción es exitosa.
  • Si está devolviendo una View() desde una acción POST, hágalo para la validación de formularios, y hágalo de la forma en que MVC está diseñado utilizando los ayudantes integrados. Si lo haces de esta manera, entonces no deberías necesitar usar .Clear()
  • Si está utilizando esta acción para devolver ajax para un SPA , use un controlador web api y olvídese de ModelState ya que no debería usarlo de todos modos.

Respuesta anterior:

ModelState en MVC se utiliza principalmente para describir el estado de un objeto modelo en gran medida en relación con si ese objeto es válido o no. Este tutorial debe explicar mucho.

En general, no es necesario que borre el modelo de estado tal como lo mantiene el motor de MVC. Limpiarlo manualmente podría causar resultados no deseados al intentar cumplir con las mejores prácticas de validación de MVC.

Parece que está intentando establecer un valor predeterminado para el título. Esto se debe hacer cuando se crea una instancia del objeto modelo (capa de dominio en algún lugar o en el objeto mismo – ctor sin parámetros), en la acción get de manera que vaya a la página la primera vez o completamente en el cliente (a través de ajax o algo) para que parezca que el usuario lo ingresó y vuelve con la colección de formularios publicados. De alguna forma, su enfoque de agregar este valor en la recepción de una colección de formularios (en la acción POST // Editar) está causando este extraño comportamiento que podría dar como resultado que un .Clear() parezca funcionar para usted. Confía en mí, no quieres usar el claro. Pruebe una de las otras ideas.

Si desea borrar un valor para un campo individual, entonces encontré útil la siguiente técnica.

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

Nota: Cambie “Clave” al nombre del campo que desea restablecer.

Bueno, el modelo de estado básicamente mantiene el estado actual del modelo en términos de validación, sostiene

ModelErrorCollection: representa los errores cuando el modelo intenta vincular los valores. ex.

 TryUpdateModel(); UpdateModel(); 

o como un parámetro en ActionResult

 public ActionResult Create(Person person) 

ValueProviderResult : mantenga los detalles sobre el bash de vincularse al modelo. ex. AttemptedValue, Culture, RawValue .

El método Clear () debe usarse con precaución porque puede generar resultados no previstos. Y perderá algunas buenas propiedades del ModeloEstado como AttemptedValue, esto es usado por MVC en segundo plano para volver a llenar los valores del formulario en caso de error.

 ModelState["a"].Value.AttemptedValue 

Tuve una instancia en la que deseaba actualizar el modelo de una forma perdida, y no deseaba ‘Redirigir a la acción’ por razones de rendimiento. Los valores previos de los campos ocultos se conservaron en mi modelo actualizado, lo que provocó una serie de problemas.

Algunas líneas de código pronto identificaron los elementos dentro de ModelState que quería eliminar (después de la validación), por lo que los nuevos valores se usaron en la forma:

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

Bueno, parece que a muchos de nosotros nos ha mordido esto, y aunque la razón por la que esto sucede tiene sentido, necesitaba una forma de garantizar que se mostrara el valor de mi Modelo, y no el Estado Modelo.

Algunos han sugerido ModelState.Remove(string key) , pero no es obvio cuál debería ser la key , especialmente para los modelos nesteds. Aquí hay un par de métodos que ideé para ayudar con esto.

El método RemoveStateFor tomará un ModelStateDictionary , un modelo y una expresión para la propiedad deseada, y lo eliminará. HiddenForModel se puede usar en su vista para crear un campo de entrada oculto usando solo el valor del modelo, primero quitando su entrada de estado del modelo. (Esto podría expandirse fácilmente para los otros métodos de extensión de ayuda).

 ///  /// 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. ///  public static MvcHtmlString HiddenForModel(this HtmlHelper helper, Expression> expression) { RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression); return helper.HiddenFor(expression); } ///  /// 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. ///  public static void RemoveStateFor(this ModelStateDictionary modelState, TModel model, Expression> expression) { var key = ExpressionHelper.GetExpressionText(expression); modelState.Remove(key); } 

Llamar desde un controlador como este:

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

o desde una vista como esta:

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

Utiliza System.Web.Mvc.ExpressionHelper para obtener el nombre de la propiedad ModelState.

Quería actualizar o restablecer un valor si no valía del todo, y me encontré con este problema.

La respuesta fácil, ModelState.Remove, es … problemática … porque si usas ayudantes, realmente no conoces el nombre (a menos que te apegues a la convención de nombres). A menos que tal vez crees una función que tu ayudante personalizada y tu controlador pueden usar para obtener un nombre.

Esta característica debería haberse implementado como una opción en el helper, donde por defecto no lo hace, pero si quisiera que la entrada no aceptada volviera a mostrarse, podría decirlo.

Pero al menos entiendo el problema ahora;).

Lo tengo al final. Mi Custom ModelBinder que no estaba registrado y hace esto:

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

Así que algo que estaba haciendo el enlace de modelo predeterminado debe haber estado causando el problema. No estoy seguro de qué, pero mi problema al menos está solucionado ahora que se está registrando mi carpeta de modelo personalizado.

En general, cuando te encuentras luchando contra las prácticas estándar del marco, es hora de reconsiderar tu enfoque. En este caso, el comportamiento de ModelState. Por ejemplo, cuando no desee el estado del modelo después de un POST, considere un redireccionamiento al 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); } 

EDITADO para responder el comentario de la cultura:

Esto es lo que uso para manejar una aplicación MVC multicultural. Primero las subclases manejadoras de ruta:

 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 } 

Y así es como conecto las rutas. Después de crear las rutas, anexo mi subagente (example.com/subagent1, example.com/subagent2, etc.) luego el código de cultura. Si todo lo que necesita es la cultura, simplemente elimine el subagente de los manejadores de ruta y las rutas.

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