2009-03-19 18 views
7

क्या मेरे मॉडल पर एक फॉर्म इनपुट में विदेशी कुंजी संबंध बांधना संभव है?एएसपी.नेट एमवीसी मॉडल बाध्यकारी विदेशी कुंजी रिश्ते

कहें कि मेरे पास Car और Manufacturer के बीच एक से अधिक संबंध हैं। मैं Car को अपडेट करने के लिए एक फॉर्म बनाना चाहता हूं जिसमें Manufacturer सेट करने के लिए एक चयन इनपुट शामिल है। मैं अंतर्निहित मॉडल बाध्यकारी का उपयोग करके ऐसा करने में सक्षम होने की उम्मीद कर रहा था, लेकिन मुझे लगता है कि मुझे इसे स्वयं करना होगा।

मेरे कार्रवाई विधि हस्ताक्षर इस तरह दिखता है:

public JsonResult Save(int id, [Bind(Include="Name, Description, Manufacturer")]Car car) 

प्रपत्र पदों मान नाम, वर्णन और निर्माता, जहां निर्माता प्रकार int का एक प्राथमिक कुंजी है। नाम और विवरण ठीक से सेट हो जाते हैं, लेकिन निर्माता नहीं, जो समझ में आता है क्योंकि मॉडल बाइंडर को पता नहीं है कि पीके फ़ील्ड क्या है। क्या इसका मतलब है कि मुझे एक कस्टम IModelBinder लिखना होगा जो इससे अवगत है? मुझे यकीन नहीं है कि यह कैसे काम करेगा क्योंकि मेरे डेटा एक्सेस रिपॉजिटरीज़ को प्रत्येक Controller कन्स्ट्रक्टर पर आईओसी कंटेनर के माध्यम से लोड किया जाता है।

उत्तर

6

यहां मेरा ले लिया गया है - यह एक कस्टम मॉडल बाइंडर है कि जब GetPropertyValue से पूछा जाता है, यह देखने के लिए कि संपत्ति मेरे मॉडल असेंबली से एक वस्तु है या नहीं, और मेरे एन इंजेक्ट IKernel में पंजीकृत आईआरपीओसेटरी < है। यदि यह निनजेक्ट से आईरिपोजिटरी प्राप्त कर सकता है, तो यह विदेशी कुंजी ऑब्जेक्ट को पुनर्प्राप्त करने के लिए इसका उपयोग करता है।

public class ForeignKeyModelBinder : System.Web.Mvc.DefaultModelBinder 
{ 
    private IKernel serviceLocator; 

    public ForeignKeyModelBinder(IKernel serviceLocator) 
    { 
     Check.Require(serviceLocator, "IKernel is required"); 
     this.serviceLocator = serviceLocator; 
    } 

    /// <summary> 
    /// if the property type being asked for has a IRepository registered in the service locator, 
    /// use that to retrieve the instance. if not, use the default behavior. 
    /// </summary> 
    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, 
     PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) 
    { 
     var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (submittedValue == null) 
     { 
      string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, "Id"); 
      submittedValue = bindingContext.ValueProvider.GetValue(fullPropertyKey); 
     } 

     if (submittedValue != null) 
     { 
      var value = TryGetFromRepository(submittedValue.AttemptedValue, propertyDescriptor.PropertyType); 

      if (value != null) 
       return value; 
     } 

     return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); 
    } 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, "Id"); 
     var submittedValue = bindingContext.ValueProvider.GetValue(fullPropertyKey); 
     if (submittedValue != null) 
     { 
      var value = TryGetFromRepository(submittedValue.AttemptedValue, modelType); 

      if (value != null) 
       return value; 
     } 

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

    private object TryGetFromRepository(string key, Type propertyType) 
    { 
     if (CheckRepository(propertyType) && !string.IsNullOrEmpty(key)) 
     { 
      Type genericRepositoryType = typeof(IRepository<>); 
      Type specificRepositoryType = genericRepositoryType.MakeGenericType(propertyType); 

      var repository = serviceLocator.TryGet(specificRepositoryType); 
      int id = 0; 
#if DEBUG 
      Check.Require(repository, "{0} is not available for use in binding".FormatWith(specificRepositoryType.FullName)); 
#endif 
      if (repository != null && Int32.TryParse(key, out id)) 
      { 
       return repository.InvokeMethod("GetById", id); 
      } 
     } 

     return null; 
    } 

    /// <summary> 
    /// perform simple check to see if we should even bother looking for a repository 
    /// </summary> 
    private bool CheckRepository(Type propertyType) 
    { 
     return propertyType.HasInterface<IModelObject>(); 
    } 

} 

आपको स्पष्ट रूप से अपने डि कंटेनर और अपने खुद के भंडार प्रकार के लिए Ninject स्थानापन्न कर सकते हैं।

