Asp.net MVC ModelState.Clear


116

누구든지 Asp.net MVC에서 ModelState의 역할 (또는 링크)에 대한 간결한 정의를 줄 수 있습니까? 특히 어떤 상황에서 전화가 필요하거나 바람직한 지 알아야합니다 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>

이 오래된 데이터를 캐시 싶다면 멍청한 놈 AspMVC는 다시 사용자에게 모델을주는 시점 무엇 : 난 같은 문제가, 덕분에 많은 형제했다 @
deadManN

답변:


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의 HTML 도우미는 잘못된 값 렌더링
지하철 스머프

8
이봐, 벌써 2 시간 동안 싸웠어. 이 답변을 게시 해 주셔서 감사합니다!
Andrey Agibalov

37
이것은 여전히 ​​사실이며 저를 포함한 많은 사람들이 이로 인해 많은 시간을 낭비하고 있습니다. 버그 또는 설계 상, 상관 없어요. "예기치 않은"문제입니다.

7
나는이 "기능"미래에 제거 희망 @Proviste에 동의

8
나는 이것에 4 시간을 보냈다. 추한.
Brian MacKay 2011

46

최신 정보:

  • 이것은 버그가 아닙니다.
  • View()POST 작업에서 반환 을 중지하십시오 . 대신 PRG 를 사용 하고 작업이 성공하면 GET으로 리디렉션 하십시오 .
  • 당신이하면 된다 을 반환 View()후 조치에서 양식 유효성 검사를 수행하고 그에게 길을 어떻게 MVC가 설계 헬퍼 내장 사용합니다. 이렇게하면 사용할 필요가 없습니다..Clear()
  • 이 작업을 사용하여 SPA에 대한 ajax를 반환 하는 경우 웹 API 컨트롤러를 ModelState사용하고 어쨌든 사용해서는 안되므로 잊어 버립니다 .

이전 답변 :

MVC의 ModelState는 주로 해당 개체가 유효한지 여부와 관련하여 모델 개체의 상태를 설명하는 데 주로 사용됩니다. 이 튜토리얼 은 많은 것을 설명해야합니다.

일반적으로 ModelState는 MVC 엔진에서 유지 관리하므로 지울 필요가 없습니다. 수동으로 지우면 MVC 유효성 검사 모범 사례를 준수하려고 할 때 원하지 않는 결과가 발생할 수 있습니다.

