2009-05-04 19 views
5

मैं एएसपी.नेट एमवीसी में सर्वर-साइड सत्यापन के लिए डेटा एनोटेशन का उपयोग करने के लिए DataAnnotationsModelBinder का उपयोग करने का प्रयास कर रहा हूं। जब इस तरह केDataAnnotationsModelBinder कस्टम व्यू मॉडेल के साथ कैसे काम करता है?

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

    public Baz MyBazProperty {get;set;} 
} 
के रूप में, एक जटिल ViewModel उपयोग करने की कोशिश

सब कुछ जब तक ठीक काम करता है के रूप में मेरे ViewModel ऐसे

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

हालांकि के रूप में तत्काल गुणों के साथ सिर्फ एक सरल वर्ग है, DataAnnotationsModelBinder एक NullReferenceException का कारण बनता है

यह उन दृश्यों के लिए एक बड़ी समस्या है जो एक से अधिक LINQ इकाई प्रदान करते हैं क्योंकि मैं वास्तव में कस्टम ViewModel एस का उपयोग करना पसंद करता हूं जिसमें अनियमित व्यूडाटा सरणी के बजाय कई LINQ इकाइयां शामिल हैं ।

DefaultModelBinder में यह समस्या नहीं है, इसलिए यह DataAnnotationsModelBinder में एक बग की तरह लगता है। क्या इसका कोई कामकाज है?

संपादित करें: एक संभावित समाधान, इस तरह ViewModel कक्षा में बच्चे ऑब्जेक्ट के गुणों को बेनकाब करने के निश्चित रूप से है:

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 ऐसी हाइर्चियों के साथ ठीक काम करता है, इसलिए मुझे लगता है कि DataAnnotationsModelBinder भी चाहिए।

दूसरा संपादित करें: ऐसा लगता है कि यह वास्तव में DataAnnotationsModelBinder में एक बग है। हालांकि, आशा है कि यह अगले एएसपी.नेट एमवीसी फ्रेमवर्क संस्करण जहाजों से पहले तय किया जा सकता है। अधिक जानकारी के लिए this forum thread देखें।

+0

मेरे पास एक समान मॉडल है लेकिन आम तौर पर एक समय में अलग-अलग वस्तुओं को संपादित करता है। उदाहरण के लिए: मेरे पास एक घोषणा ऑब्जेक्ट है जिसमें संलग्नक (छवियों, पीडीएफ) की श्रृंखला हो सकती है या नहीं, लेकिन मैं घोषणा स्वयं को संपादित कर सकता हूं और इसलिए सत्यापनकर्ता को घोषणा की बाल वस्तुओं में उतरने के लिए मजबूर नहीं किया जाता है। मैं फिर बच्चे वस्तुओं को अलग से संपादित करूंगा - एक ही दृश्य, लेकिन अलग पोस्ट कार्रवाई। मुझे अब दिलचस्पी है - आपके कार्य कैसे काम करते हैं कि आप विशाल वस्तु पेड़ों को मान्य करते हैं? और यूआई इसके लिए कैसे काम करता है? –

+0

दृश्यों के बारे में: मैंने वर्णन किया है कि यह कैसे करें http://devermind.com/linq/aspnet-mvc-using-custom-viewmodels-with-post-action-methods प्रमाणीकरण के संबंध में: अब तक मैं रहा हूं स्कॉट ग के ट्यूटोरियल में वर्णित तरीके से सर्वर-साइड सत्यापन का उपयोग करना: http://weblogs.asp.net/scottgu/archive/2009/03/10/free-asp-net-mvc-ebook-tutorial.aspx। मेरा नियंत्रक तब विभिन्न इकाइयों से सत्यापन त्रुटियों को इकट्ठा करता है: मॉडलस्टेट। AddRuleViolations (model.User.GetRuleViolations(), "उपयोगकर्ता"); ModelState.AddRuleViolations (model.Company.GetRuleViolations(), "कंपनी"); –

+0

