Asp.net MVC ModeliState.Clear


116

Biri bana ModelState'in Asp.net MVC'deki rolünün kısa bir tanımını verebilir mi (veya birine bir bağlantı). Özellikle hangi durumlarda aramanın gerekli veya arzu edildiğini bilmem gerekiyor ModelState.Clear().

Biraz açık uçlu ha ... pardon, sanırım gerçekten ne yaptığımı söylersen yardımcı olabilir

Denetleyicide "Sayfa" adında bir Düzenleme Eylemim var. Sayfanın ayrıntılarını değiştirmek için formu ilk gördüğümde her şey iyi yükleniyor (bir "MyCmsPage" nesnesine bağlanma). Sonra MyCmsPage nesnesinin alanlarından biri için bir değer oluşturan bir düğmeye tıklıyorum ( MyCmsPage.SeoTitle). İnce üretir ve nesneyi günceller ve ardından yeni değiştirilmiş sayfa nesnesiyle eylem sonucunu döndürürüm ve ilgili metin kutusunun (kullanılarak oluşturulmuş <%= Html.TextBox("seoTitle", page.SeoTitle)%>) güncellenmesini beklerim ... ama ne yazık ki yüklenen eski modelin değerini görüntüler.

Kullanarak bunun etrafında çalıştım ModelState.Clear()ama neden / nasıl çalıştığını bilmem gerekiyor, bu yüzden sadece körü körüne yapmıyorum.

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, eski verileri önbelleğe almak istiyorsa, modeli tekrar kullanıcıya vermenin amacı nedir: @ aynı sorunu
yaşadım

Yanıtlar:


135

MVC'de bir hata olduğunu düşünüyorum. Bugün saatlerce bu sorunla uğraştım.

Bu göz önüne alındığında:

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

Görünüm, değişiklikleri göz ardı ederek orijinal modelle işlenir. Bu yüzden düşündüm, belki aynı modeli kullanmamdan hoşlanmıyor, bu yüzden şöyle denedim:

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

Ve yine de görünüm orijinal modelle işliyor. Garip olan, görünüme bir kesme noktası koyduğumda ve modeli incelediğimde, değişen değere sahip olmasıdır. Ancak yanıt akışı eski değerlere sahiptir.

Sonunda, senin yaptığın aynı işi keşfettim:

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

Beklendiği gibi çalışıyor.

Bunun bir "özellik" olduğunu sanmıyorum, değil mi?


33
Seninle neredeyse aynı şeyi yaptım. Bunun bir hata olmadığını öğrendim. Tasarım gereği: Bir Hata mı? EditorFor ve DisplayFor aynı değeri göstermiyor ve ASP.NET
Metro Şirin

8
Dostum, onunla savaşmak için 2 saatimi harcadım. Bu cevabı gönderdiğiniz için teşekkürler!
Andrey Agibalov

37
bu hala doğru ve ben dahil pek çok insan bu yüzden çok zaman kaybediyor. hata veya tasarım gereği, umrumda değil, bu "beklenmedik".
Proviste

7
@Proviste'ye katılıyorum, umarım bu "özellik" gelecekte kaldırılır
Ben

8
Bunun için dört saat geçirdim. Çirkin.
Brian MacKay

46

Güncelleme:

  • Bu bir hata değil.
  • Lütfen View()bir POST işleminden geri dönmeyi bırakın . Kullanım PRG yerine ve bir GET için yönlendirme eylemi sonucu başarı ise.
  • Eğer varsa vardır dönen View()bir POST eylemden form doğrulama için bunu ve bunu yoldan yapmak MVC tasarlanmıştır yardımcıları inşa kullanarak. Bu şekilde yaparsanız, kullanmanıza gerek kalmaz.Clear()
  • Bir SPA için ajax'ı iade etmek için bu eylemi kullanıyorsanız , bir web api denetleyicisi kullanın ve ModelStateyine de kullanmamanız gerektiğinden unutun .

Eski cevap:

MVC'deki ModelState, öncelikle bir model nesnesinin durumunu, büyük ölçüde bu nesnenin geçerli olup olmadığıyla ilişkili olarak tanımlamak için kullanılır. Bu eğitim çok şey açıklamalıdır.

Genel olarak, sizin için MVC motoru tarafından sağlandığı için ModelState'i temizlemenize gerek yoktur. Manuel olarak temizlemek, MVC doğrulama en iyi uygulamalarına uymaya çalışırken istenmeyen sonuçlara neden olabilir.