+2

बहुत उपयोगी उदाहरण! एक विचार जो मैं सुझाव दे सकता हूं, हालांकि, इस मॉडल बाइंडर को बाइंडर के अंदर जांचने के बजाय इस मॉडल बाइंडर को लक्षित करने के लिए 'IModelBinderProvider 'इंटरफ़ेस का उपयोग कर रहा है। ब्रैड विल्सन ने इस बारे में लिखा [यहां] (http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html)। –

+0

हाँ, यह बहुत अच्छा होगा। हालांकि, मैंने अभी तक एमवीसी 3 में अपडेट नहीं किया है। –

3

निश्चित रूप से प्रत्येक कार में केवल एक निर्माता होता है। यदि ऐसा है तो आपके पास एक निर्माता आईडी फ़ील्ड होना चाहिए जिससे आप चयन के मूल्य को बाध्य कर सकें। यही है, आपके चयन में निर्माता का नाम होना चाहिए क्योंकि यह टेक्स्ट और आईडी मान के रूप में है। अपने बचत मूल्य में, निर्माता के बजाय निर्माता आईडीआई बांधें।

<%= Html.DropDownList("ManufacturerID", 
     (IEnumerable<SelectListItem>)ViewData["Manufacturers"]) %> 

साथ

ViewData["Manufacturers"] = db.Manufacturers 
           .Select(m => new SelectListItem 
              { 
               Text = m.Name, 
               Value = m.ManufacturerID 
              }) 
           .ToList(); 

और

public JsonResult Save(int id, 
         [Bind(Include="Name, Description, ManufacturerID")]Car car) 
+1

यदि मॉडल पीओसीओ का उपयोग करके बनाया गया है, तो 'कार' पर 'निर्माता आईडी' संपत्ति होने पर मुझे सही नहीं लगता है।क्या यह वास्तव में इस तरह के मॉडल बाध्यकारी को हल करने का पसंदीदा तरीका है? –

+0

मुझे यकीन नहीं है कि आपका क्या मतलब है। आम तौर पर, मेरे पास कार और निर्माता इकाइयों से संबंधित एक विदेशी कुंजी क्षेत्र होगा। यह एक "आईडी" फ़ील्ड होने के लिए बहुत मानक है। मुझे लगता है कि आप इस मॉडल को अपने मॉडल में बेनकाब करने का विकल्प नहीं चुन सकते हैं, लेकिन आप निश्चित रूप से कर सकते हैं। मैं आमतौर पर LINQtoSQL का उपयोग करता हूं और मैं आपको आश्वस्त कर सकता हूं कि कार इकाई पर एक निर्माता आईडी और संबंधित निर्माता इकाई दोनों होगी। – tvanfosson

2

शायद यह एक देर से एक है, लेकिन आप इस लक्ष्य को हासिल करने के लिए एक कस्टम मॉडल बांधने की मशीन का उपयोग कर सकते हैं। आम तौर पर मैं इसे @tvanofosson जैसा ही करता हूं लेकिन मेरे पास एक ऐसा मामला था जहां मैं AspNetMembershipProvider टेबल पर उपयोगकर्ता विवरण जोड़ रहा था। चूंकि मैं केवल पीओसीओ का उपयोग करता हूं (और इसे एंटीटीफ्रेमवर्क से मैप करता हूं) मैं एक आईडी का उपयोग नहीं करना चाहता था क्योंकि यह व्यवसाय के दृष्टिकोण से उचित नहीं था इसलिए मैंने उपयोगकर्ताओं को जोड़ने/पंजीकरण करने के लिए केवल एक मॉडल बनाया। इस मॉडल में उपयोगकर्ता के लिए सभी गुण और एक रोल प्रॉपर्टी भी थी। मैं रोलमोडेल प्रतिनिधित्व के लिए भूमिका का एक टेक्स्ट नाम बांधना चाहता था। वह मूल रूप से है मैं क्या किया:

ModelBinders.Binders.Add(typeof(RoleModel), new RoleModelBinder()); 

और ध्यान में रखते हुए उपयोग::

<%= Html.DropDownListFor(model => model.Role, new SelectList(Model.Roles, "RoleName", "RoleName", Model.Role))%> 

मैं इस आशा

public class RoleModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     string roleName = controllerContext.HttpContext.Request["Role"]; 

     var model = new RoleModel 
          { 
           RoleName = roleName 
          }; 

     return model; 
    } 
} 

तब मैं Global.asax के लिए निम्न जोड़ने के लिए किया था तुम्हारी सहायता करता है।

+0

मैंने इसे एक प्रश्न के रूप में दोबारा पोस्ट किया है http://stackoverflow.com/questions/3642870/custom-model-binder-for-dropdownlist-not-selecting-crectrect-value। – nfplee

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