ASP.NET MVC 베타 1 : DefaultModelBinder가 관련없는 요청간에 매개 변수 및 유효성 검사 상태를 잘못 유지합니다.

StackOverflow https://stackoverflow.com/questions/238460

문제

기본 모델 바인딩을 사용하여 양식 매개 변수를 동작의 매개 변수에 바인딩 할 때 프레임 워크는 첫 번째 요청으로 전달 된 값을 기억합니다. 즉, 해당 조치에 대한 후속 요청이 첫 번째 데이터와 동일한 데이터를 가져옵니다. 매개 변수 값과 유효성 검사 상태는 관련없는 웹 요청간에 지속됩니다.

다음은 내 컨트롤러 코드입니다 (service 앱의 백엔드에 대한 액세스를 나타냅니다) :

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        return View(RunTime.Default);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }

내 .aspx보기 (강하게 입력 한 ViewPage<RunTime>)는 다음과 같은 지시문을 포함합니다.

<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>

이것은 사용합니다 DefaultModelBinder 클래스입니다 내 모델의 속성을 자극하기위한 것입니다.

페이지를 치고 유효한 데이터를 입력합니다 (예 : Time = 1). 앱은 Time = 1으로 새 객체를 올바르게 저장합니다. 그런 다음 다시 누르면 다른 유효한 데이터를 입력하십시오 (예 : Time = 2). 그러나 저장된 데이터는 원본입니다 (예 : Time = 1). 이것은 또한 유효성 검사에 영향을 미치므로 원래 데이터가 유효하지 않은 경우 향후 입력 한 모든 데이터는 유효하지 않은 것으로 간주됩니다. IIS를 다시 시작하거나 코드를 재건하면 지속 된 상태가 플러시됩니다.

내 자신의 하드 코딩 된 모델 바인더를 작성하여 문제를 해결할 수 있습니다. 기본 순진한 예는 다음과 같습니다.

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }


internal class RunTimeBinder : DefaultModelBinder
{
    public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        // Without this line, failed validation state persists between requests
        bindingContext.ModelState.Clear();


        double time = 0;
        try
        {
            time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
        }
        catch (FormatException)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
        }

        var model = new RunTime(time);
        return new ModelBinderResult(model);
    }
}

내가 뭔가를 놓치고 있습니까? 첫 번째 데이터가 한 브라우저에 있고 두 번째 데이터가 다른 브라우저에 입력되면 문제를 재현 할 수 있기 때문에 브라우저 세션 문제라고 생각하지 않습니다.

도움이 되었습니까?

해결책

문제는 내 컨트롤러가 통화 사이에 재사용되고 있다는 것이 밝혀졌습니다. 원래 게시물에서 생략하기로 선택한 세부 사항 중 하나는 Castle.windsor 컨테이너를 사용하여 컨트롤러를 만들고 있다는 것입니다. 컨트롤러를 일시적인 라이프 스타일로 표시하지 못했기 때문에 각 요청마다 동일한 인스턴스를 다시 얻었습니다. 따라서 바인더가 사용하는 컨텍스트는 재사용되었으며 물론 오래된 데이터가 포함되어 있습니다.

Eilon의 코드와 광산의 차이를 신중하게 분석하면서 문제를 발견하여 다른 모든 가능성을 제거했습니다. 로서 성 문서화가 말합니다, 이것은 "끔찍한 실수"입니다! 이것이 다른 사람들에게 경고가되게하십시오!

응답에 감사드립니다. Eilon- 시간을내어 죄송합니다.

다른 팁

나는이 문제를 재현하려고 노력했지만 같은 행동을 보지 못했습니다. 나는 당신이 가지고있는 것과 거의 동일한 컨트롤러와보기를 만들었고 (일부 가정과 함께) 새로운 "런타임"을 만들 때마다 그 값을 Tempdata에 넣고 리디렉션을 통해 보냈습니다. 그런 다음 대상 페이지에서 나는 값을 잡았고 그것은 항상 그 요청에 입력 한 값이었습니다. 결코 오래된 가치가 아닙니다.

내 컨트롤러는 다음과 같습니다.

공개 클래스 HomeController : 컨트롤러 {public actionResult index () {viewData [ "title"] = "홈 페이지"; 문자열 메시지 = "환영 :" + tempdata [ "메시지"]; if (tempdata.containskey ( "value")) {int thevalue = (int) tempdata [ "value"]; 메시지 + = "" + theValue.toString (); } viewData [ "message"] = message; return view (); }

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
    return View(RunTime.Default);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
    if (ModelState.IsValid) {
        //service.CreateNewRun(newRunTime);
        TempData["Message"] = "New run created";
        TempData["value"] = newRunTime.TheValue;
        return RedirectToAction("index");
    }
    return View(newRunTime);
}

}

그리고 여기에 내 견해가 있습니다 (create.aspx) :

<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>

또한 "런타임"유형이 어떻게 생겼는지 잘 모르겠으므로 다음을 만들었습니다.

   public class RunTime {
        public static readonly RunTime Default = new RunTime(-1);

        public RunTime() {
        }

        public RunTime(int theValue) {
            TheValue = theValue;
        }

        public int TheValue {
            get;
            set;
        }
    }

