Asp.net MVC ModelState.Четкий


может ли кто - нибудь дать мне краткое определение роли ModelState в Asp.net MVC (или ссылка на него). В частности мне нужно знать в каких ситуациях нужно или желательно звонить ModelState.Clear().

немного открытый конец ха... извините, я думаю, что это может помочь, если сказать вам, что я остро делаю:

у меня есть действие редактирования на контроллере под названием "страница". Когда я впервые вижу форму для изменения деталей страницы, все загружается нормально (привязка к объект "MyCmsPage"). Затем я нажимаю кнопку, которая генерирует значение для одного из полей объекта MyCmsPage (MyCmsPage.SeoTitle). Он генерирует штраф и обновляет объект, а затем я возвращаю результат действия с недавно измененным объектом страницы и ожидаю соответствующего текстового поля (отображается с помощью <%= Html.TextBox("seoTitle", page.SeoTitle)%>) для обновления ... но, увы, он отображает значение из старой модели, которая была загружена.

Я работал вокруг него с помощью ModelState.Clear() но мне нужно знать, почему / как это сработало, поэтому я не просто делать это вслепую.

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 106

9 ответов:

Я думаю, что это ошибка в 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); 
}

работает, как ожидалось.

Я не думаю, что это "особенность", так?

обновление:

  • это не ошибка.
  • Пожалуйста, прекратите возвращения View() из действия POST. Используйте PRG вместо этого и перенаправить на GET, если действие является успешным.
  • если вы are возвращение a View() из действия POST, сделайте это для проверки формы, и сделайте это так MVC разработан С помощью встроенных помощников. Если вы делаете это таким образом, то вам не нужно использовать .Clear()
  • если вы используете это действие для возврата ajax для СПА, используйте контроллер веб-api и забудьте о ModelState так как вы не должны использовать его в любом случае.

ответ:

ModelState в MVC используется в основном для описания состояния объекта модели в основном в зависимости от того, является ли этот объект допустимым или нет. в этом уроке должно многое объяснить.

как правило, вы не должны необходимо очистить ModelState, поскольку он поддерживается двигателем MVC для вас. Очистка его вручную может привести к нежелательным результатам при попытке придерживаться лучших практик проверки MVC.

похоже, что вы пытаетесь установить значение по умолчанию для заголовка. Это должно быть сделано, когда объект модели создается (доменный слой где - то или в самом объекте-без параметров ctor), на действии get таким образом, что он спускается на страницу 1-й раз или полностью на клиенте (через ajax или что-то еще), чтобы он выглядел так, как будто пользователь ввел его, и он возвращается с опубликованной коллекцией форм. Некоторые, как ваш подход добавления этого значения при получении коллекции форм (в действии POST / / Edit) вызывает это странное поведение, которое может привести к .Clear()появляется работать на вас. Поверь мне-Ты не хочешь использовать клир. Попробуйте одну из других идей.

Если вы хотите очистить значение для отдельного поля, то я нашел следующий метод полезным.

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

Примечание: Измените "ключ" на имя поля, которое вы хотите сбросить.

Ну ModelState в основном держит текущее состояние модели с точки зрения проверки, он держит

ModelErrorCollection: представляют ошибки, когда модель пытается связать значения. бывший.

TryUpdateModel();
UpdateModel();

или как параметр в ActionResult

public ActionResult Create(Person person)

ValueProviderResult: удерживайте сведения о попытке привязки к модели. бывший. AttemptedValue, Культура, RawValue.

ясный() метод необходимо использовать с осторожностью, потому что это может привести к необнаруженным результатам. И вы потеряете некоторые хорошие свойства ModelState, такие как AttemptedValue, это используется MVC в фоновом режиме для повторного заполнения значений формы в случае ошибки.

ModelState["a"].Value.AttemptedValue

У меня был экземпляр, в котором я хотел обновить модель суммированной формы и не хотел "перенаправлять на действие" по причине performanace. Предыдущие значения скрытых полей были сохранены на моей обновленной модели-вызывая все виды проблем!.

несколько строк кода вскоре определили элементы в ModelState, которые я хотел удалить (после проверки), поэтому новые значения были использованы в виде: -

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

Ну, многие из нас, похоже, были укушены этим, и хотя причина этого имеет смысл, мне нужен был способ убедиться, что значение на моей модели было показано, а не ModelState.

некоторые предлагали ModelState.Remove(string key), но это не очевидно, что key должно быть, особенно для вложенных моделей. Вот несколько методов, которые я придумал, чтобы помочь с этим.

The 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.Уберите, есть.. проблематичный.. потому что если вы используете помощников, вы действительно не знаете имя (если вы не придерживаетесь соглашения об именах). Разве вы создаете функцию, что и custom помощник и ваш контроллер может использовать, чтобы получить имя.

эта функция должна была быть реализована в качестве опции на помощника, где по умолчанию это делает не сделайте это, но если вы хотите, чтобы неприемлемый вход для повторного воспроизведения вы могли бы просто сказать об этом.

но, по крайней мере, теперь я понимаю проблему ;).

получил его в конце. Мой пользовательский ModelBinder, который не был зарегистрирован и это :

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

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

таким образом, что-то, что привязка модели по умолчанию делала, должно быть, вызывало проблему. Не уверен, что, но моя проблема, по крайней мере, исправлена теперь, когда моя пользовательская модель binder регистрируется.

как правило, когда вы обнаружите, что боретесь с рамками стандартных практик, пришло время пересмотреть свой подход. В этом случае поведение ModelState. Например, если вы не хотите состояние модели после публикации, рассмотрите перенаправление на 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()));
            }
        }

    }