2012-02-23 6 views
44

कहें कि मेरे पास एक उत्पाद मॉडल है, उत्पाद मॉडल में उत्पादसूब टाइप (सार) की एक संपत्ति है और हमारे पास दो ठोस कार्यान्वयन शर्ट और पैंट हैं।एमवीसी 3 मॉडल एक उप प्रकार (सार कक्षा या इंटरफेस) बाध्यकारी

यहाँ स्रोत है:

public class Product 
{ 
    public int Id { get; set; } 

    [Required] 
    public string Name { get; set; } 

    [Required] 
    public decimal? Price { get; set; } 

    [Required] 
    public int? ProductType { get; set; } 

    public ProductTypeBase SubProduct { get; set; } 
} 

public abstract class ProductTypeBase { } 

public class Shirt : ProductTypeBase 
{ 
    [Required] 
    public string Color { get; set; } 
    public bool HasSleeves { get; set; } 
} 

public class Pants : ProductTypeBase 
{ 
    [Required] 
    public string Color { get; set; } 
    [Required] 
    public string Size { get; set; } 
} 

मेरी UI में, उपयोगकर्ता एक ड्रॉपडाउन है, वे उत्पाद के प्रकार का चयन कर सकते हैं और इनपुट तत्वों सही उत्पाद के प्रकार के अनुसार प्रदर्शित होते हैं। मेरे पास यह सब पता लगाया गया है (अजाक्स का उपयोग ड्रॉपडाउन परिवर्तन पर मिलता है, आंशिक/संपादक टेम्पलेट लौटाता है और तदनुसार jquery सत्यापन को फिर से सेटअप करता है)।

अगला मैंने ProductTypeBase के लिए कस्टम मॉडल बाइंडर बनाया।

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
{ 

     ProductTypeBase subType = null; 

     var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); 

     if (productType == 1) 
     { 
      var shirt = new Shirt(); 

      shirt.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string)); 
      shirt.HasSleeves = (bool)bindingContext.ValueProvider.GetValue("SubProduct.HasSleeves").ConvertTo(typeof(bool)); 

      subType = shirt; 
     } 
     else if (productType == 2) 
     { 
      var pants = new Pants(); 

      pants.Size = (string)bindingContext.ValueProvider.GetValue("SubProduct.Size").ConvertTo(typeof(string)); 
      pants.Color = (string)bindingContext.ValueProvider.GetValue("SubProduct.Color").ConvertTo(typeof(string)); 

      subType = pants; 
     } 

     return subType; 

    } 
} 

यह सही ढंग से मूल्यों बांधता है और अधिकांश भाग के लिए काम करता है को छोड़कर मैं सर्वर साइड सत्यापन खो देते हैं। तो एक कूबड़ पर है कि मैं यह कर रहा हूं गलत तरीके से मैं कुछ अधिक खोज किया था और डैरिन दिमित्रोव से इस जवाब में आए:

ASP.NET MVC 2 - Binding To Abstract Model

तो मैं केवल CreateModel ओवरराइड करने के लिए मॉडल बांधने की मशीन बंद है, लेकिन अब ऐसा नहीं होता मूल्यों को बांधें।

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     ProductTypeBase subType = null; 

     var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); 

     if (productType == 1) 
     { 
      subType = new Shirt(); 
     } 
     else if (productType == 2) 
     { 
      subType = new Pants(); 
     } 

     return subType; 
    } 

हालांकि MVC 3 src कदम है, यह BindProperties में की तरह लगता है, GetFilteredModelProperties एक खाली परिणाम देता है, और मुझे लगता है कि क्योंकि bindingcontext मॉडल ProductTypeBase के लिए सेट है जो किसी भी गुण नहीं है।

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

किसी भी मदद के लिए धन्यवाद!

अद्यतन:

मैं यह स्पष्ट नहीं था, लेकिन कस्टम मॉडल बांधने की मशीन मैं कहा, से DefaultModelBinder

उत्तर

स्थापना ModelMetadata और मॉडल लापता टुकड़ा था इनहेरिट करती है। धन्यवाद मनस!

protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
     { 
      if (modelType.Equals(typeof(ProductTypeBase))) { 
       Type instantiationType = null; 

       var productType = (int)bindingContext.ValueProvider.GetValue("ProductType").ConvertTo(typeof(int)); 

       if (productType == 1) { 
        instantiationType = typeof(Shirt); 
       } 
       else if (productType == 2) { 
        instantiationType = typeof(Pants); 
       } 

       var obj = Activator.CreateInstance(instantiationType); 
       bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType); 
       bindingContext.ModelMetadata.Model = obj; 
       return obj; 
      } 

      return base.CreateModel(controllerContext, bindingContext, modelType); 

     } 

उत्तर

53

यह CreateModel (...) अधिभावी के माध्यम से प्राप्त किया जा सकता। मैं एक उदाहरण के साथ प्रदर्शित करेंगे।

1. आइए मॉडल बनाएं और कुछ आधार और बाल कक्षाएं बनाएं।

public class MyModel 
{ 
    public MyBaseClass BaseClass { get; set; } 
} 

public abstract class MyBaseClass 
{ 
    public virtual string MyName 
    { 
     get 
     { 
      return "MyBaseClass"; 
     } 
    } 
} 

public class MyDerievedClass : MyBaseClass 
{ 

    public int MyProperty { get; set; } 
    public override string MyName 
    { 
     get 
     { 
      return "MyDerievedClass"; 
     } 
    } 
} 

2. अब एक modelbinder बना सकते हैं और नियंत्रक में अब CreateModel

public class MyModelBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     /// MyBaseClass and MyDerievedClass are hardcoded. 
     /// We can use reflection to read the assembly and get concrete types of any base type 
     if (modelType.Equals(typeof(MyBaseClass))) 
     { 
      Type instantiationType = typeof(MyDerievedClass);     
      var obj=Activator.CreateInstance(instantiationType); 
      bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType); 
      bindingContext.ModelMetadata.Model = obj; 
      return obj; 
     } 
     return base.CreateModel(controllerContext, bindingContext, modelType); 
    } 

} 

3. ओवरराइड बनाने के लिए और पोस्ट कार्रवाई।

[HttpGet] 
public ActionResult Index() 
    { 
     ViewBag.Message = "Welcome to ASP.NET MVC!"; 

     MyModel model = new MyModel(); 
     model.BaseClass = new MyDerievedClass(); 

     return View(model); 
    } 

    [HttpPost] 
    public ActionResult Index(MyModel model) 
    { 

     return View(model); 
    } 

4. अब Global.asax में MyModelBinder डिफ़ॉल्ट ModelBinder के रूप में सेट यह एक एकल कार्रवाई हम कार्रवाई मानकों में ModelBinder विशेषता का उपयोग कर सकते हैं के लिए, सभी कार्यों के लिए एक डिफ़ॉल्ट मॉडल बांधने की मशीन स्थापित करने के लिए किया जाता है)

protected void Application_Start() 
    { 
     AreaRegistration.RegisterAllAreas(); 

     ModelBinders.Binders.DefaultBinder = new MyModelBinder(); 

     RegisterGlobalFilters(GlobalFilters.Filters); 
     RegisterRoutes(RouteTable.Routes); 
    } 

5. अब हम प्रकार MyModel की व्यू और प्रकार MyDerievedClass के एक आंशिक दृश्य बना सकते हैं

Index.cshtml

@model MvcApplication2.Models.MyModel 

@{ 
ViewBag.Title = "Index"; 
Layout = "~/Views/Shared/_Layout.cshtml"; 
} 

<h2>Index</h2> 

@using (Html.BeginForm()) { 
@Html.ValidationSummary(true) 
<fieldset> 
    <legend>MyModel</legend> 
    @Html.EditorFor(m=>m.BaseClass,"DerievedView") 
    <p> 
     <input type="submit" value="Create" /> 
    </p> 
</fieldset> 
} 

DerievedView.cshtml

@model MvcApplication2.Models.MyDerievedClass 

@Html.ValidationSummary(true) 
<fieldset> 
    <legend>MyDerievedClass</legend> 

    <div class="editor-label"> 
     @Html.LabelFor(model => model.MyProperty) 
    </div> 
    <div class="editor-field"> 
     @Html.EditorFor(model => model.MyProperty) 
     @Html.ValidationMessageFor(model => model.MyProperty) 
    </div> 