런타임 구현에 정적 값이나 무언가가 포함될 수 있습니까?

감사,

에일론

이것이 관련되어 있는지 여부는 확실하지 않지만 < %= html.textbox ( "newruntime.time", ViewData.Model.Time)에 대한 귀하의 호출은 실제로 잘못된 과부하를 선택할 수 있습니다 (시간이 정수이므로 정수이기 때문에. 그것은 그것을 선택합니다 object htmlAttributes 오히려 과부하 string value.

렌더링 된 HTML을 확인하면 이것이 발생하는지 알려줍니다. int를 변경합니다 ViewData.Model.Time.ToString() 올바른 오버로드를 강요합니다.

당신의 문제가 다른 것처럼 들리지만, 나는 그것을 알아 차리고 과거에 화상을 입었습니다.

Seb, 나는 당신이 예를 들어 무슨 뜻인지 잘 모르겠습니다. 나는 Unity 구성에 대해 아무것도 모른다. Castle.Windsor와 함께 상황을 설명하고 아마도 Unity를 올바르게 구성하는 데 도움이 될 것입니다.

기본적으로 Castle.windsor는 주어진 유형을 요청할 때마다 동일한 객체를 반환합니다. 이것은 싱글 톤 라이프 스타일입니다. 다양한 라이프 스타일 옵션에 대한 좋은 설명이 있습니다. Castle.Windsor 문서.

ASP.NET MVC에서 컨트롤러 클래스의 각 인스턴스는 서비스를 위해 생성 된 웹 요청의 컨텍스트에 바인딩됩니다. 따라서 IOC 컨테이너가 매번 컨트롤러 클래스의 동일한 인스턴스를 반환하는 경우 컨트롤러 클래스를 사용한 첫 번째 웹 요청의 컨텍스트에 항상 컨트롤러가 연결됩니다. 특히, ModelState 그리고 DefaultModelBinder 재사용되므로 바인딩 모델 객체와 유효성 검사 메시지가 ModelState 오래 될 것입니다.

따라서 MVC가 컨트롤러 클래스의 인스턴스를 요청할 때마다 새 인스턴스를 반환하려면 IOC가 필요합니다.

Castle.windsor에서는 이것을 일시적인 라이프 스타일이라고합니다. 구성하려면 두 가지 옵션이 있습니다.

  1. XML 구성 : 컨트롤러를 나타내는 구성 파일의 각 요소에 lifestlye = "transient"를 추가합니다.
  2. 코드 내 구성 : 컨트롤러를 등록 할 때 컨테이너에 과도 라이프 스타일을 사용하도록 지시 할 수 있습니다. 이것이 Ben이 언급 한 MVCContrib 도우미가 귀하를 위해 자동으로 수행하는 일입니다. mvccontrib 소스 코드.

Unity는 Castle.windsor의 라이프 스타일에 비슷한 개념을 제공한다고 생각합니다. 따라서 컨트롤러의 일시적인 라이프 스타일에 해당하는 Unity를 구성해야합니다. mvccontrib에는 일부가있는 것으로 보입니다 통일 지원 - 어쩌면 당신은 거기서 볼 수 있습니다.

도움이 되었기를 바랍니다.

ASP.NET MVC 앱에서 Windsor IOC 컨테이너를 사용하려고 할 때 비슷한 문제가 발생했을 때 동일한 발견 항해를 통해 작동하도록해야했습니다. 다음은 다른 사람을 도울 수있는 세부 사항 중 일부입니다.

이것을 사용하는 것은 Global.asax의 초기 설정입니다.

  if (_container == null) 
  {
    _container = new WindsorContainer("config/castle.config");
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 
  }

컨트롤러 인스턴스를 요청할 때 다음과 같은 WindsorControllerFactory를 사용합니다.

  return (IController)_container.Resolve(controllerType);

Windsor가 모든 컨트롤러를 올바르게 연결하는 동안 어떤 이유로 든 매개 변수가 양식에서 관련 컨트롤러 동작으로 전달되지 않았습니다. 대신에 올바른 행동을 불러 내고 있었지만 그들은 모두 null이었다.

기본값은 컨테이너가 싱글 톤을 다시 전달하는 것입니다. 분명히 컨트롤러에 나쁜 것은 문제의 원인입니다.

http://www.castleproject.org/monorail/documentation/trunk/integration/windsor.html

그러나이 문서는 컨트롤러의 라이프 스타일을 과도로 변경할 수 있지만 구성 파일을 사용하는 경우 실제로 수행하는 방법을 알려주지는 않습니다. 충분히 쉽다는 것이 밝혀졌습니다.

<component 
  id="home.controller" 
  type="DoYourStuff.Controllers.HomeController, DoYourStuff" 
  lifestyle="transient" />

코드 변경 없이는 이제 예상대로 작동해야합니다 (즉, 컨테이너의 한 인스턴스가 제공하는 매번 고유 한 컨트롤러). 그런 다음 내가 알고있는 좋은 소년/소녀와 같은 코드 대신 구성 파일에서 모든 IOC 구성을 수행 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top