मुझे यकीन है कि यह मेरे मॉडल बाइंडर कोड में कहीं भी एक बग है, लेकिन मेरे पास इसे ट्रैक करने का समय नहीं है। अधिक दबाव देने वाले डिलिवरेबल्स रास्ते में हैं। :( –

उत्तर

8

मुझे आज भी एक ही समस्या का सामना करना पड़ा। अपने आप की तरह मैं अपने मॉडल को सीधे अपने मॉडल से जोड़ता हूं लेकिन एक इंटरमीडिएट व्यूडाटा मॉडेल क्लास का उपयोग करता हूं जिसमें मॉडल का उदाहरण होता है और किसी भी पैरामीटर/कॉन्फ़िगरेशन जिन्हें मैं दृश्य में भेजना चाहता हूं।

मैंने को NullReferenceException को बाधित करने के लिए डेटा एन्नोटेशन मॉडेलबिन्डर पर संशोधित करना समाप्त कर दिया, और मैं व्यक्तिगत रूप से गुणों को पसंद नहीं करता था अगर वे वैध थे (नीचे कारण देखें)।

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

मैं भी इसे संशोधित इतना है कि यह हमेशा डेटा संपत्ति पर कोई फर्क नहीं पड़ता बांधता है अगर यह वैध है या नहीं। इस तरह से मैं सिर्फ मॉडल को वापस अमान्य अमान्य गुणों को रीसेट करने के लिए रीसेट कर सकता हूं।

नियंत्रक अंश

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

मेरे मॉडल वर्ग (व्यक्ति) एक वेब सेवा से आता है तो मैं नहीं विशेषताओं उन पर सीधे तरह से मैं इस प्रकार है हल डाल सकते हैं,:

नेस्टेड 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; } 
} 

अब अगर मैं वायुसेना बाँध के साथ उदाहरण ओआरएम फ़ील्ड [ViewDataModel.]Person.NearestGeographicRegion.Text & [ViewDataModel.]Person.NearestGeographicRegion.Value मॉडलस्टेट सही ढंग से उन्हें सत्यापित करना शुरू कर देता है और डेटा एन्नोटेशन मॉडेलबिन्डर उन्हें सही तरीके से बांधता है।

यह उत्तर निश्चित नहीं है, यह आज दोपहर मेरे सिर को खरोंचने का उत्पाद है। यह ठीक तरह से परीक्षण नहीं किया गया है, हालांकि यह the project में यूनिट परीक्षण पास कर दिया गया है ब्रायन विल्सन ने शुरू किया और मेरा अधिकांश सीमित परीक्षण। इस मामले पर सच्चे बंद होने के लिए मुझे इस समाधान पर Brad Wilson विचार सुनना अच्छा लगेगा।

+0

मेरे लिए भी बहुत अच्छा काम करने लगता है, कम से कम मेरे पहले त्वरित परीक्षण द्वारा निर्णय लेना। बहुत बहुत धन्यवाद! मैं ब्रैड को आपके बगफिक्स के बारे में बता दूंगा, शायद उसके पास देखने के लिए कुछ समय होगा। –

+0

कूल अच्छा यह आपके लिए भी काम कर रहा है। एक अंतिम संकेत अगर आप कस्टम ऑब्जेक्ट सरणी से जुड़ते हैं (देखें: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx) इस आलेख के विपरीत इस मॉडलबिंडर के साथ उल्लेख है कि आपको इंडेक्स फ़ील्ड की आवश्यकता नहीं है, वास्तव में यह त्रुटि होगी लेकिन बिना ठीक काम करता है। –

3

इस मुद्दे के लिए ठीक सरल है, जैसा कि मार्टिजन ने नोट किया है।

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

यह करने के लिए बदला जाना चाहिए:

BindProperty विधि में, आप कोड की इस पंक्ति मिलेगा MVC 2 में

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

हम DataAnnotations शामिल करने के लिए इच्छुक रहे हैं का समर्थन करते हैं, जो होगा DataAnnotationsModelBinder शामिल करें। यह सुविधा पहले सीटीपी का हिस्सा होगी।

+0

धन्यवाद, ब्रैड! :) –

संबंधित मुद्दे