2009-05-04 4 views
5

ASP.NET MVC에서 서버 측 유효성 검사를 위해 데이터 주석을 사용하기 위해 DataAnnotationsModelBinder을 사용하려고합니다. 같은DataAnnotationsModelBinder는 사용자 정의 ViewModels과 어떻게 작동합니까?

public class Foo 
{ 
    public class Baz 
    { 
     public int Bar {get;set;} 
    } 

    public Baz MyBazProperty {get;set;} 
} 
으로, 복잡한 ViewModel를 사용하려고 할 때 내 뷰 모델은

그러나
public class Foo 
{ 
    public int Bar {get;set;} 
} 

즉각적인 특성을 가진 단순한 클래스로

다만큼 잘 작동의 DataAnnotationsModelBinderNullReferenceException 원인

형식이 지정되지 않은 ViewData 배열 대신 여러 LINQ 엔터티가 포함 된 사용자 지정 ViewModel을 사용하는 것이 더 좋기 때문에이 문제는 둘 이상의 LINQ 엔터티를 렌더링하는보기에서 큰 문제입니다. .

DefaultModelBinder에는이 문제가 없으므로 DataAnnotationsModelBinder의 버그 인 것 같습니다. 이 문제를 해결할 수있는 방법이 있습니까?

편집 : 가능한 해결 방법은이 같은 뷰 모델 클래스의 자식 객체의 속성을 노출 물론이다

public class Foo 
{ 
    private Baz myBazInstance; 

    [Required] 
    public string ExposedBar 
    { 
     get { return MyBaz.Bar; } 
     set { MyBaz.Bar = value; } 
    } 

    public Baz MyBaz 
    { 
     get { return myBazInstance ?? (myBazInstance = new Baz()); } 
     set { myBazInstance = value; } 
    } 

    #region Nested type: Baz 

    public class Baz 
    { 
     [Required] 
     public string Bar { get; set; } 
    } 

    #endregion 
} 

#endregion 

하지만이 모든 여분의 코드를 작성하지 않으려는 것입니다. DefaultModelBinder 같은 hiearchies 함께 잘 작동, 그래서 나는 DataAnnotationsModelBinder 가정해야합니다.

두 번째 편집 : 이것은 실제로 DataAnnotationsModelBinder의 버그입니다. 그러나 다음 ASP.NET MVC 프레임 워크 버전이 출시되기 전에 이것이 수정 될 수 있기를 바랍니다. 자세한 내용은 this forum thread을 참조하십시오.

+0

나는 비슷한 모델을 가지고 있지만 일반적으로 한 번에 개별 개체를 편집합니다. 예를 들면 : 일련의 첨부 파일 (이미지, PDF)이있을 수도 있고 없을 수도있는 Announcement 개체가 있지만 알림 만 편집하므로 유효성 검사기가 알림의 자식 개체로 내려 가도록 강요하지 마십시오. 그런 다음 자식 개체를 별도로 편집 할 것입니다. 동일한보기이지만 다른 POST 동작입니다. 나는 지금 관심이 있습니다 - 당신의 행동은 어떻게 거대한 대상 나무를 검증하는 것입니까? UI가 어떻게 작동합니까? –

+0

관련 문의 : 나는 http://devermind.com/linq/aspnet-mvc-using-custom-viewmodels-with-post-action-methods에서 이것을 수행하는 방법을 설명했다. 유효성에 관한 : 지금까지 나는 Scott Gu의 자습서 (http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx)에 설명 된 것과 같은 방식으로 서버 측 유효성 검사를 사용합니다. 내 컨트롤러는 다음과 같이 다른 엔터티에서 유효성 검사 오류를 수집합니다. ModelState.AddRuleViolations (model.User.GetRuleViolations(), "User"); ModelState.AddRuleViolations (model.Company.GetRuleViolations(), "Company"); –

+0

내 모델 바인더 코드의 어딘가에있는 버그라고 확신하지만, 그것을 추적 할 시간이 없었습니다. 더 많은 결과물이 나왔습니다. : –

답변

8

오늘 똑같은 문제에 직면했습니다. 자신과 마찬가지로 뷰에 내 모델을 직접 연결하지 않고 모델의 인스턴스와 뷰에 전송할 모든 매개 변수/구성을 보유하는 중간 ViewDataModel 클래스를 사용합니다.

NullReferenceException을 우회하기 위해 DataAnnotationsModelBinder에서 BindProperty을 수정했으며 개인적으로 유효했던 경우 속성이 바인딩되는 것을 좋아하지 않았습니다 (아래 이유 참조). 이 유효입니다 아닌지 항상이 재산에 상관없이 데이터를 결합 있도록

protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) { string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name); // Only bind properties that are part of the request if (bindingContext.ValueProvider.DoesAnyKeyHavePrefix(fullPropertyKey)) { var innerContext = new ModelBindingContext() { Model = propertyDescriptor.GetValue(bindingContext.Model), ModelName = fullPropertyKey, ModelState = bindingContext.ModelState, ModelType = propertyDescriptor.PropertyType, ValueProvider = bindingContext.ValueProvider }; IModelBinder binder = Binders.GetBinder(propertyDescriptor.PropertyType); object newPropertyValue = ConvertValue(propertyDescriptor, binder.BindModel(controllerContext, innerContext)); ModelState modelState = bindingContext.ModelState[fullPropertyKey]; if (modelState == null) { var keys = bindingContext.ValueProvider.FindKeysWithPrefix(fullPropertyKey); if (keys != null && keys.Count() > 0) modelState = bindingContext.ModelState[keys.First().Key]; } // Only validate and bind if the property itself has no errors //if (modelState.Errors.Count == 0) { SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue); if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) { OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue); } //} // There was an error getting the value from the binder, which was probably a format // exception (meaning, the data wasn't appropriate for the field) if (modelState.Errors.Count != 0) { foreach (var error in modelState.Errors.Where(err => err.ErrorMessage == "" && err.Exception != null).ToList()) { for (var exception = error.Exception; exception != null; exception = exception.InnerException) { if (exception is FormatException) { string displayName = GetDisplayName(propertyDescriptor); string errorMessage = InvalidValueFormatter(propertyDescriptor, modelState.Value.AttemptedValue, displayName); modelState.Errors.Remove(error); modelState.Errors.Add(errorMessage); break; } } } } } } 

나는 또한 그것을 수정했습니다. 이 방법을 사용하면 null로 재설정되는 잘못된 속성이있는 뷰에 모델을 다시 전달할 수 있습니다.

컨트롤러 발췌

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(ProfileViewDataModel model) 
{ 
    FormCollection form = new FormCollection(this.Request.Form); 
    wsPerson service = new wsPerson(); 
    Person newPerson = service.Select(1, -1); 
    if (ModelState.IsValid && TryUpdateModel<IPersonBindable>(newPerson, "Person", form.ToValueProvider())) 
    { 
     //call wsPerson.save(newPerson); 
    } 
    return View(model); //model.Person is always bound no null properties (unless they were null to begin with) 
} 

내 모델 클래스 (사람이) 그래서, 직접에 나는이 다음과 같다 해결 방법 속성을 넣을 수없는 웹 서비스에서 제공 :

을 내가 AF를 결합하면 중첩 된 DataAnnotations 이제

[Validation.MetadataType(typeof(PersonValidation))] 
public partial class Person : IPersonBindable { } //force partial. 

public class PersonValidation 
{ 
    [Validation.Immutable] 
    public int Id { get; set; } 
    [Validation.Required] 
    public string FirstName { get; set; } 
    [Validation.StringLength(35)] 
    [Validation.Required] 
    public string LastName { get; set; } 
    CategoryItemNullable NearestGeographicRegion { get; set; } 
} 

[Validation.MetadataType(typeof(CategoryItemNullableValidation))] 
public partial class CategoryItemNullable { } 

public class CategoryItemNullableValidation 
{ 
    [Validation.Required] 
    public string Text { get; set; } 
    [Validation.Range(1,10)] 
    public string Value { get; set; } 
} 

와 예 orm 필드를 [ViewDataModel.]Person.NearestGeographicRegion.Text & [ViewDataModel.]Person.NearestGeographicRegion.Value으로 변경하면 ModelState에서 해당 모델의 유효성 검사를 올바르게 시작하므로 DataAnnotationsModelBinder가 올바르게 바인딩됩니다.

이 대답은 오늘 오후 내 머리를 긁적 거리는 결과입니다. 브라이언 윌슨 (Brian Wilson)이 시작하고 대부분 제한된 테스트를 거친 후 the project 단위 테스트를 통과했지만 제대로 테스트되지 않았습니다. 이 문제에 대한 진정한 종결을 위해 나는이 솔루션에 대해 Brad Wilson의 의견을 듣고 싶습니다.

+0

브래드에게 버그 픽스에 대해 알려주도록하겠습니다. 아마도 좀 더 시간을 가질 수있을 것입니다. –

+0

멋지 네요. 당신의 커스터마이징 된 객체 배열에 바인딩하는 방법에 대한 마지막 힌트 (http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx 참조) 이 모델러와는 달리이 모델러는 색인 필드가 필요 없지만 실제로는 오류가 있지만 잘 작동하지는 않습니다. –

3

Martijn이 지적한 것처럼이 문제는 간단합니다. 하는 것입니다, MVC 2

if (modelState == null || modelState.Errors.Count == 0) { 

우리는 DataAnnotations을 포함하고자하는 지원 : 그것은 변경해야합니다

if (modelState.Errors.Count == 0) { 

다음 BindProperty 방법에

, 당신은이 코드 라인을 찾을 수 DataAnnotationsModelBinder를 포함하십시오. 이 기능은 첫 번째 CTP의 일부가 될 것입니다.

+0

Brad! –