Başlık için varsayılan bir değer belirlemeye çalıştığınız anlaşılıyor. Bu, model nesnesi somutlaştırıldığında (bir yerde veya nesnenin kendisinde etki alanı katmanı - parametresiz ctor), ilk kez sayfaya veya tamamen istemcide (ajax veya başka bir şey aracılığıyla) sayfaya inecek şekilde alma eyleminde yapılmalıdır. böylece kullanıcı girmiş gibi görünür ve gönderilen form koleksiyonuyla geri döner. Bir form koleksiyonunun alınmasına bu değeri ekleme yaklaşımınızın (POST eyleminde // Düzenle), sizin için işe yarayabilecek bir .Clear() görünüme neden olabilecek bu tuhaf davranışa neden olduğunu biraz. Güven bana - temizliği kullanmak istemezsin. Diğer fikirlerden birini deneyin.


1
Hizmetler katmanımı biraz yeniden düşünmeme yardımcı oluyor (inilti ama thx), ancak ağdaki birçok şeyde olduğu gibi, doğrulama için ModelState kullanma bakış açısına büyük ölçüde eğiliyor.
Mr Grok

ModelState.Clear () ile neden özellikle ilgilendiğimi ve sorgumun nedenini göstermek için soruya daha fazla bilgi eklendi
Bay Grok

5
Bu argümanı bir [HttpPost] işlevinden View (...) döndürmeyi durdurmak için gerçekten satın almıyorum. İçeriği ajax aracılığıyla POST yapıyorsanız ve ardından belgeyi elde edilen PartialView ile güncelliyorsanız, MVC ModelState'in yanlış olduğu gösterilmiştir. Bulduğum tek geçici çözüm, denetleyici yönteminde temizlemektir.
Aaron Hudon

@AaronHudon PRG oldukça iyi kurulmuş.
Matt Kocaj

Bir AJAX çağrısıyla POST yaparsam, bir GET eylemine yeniden yönlendirebilir ve OP'nin istediği gibi modelle dolu bir görünümü eşzamansız olarak döndürebilir miyim?
MyiEye

17

Tek bir alan için bir değeri temizlemek istiyorsanız, aşağıdaki tekniği yararlı buldum.

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

Not: "Anahtar" ı sıfırlamak istediğiniz alanın adı ile değiştirin.


Bunun neden benim için farklı çalıştığını bilmiyorum (belki MVC4)? Ama daha sonra model.Key = "" de yapmak zorunda kaldım. Her iki hat da gereklidir.
TTT

@PeterGluck yorumunu kaldırmanız için size iltifat etmek istiyorum. Tüm model durumunu temizlemekten daha iyidir (saklamak istediğim bazı alanlarda hatalarım olduğundan).
2016

6

ModelState, temelde doğrulama açısından modelin mevcut Durumunu tutar,

ModelErrorCollection: Model değerleri bağlamayı denediğinde oluşan hataları temsil eder. ex.

TryUpdateModel();
UpdateModel();

veya ActionResult'daki bir parametre gibi

public ActionResult Create(Person person)

ValueProviderResult : Modele bağlanma girişimiyle ilgili ayrıntıları tutun. ex. AttemptedValue, Culture, RawValue .

Clear () yöntemi, incelenmemiş sonuçlara yol açabileceğinden dikkatli kullanılmalıdır. Ve ModelState'in AttemptedValue gibi bazı güzel özelliklerini kaybedeceksiniz, bu MVC tarafından hata durumunda form değerlerini yeniden doldurmak için arka planda kullanılır.

ModelState["a"].Value.AttemptedValue

1
hmmm ... Görünüşüne göre sorunu anladığım yer burası olabilir. Model.SeoTitle özelliğinin değerini inceledim ve değişti, ancak denenen değer değişmedi. Görünüşe göre sayfada bir hata varmış gibi değeri yapıştırıyor (ModelState Sözlüğünü kontrol ettiniz ve hata yok).
Mr Grok

6

Toplanan bir formun modelini güncellemek istediğim bir örneğim vardı ve performans nedeniyle 'Eyleme Yönlendir' istemedim. Gizli alanların önceki değerleri güncellenmiş modelimde tutuluyordu - bu da her türlü soruna neden oluyordu !.

Birkaç satır kod kısa süre sonra ModelState içinde kaldırmak istediğim öğeleri belirledi (doğrulamadan sonra), bu nedenle yeni değerler şu biçimde kullanıldı: -

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

5

Pek çoğumuz bununla ısırılmış gibi görünüyor ve bunun olmasının nedeni mantıklı olsa da ModelState yerine Modelimdeki değerin gösterildiğinden emin olmak için bir yola ihtiyacım vardı.

Bazıları önerdi ModelState.Remove(string key), ancak keyözellikle iç içe geçmiş modeller için ne olması gerektiği açık değil . İşte buna yardımcı olmak için bulduğum birkaç yöntem.

RemoveStateForYöntem, bir alacak ModelStateDictionary, bir Model ve arzu edilen özellik için bir ifade ve çıkarın. HiddenForModelGörünümünüzde, önce ModelState girişini kaldırarak, yalnızca Modeldeki değeri kullanarak gizli bir girdi alanı oluşturmak için kullanılabilir. (Bu, diğer yardımcı uzatma yöntemleri için kolayca genişletilebilir).

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

Bir denetleyiciden şu şekilde arayın:

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

veya bunun gibi bir görünümden:

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

System.Web.Mvc.ExpressionHelperModelState özelliğinin adını almak için kullanır .


1
Çok hoş! ExpressionHelper işlevi için bunun üzerinde bir sekme tutmak.
Gerard

4

Tam olarak doğrulanmadıysa bir değeri güncellemek veya sıfırlamak istedim ve bu sorunla karşılaştım.

Kolay cevap, ModelState.Remove, .. sorunlu .. çünkü yardımcıları kullanıyorsanız adı gerçekten bilmiyorsunuz (adlandırma kuralına bağlı kalmadığınız sürece). Belki de hem özel yardımcınızın hem de denetleyicinizin bir ad almak için kullanabileceği bir işlev oluşturmadığınız sürece .

Bu özellik yardımcı üzerinde bir seçenek olarak uygulanmış olmalıdır, burada varsayılan olarak bunu yapmaz , ancak kabul edilmeyen girdinin yeniden görüntülenmesini istiyorsanız, bunu söyleyebilirsiniz.

Ama en azından sorunu şimdi anlıyorum;).


