Asp.net MVC ModelState.Clear


116

Хтось може дати мені коротке визначення ролі ModelState в MVC Asp.net (або посилання на один). Зокрема, мені потрібно знати, в яких ситуаціях потрібно або бажано телефонувати 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>

Noob AspMVC, якщо він хоче кешувати старі дані, то який сенс у наданні моделі користувачеві знову: @ У мене був такий самий випуск, дякую багато
брату

Відповіді:


135

Я думаю, це помилка в 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); 
}

Працює як очікувалося.

Я не думаю, що це "особливість", чи не так?


33
Просто зробив майже те саме, що і ти. З'ясували, що це не помилка. Це за задумом: помилка? EditorFor і DisplayFor не відображають однакове значення, і ASP.NET MVC's Html Helpers Render the Wroght Value
Metro Smurf

8
Людина, я вже 2 години боровся з цим. Дякуємо, що опублікували цю відповідь!
Андрій Агібалов

37
це все-таки так, і багато людей, включаючи мене, втрачають багато часу через це. помилка або дизайн, мені все одно, це "несподівано".
Забезпечити

7
Я погоджуюся з @Proviste, сподіваюся, що ця "функція" буде видалена в майбутньому
Бен

8
Я просто витратив на це чотири години. Некрасивий.
Брайан Маккей

46

Оновлення:

  • Це не помилка.
  • Перестаньте повертатися View()від дії POST. Замість цього використовуйте PRG та переадресуйте на GET, якщо дія буде успішною.
  • Якщо будуть повертаючи View()з дії в POST, зробіть це для перевірки форми, і зробити це так , як MVC розроблений з використанням вбудованого в помічниках. Якщо ви робите це таким чином, вам не потрібно використовувати.Clear()
  • Якщо ви використовуєте цю дію для повернення ajax для SPA , скористайтеся веб-контролером api та забудьте про те, що ModelStateвам не слід його використовувати.

Стара відповідь:

ModelState в MVC використовується в основному для опису стану модельного об'єкта в значній мірі щодо того, чи дійсний цей об'єкт чи ні. Цей підручник повинен багато пояснити.

Як правило, вам не потрібно очищати ModelState, оскільки він підтримується двигуном MVC для вас. Видалення його вручну може спричинити небажані результати при спробі дотримуватися кращих практик перевірки MVC.