제목의 기본값을 설정하려는 것 같습니다. 이것은 모델 객체가 인스턴스화 될 때 (어딘가에있는 도메인 레이어 또는 객체 자체-매개 변수없는 ctor) get 액션에서 처음으로 페이지로 내려가거나 클라이언트에서 완전히 내려갈 때 (ajax 또는 무언가를 통해) 수행되어야합니다. 사용자가 입력 한 것처럼 나타나고 게시 된 양식 컬렉션으로 돌아옵니다. 양식 컬렉션을받을 때이 값을 추가하는 방법 (POST 작업 // 편집에서)이이 기괴한 동작을 유발 하여 작동 하는 .Clear() 것처럼 보일 수 있습니다. 저를 믿으십시오-당신은 클리어를 사용하고 싶지 않습니다. 다른 아이디어 중 하나를 시도하십시오.


1
내 서비스 레이어를 약간 재고하는 데 도움이되지만 (신음하지만 thx) 인터넷에있는 많은 것들과 마찬가지로 유효성 검사를 위해 ModelState를 사용하는 관점에 크게 기울어집니다.
Mr Grok

나는 특히 ModelState.Clear (관심) 내 쿼리에 대한 이유있어 이유를 보여 질문에 더 많은 정보가 추가
씨 grok 수

5
[HttpPost] 함수에서 View (...) 반환 을 중지 하기 위해이 인수를 구매하지 않습니다 . ajax를 통해 콘텐츠를 게시 한 다음 결과 PartialView로 문서를 업데이트하는 경우 MVC ModelState가 잘못된 것으로 표시됩니다. 내가 찾은 유일한 해결 방법은 컨트롤러 메서드에서 지우는 것입니다.
아론 Hudon

@AaronHudon PRG는 꽤 잘 확립되어 있습니다.
Matt Kocaj 2016 년

AJAX 호출로 POST하는 경우 GET 작업으로 리디렉션하고 OP가 원하는 것처럼 모델로 채워진 뷰를 모두 비동기 적으로 반환 할 수 있습니까?
MyiEye

17

개별 필드의 값을 지우려면 다음 기술이 유용하다는 것을 알았습니다.

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

참고 : "키"를 재설정하려는 필드의 이름으로 변경하십시오.


왜 이것이 저에게 다르게 작동했는지 모르겠습니다 (아마도 MVC4)? 하지만 나중에 model.Key = ""도해야했습니다. 두 줄이 모두 필요합니다.
TTT

@PeterGluck의 댓글 제거에 대해 칭찬하고 싶습니다. 전체 모델 상태를 지우는 것보다 낫습니다 (유지하고 싶은 일부 필드에 오류가 있기 때문에).
Tjab

6

ModelState는 기본적으로 유효성 검사 측면에서 모델의 현재 상태를 유지합니다.

ModelErrorCollection : 모델이 값을 바인딩하려고 할 때 오류를 나타냅니다. 전의.

TryUpdateModel();
UpdateModel();

또는 ActionResult의 매개 변수처럼

public ActionResult Create(Person person)

ValueProviderResult : 시도 된 모델 바인딩에 대한 세부 정보를 보유합니다. 전의. AttemptedValue, Culture, RawValue .

Clear () 메서드는 검사하지 않은 결과를 초래할 수 있으므로주의해서 사용해야합니다. 그리고 AttemptedValue와 같은 ModelState의 멋진 속성을 잃게 될 것입니다. 이것은 백그라운드에서 MVC가 오류 발생시 양식 값을 다시 채우는 데 사용됩니다.

ModelState["a"].Value.AttemptedValue

1
흠 ... 그게 내가보기에 문제가되는 곳일지도 몰라요. Model.SeoTitle 속성의 값을 검사했지만 변경되었지만 시도 된 값은 변경되지 않았습니다. 페이지에 오류가 없는데도 오류가있는 것처럼 값을 고정하는 것처럼 보입니다 (ModelState 사전을 확인했고 오류가 없음).
Mr Grok

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가 아닌 Model의 값이 표시되도록하는 방법이 필요했습니다.

일부는을 제안 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.ExpressionHelperModelState 속성의 이름을 가져 오는 데 사용 합니다.


1
아주 좋아요! ExpressionHelper 기능에 대한 탭을 유지합니다.
Gerard ONeill

4

유효성이 검증되지 않은 경우 값을 업데이트하거나 재설정하고 싶었고이 문제가 발생했습니다.

쉬운 대답 인 ModelState.Remove는 문제가 있습니다. 도우미를 사용하는 경우 이름을 실제로 알지 못하기 때문입니다 (이름 지정 규칙을 따르지 않는 한). 사용자 지정 도우미와 컨트롤러가 이름을 가져 오는 데 사용할 수 있는 함수를 만들지 않는 한 .

이 기능은 도우미의 옵션으로 구현 되었어야합니다. 기본적으로는 이 작업을 수행 하지 않지만 승인되지 않은 입력을 다시 표시하려면 그렇게 말할 수 있습니다.

그러나 적어도 나는 지금 문제를 이해합니다.).


나는 이것을 정확히해야했다. Remove()올바른 키 를 얻는 데 도움이 된 아래 게시 된 방법을 참조하십시오 .
Tobias J

0

결국 알았습니다. 등록되지 않은 내 Custom 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()));
            }
        }

    }

당신은 POST REDIRECT 연습을 제안하는 것이 옳습니다. 사실 저는 거의 모든 포스트 액션에 이것을합니다. 그러나 나는 매우 특별한 필요가 있었다. 페이지 상단에 필터 양식이 있는데 처음에는 get으로 제출했다. 그러나 날짜 필드가 바인딩되지 않은 문제가 발생하여 GET 요청이 문화를 전달하지 않는다는 것을 발견했습니다 (내 앱에 프랑스어를 사용합니다). 그래서 요청을 POST로 전환하여 날짜를 성공적으로 바인딩해야했습니다.
그러다가이

@SouhaiebBesbes 내가 문화를 다루는 방법을 보여주는 업데이트를 참조하십시오.
B2K

@SouhaiebBesbes는 아마도 TempData에 문화를 저장하는 것이 조금 더 간단 할 것입니다. stackoverflow.com/questions/12422930/… 참조
B2K

0

글쎄, 이것은 내 Razor Page에서 작동하는 것처럼 보였고 .cs 파일로의 왕복조차도하지 않았습니다. 이것은 오래된 HTML 방식입니다. 유용 할 수 있습니다.

<input type="reset" value="Reset">
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.