Bunu tam olarak yapmam gerekiyordu; bana Remove()doğru anahtara yardımcı olan yöntemlerimi aşağıda yayınladım .
Tobias J

0

Sonunda anladım. Kayıtlı olmayan ve bunu yapan My Custom ModelBinder:

var mymsPage = new MyCmsPage();

NameValueCollection frm = controllerContext.HttpContext.Request.Form;

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

Yani varsayılan model bağlamanın yaptığı bir şey soruna neden olmuş olmalı. Ne olduğundan emin değilim, ama sorunum en azından özel model bağlayıcım kaydedildiği için çözüldü.


Özel bir ModelBinder ile deneyimim yok, varsayılan olan şu ana kadar ihtiyaçlarıma uyuyor =).
JOBG

0

Genel olarak, kendinizi bir çerçeve standart uygulamalarına karşı savaşırken bulduğunuzda, yaklaşımınızı yeniden gözden geçirme zamanı gelmiştir. Bu durumda, ModelState davranışı. Örneğin, bir POST'tan sonra model durumunu istemediğinizde, get'e yönlendirmeyi düşünün.

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

Kültür yorumuna cevap vermek için DÜZENLENDİ:

İşte çok kültürlü bir MVC uygulamasını işlemek için kullandığım şey. Önce yol işleyici alt sınıfları:

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
}

Ve işte yolları nasıl bağladığım. Rotaları oluşturduktan sonra, alt ajanımı (example.com/subagent1, example.com/subagent2, vb.) Ve ardından kültür kodunu başa ekliyorum. İhtiyacınız olan tek şey kültürse, alt ajanı rota işleyicilerinden ve rotalardan kaldırmanız yeterlidir.

    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 uygulamasını önermekte çok haklısınız, aslında bunu hemen hemen her post eylemi için yapıyorum. Ancak çok özel bir ihtiyacım vardı: Sayfanın üstünde bir filtre formum var, başlangıçta get ile gönderilmişti. Ancak bir tarih alanının bağlı olmadığı bir sorunla karşılaştım ve sonra GET isteklerinin kültürü taşımadığını keşfettim (uygulamam için fransızca kullanıyorum), bu yüzden tarihimi başarılı bir şekilde bağlamak için isteği POST olarak değiştirmem gerekti. Sonra bu sorun geldi, ona biraz sıkıştım ..
Souhaieb Besbes

@SouhaiebBesbes Kültürü nasıl ele aldığımı gösteren güncellemelerime bakın.
B2K

@SouhaiebBesbes, kültürünüzü TempData'da saklamak belki biraz daha basit olabilir. Bkz stackoverflow.com/questions/12422930/...
B2K

0

Bu, Razor Sayfamda işe yarıyor gibiydi ve .cs dosyasına bir gidiş-dönüş bile yapmadı. Bu eski html yöntemidir. Faydalı olabilir.

<input type="reset" value="Reset">
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.