Здається, ви намагаєтеся встановити за замовчуванням значення за замовчуванням. Це слід зробити, коли об'єкт моделі інстанціюється (доменний шар десь або в самому об'єкті - без параметрів ctor), при дії get таким чином, що він спускається на сторінку в перший раз або повністю на клієнті (через ajax або щось таке) так що він виглядає так, ніби користувач ввів його, і він повертається з колекцією розміщених форм. Деякі, як ваш підхід до додавання цього значення при отриманні колекції форм (у дії POST // Редагувати) викликає цю химерну поведінку, яка може призвести до того, що, .Clear() здається, працює для вас. Повірте мені - ви не хочете використовувати ясні. Спробуйте одну з інших ідей.


1
Чи допомагає мені трохи переосмислити рівень мого сервісу (стогін, але thx), але, як і багато іншого в мережі, він сильно нахиляється до точки зору використання ModelState для перевірки.
Містер Грок

До питання додано більше інформації, щоб показати, чому мене особливо цікавить ModelState.Clear () та причину мого запиту
Містер Грок

5
Я не купую цей аргумент, щоб зупинити повернення View (...) від функції [HttpPost]. Якщо ви розміщуєте вміст через ajax, а потім оновлюєте документ отриманим PartialView, MVC ModelState виявилося неправильним. Єдине вирішення, яке я знайшов, - це очистити його в методі контролера.
Аарон Гудон

@AaronHudon PRG досить добре зарекомендував себе.
Метт Кокай

Якщо я розміщую за допомогою AJAX-дзвінка, чи можу я переадресувати на дію GET і повернути заповнену модель, як OP хоче, все асинхронно?
MyiEye

17

Якщо ви хочете очистити значення для окремого поля, я знайшов корисну наступну техніку.

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

Примітка: Змініть "Ключ" на ім'я поля, яке потрібно скинути.


Я не знаю, чому це працювало по-іншому для мене (можливо, MVC4)? Але мені довелося також зробити модель.Key = "" згодом. Обидва рядки потрібні.
TTT

Я хочу похвалити вас за коментар про видалення @PeterGluck. Це краще, ніж очистити повний модельний стан (оскільки я маю помилки в деяких полях, які хотів би зберегти).
Тяб

6

Ну а ModelState в основному утримує поточний стан моделі з точки зору валідації

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

TryUpdateModel();
UpdateModel();

або як параметр у ActionResult

public ActionResult Create(Person person)

ValueProviderResult : утримуйте подробиці про спробу прив’язки до моделі. колишній СпробаValue, культура, RawValue .

Метод Clear () слід використовувати обережно, оскільки це може призвести до несподіваних результатів. І ви втратите кілька приємних властивостей ModelState, як AttemptedValue, це використовується MVC у фоновому режимі для перенаселення значень форми у разі помилки.

ModelState["a"].Value.AttemptedValue

1
хммм ... Можливо, саме тут я отримую питання за зовнішнім виглядом. Я перевірив значення властивості Model.SeoTitle, і воно змінилося, але спроба значення не має. Виглядає так, ніби він вставляє значення так, ніби є помилка на сторінці, навіть якщо її немає (перевірили словник ModelState і немає помилок).
Містер Грок

6

У мене був приклад, коли я хотів оновити модель викладеної форми, і не хотів "перенаправляти на дії" з причини виконання. Попередні значення прихованих полів зберігалися в моїй оновленій моделі - це спричиняє всі частини проблем !.

Кілька рядків коду незабаром визначили елементи в 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, модель, і вираз для шуканої власності, і видалити його. 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.


1
Дуже хороша! Зберігання вкладки на цьому для функцій ExpressionHelper.
Джерард ОНІІЛ

4

Я хотів оновити або скинути значення, якщо воно не зовсім перевірило, і зіткнувся з цією проблемою.

Проста відповідь, ModelState.Remove, .. проблематична .. тому що якщо ви використовуєте помічників, то насправді не знаєте цього імені (якщо ви не дотримуєтесь конвенції про іменування). Якщо, можливо, ви не створили функцію, яка відповідає вашим звичаям помічник, і ваш контролер можуть використовувати для отримання імені.

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

Але принаймні я зараз розумію це питання;).


Мені потрібно було зробити саме це; дивіться мої методи, які я розмістив нижче, і які допомогли мені Remove()правильно ввести ключ.
Тобіас J

0

Зрозумів, врешті-решт. Мій спеціальний ModelBinder, який не був зареєстрований, і робить це:

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

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

Отож, щось, що робило прив'язку моделі за замовчуванням, мало бути причиною проблеми. Не впевнений у чому, але моя проблема принаймні виправлена ​​зараз, коли моя реєстраційна в’язка на замовлення моделі реєструється.


Ну, я не маю досвіду роботи зі спеціальним ModelBinder, за замовчуванням він відповідає моїм потребам досі =).
JOBG

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

    }

Ви дуже правильно пропонуєте практику ПОСЛІДЧЕНОГО ПОСЛІДЖЕННЯ, адже я це роблю майже для кожної поштової дії. Однак у мене була дуже особлива потреба: у мене є фільтровальна форма вгорі сторінки, спочатку вона надсилається разом із get. Але я зіткнувся з проблемою, коли поле дати не зв'язане, а потім виявив, що GET-запити не несуть культуру (я використовую французький для свого додатка), тому мені довелося переключити запит на POST, щоб успішно прив’язати дату. Потім виникла ця проблема, я трохи її застряг ..
Souhaieb Besbes

@SouhaiebBesbes Перегляньте мої оновлення, що показують, як я обробляю культуру.
В2К

@SouhaiebBesbes, можливо, трохи простіше було б зберігати свою культуру в TempData. Дивіться stackoverflow.com/questions/12422930/…
B2K

0

Ну, це, здавалося, працює на моїй сторінці Razor і навіть ніколи не робив зворотній шлях до файлу .cs. Це старий html-спосіб. Це може бути корисно.

<input type="reset" value="Reset">
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.