</fieldset> 

अब यह अपेक्षा के अनुरूप काम करेंगे, नियंत्रक प्रकार का एक ऑब्जेक्ट "MyDerievedClass" प्राप्त होगा। मान्यताओं की उम्मीद के अनुसार होगा।

enter image description here

+2

बिल्कुल सही, मॉडलमेटाडेटा और मॉडल को बनाने में मॉडल को याद करने वाला टुकड़ा था, धन्यवाद! –

+0

मेरे पास विरासत और व्युत्पन्न प्रकार के साथ एक बहुत ही समान समस्या थी और उपरोक्त मॉडल बाइंडर कोड ने चाल की थी। चीयर्स! –

+0

धन्यवाद! मैं अंततः इस समाधान का सहारा लेने के लिए लगभग तीन दिन कोशिश कर रहा हूं, हालांकि मेरी समस्या कुछ अलग थी। –

4

मैं एक ही समस्या थी, मैं sugested here रूप MvcContrib का उपयोग कर समाप्त हो गया।

documentation पुराना है लेकिन यदि आप नमूने देखते हैं तो यह बहुत आसान है।

आप Global.asax में अपने प्रकार के रजिस्टर करना होगा:

protected void Application_Start(object sender, EventArgs e) { 
    // (...) 
    DerivedTypeModelBinderCache.RegisterDerivedTypes(typeof(ProductTypeBase), new[] { typeof(Shirt), typeof(Pants) }); 
} 

अपने आंशिक दृश्य को दो पंक्तियों में जोड़ें: मुख्य दृश्य में,

@model MvcApplication.Models.Shirt 
@using MvcContrib.UI.DerivedTypeModelBinder 
@Html.TypeStamp() 
<div> 
    @Html.LabelFor(m => m.Color) 
</div> 
<div> 
    @Html.EditorFor(m => m.Color) 
    @Html.ValidationMessageFor(m => m.Color) 
</div> 

अंत में (EditorTemplates का उपयोग कर):

@model MvcApplication.Models.Product 
@{ 
    ViewBag.Title = "Products"; 
} 
<h2> 
    @ViewBag.Title</h2> 

@using (Html.BeginForm()) { 
    <div> 
     @Html.LabelFor(m => m.Name) 
    </div> 
    <div> 
     @Html.EditorFor(m => m.Name) 
     @Html.ValidationMessageFor(m => m.Name) 
    </div> 
    <div> 
     @Html.EditorFor(m => m.SubProduct) 
    </div> 
    <p> 
     <input type="submit" value="create" /> 
    </p> 
} 
1

अच्छी तरह से मुझे यह वही समस्या थी और मैंने सोचने के एक और सामान्य तरीके से हल किया है। मेरे मामले मैं वस्तु भेज रहा Json के माध्यम से बैकएंड से ग्राहक के लिए और ग्राहक से बैकएंड करने के लिए:

ClassDescriptor = this.GetType().AssemblyQualifiedName; 

तो Json में:

सभी सार कक्षा में सबसे पहले मैं क्षेत्र है कि मैं निर्माता में सेट है मैं ClassDescriptor क्षेत्र है

अगला बात कस्टम बांधने की मशीन लिखा था:

public class SmartClassBinder : DefaultModelBinder 
{ 
     protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
     { 

      string field = String.Join(".", new String[]{bindingContext.ModelName , "ClassDescriptor"}); 
       var values = (ValueProviderCollection) bindingContext.ValueProvider; 
       var classDescription = (string) values.GetValue(field).ConvertTo(typeof (string)); 
       modelType = Type.GetType(classDescription); 

      return base.CreateModel(controllerContext, bindingContext, modelType); 
     }  
} 

और अब सब मैं क्या करना है पर साथ वर्ग को सजाने के लिए है श्रद्धांजलि। उदाहरण के लिए:

[ModelBinder (typeof (SmartClassBinder))] सार्वजनिक वर्ग ConfigurationItemDescription

यह है कि।

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