रेजर में गतिशील बेनामी प्रकार RuntimeBinderException का कारण बनता है


156

मुझे निम्नलिखित त्रुटि मिल रही है:

'ऑब्जेक्ट' में 'RatingName' की परिभाषा नहीं है

जब आप अनाम गतिशील प्रकार को देखते हैं, तो यह स्पष्ट रूप से RatingName होता है।

त्रुटि का स्क्रीनशॉट

मुझे लगता है कि मैं एक टपल के साथ ऐसा कर सकता हूं, लेकिन मैं यह समझना चाहूंगा कि त्रुटि संदेश क्यों होता है।

जवाबों:


240

आंतरिक गुणों वाले अनाम प्रकार मेरी राय में एक खराब .NET फ्रेमवर्क डिज़ाइन निर्णय है।

इस समस्या को ठीक करने के लिए एक त्वरित और अच्छा विस्तार दिया गया है अर्थात अनाम ऑब्जेक्ट को तुरंत एक ExpandoObject में परिवर्तित करके।

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> anonymousDictionary =  new RouteValueDictionary(anonymousObject);
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var item in anonymousDictionary)
        expando.Add(item);
    return (ExpandoObject)expando;
}

इसका उपयोग करना बहुत आसान है:

return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());

आपके विचार में:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}

2
+1 मैं विशेष रूप से HtmlHelper की तलाश कर रहा था। अनामिका नाम विशेषांक IHtmlAttributes मुझे पता था कि यह बिल्कुल पहले से ही बेक किया हुआ था और इसी तरह के नियंत्रित कोड के साथ पहिया को फिर से बनाना नहीं चाहता था।
क्रिस मैरिसिक

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

@DotNet Kind, आप HtmlHelper.AnonymousObjectToHtmlAttributes का उपयोग क्यों करेंगे जब आप बस IDEDIA कर सकते हैं <string, object> गुमनाम> नया मार्गनिर्देशक (ऑब्जेक्ट)?
जेरेमी बॉयड

मैंने HtmlHelper.AnonymousObjectToHtmlAttributes और उम्मीद के मुताबिक काम किया है। आपका समाधान भी काम कर सकता है। जो भी आसान लगता है का उपयोग करें :)
Adaptabi

यदि आप चाहते हैं कि यह एक स्थायी समाधान हो, तो आप अपने नियंत्रक में व्यवहार को ओवरराइड भी कर सकते हैं, लेकिन इसके लिए कुछ और वर्कअराउंड की आवश्यकता होती है, जैसे कि गुमनाम प्रकारों की पहचान करने में सक्षम होना और स्वयं द्वारा प्रकार से स्ट्रिंग / ऑब्जेक्ट शब्दकोश बनाना। यदि आप ऐसा करते हैं, तो आप इसे ओवरराइड कर सकते हैं: संरक्षित ओवरराइड System.Web.Mvc.ViewResult View (स्ट्रिंग दृश्यनाम, स्ट्रिंग मास्टरनाम, ऑब्जेक्ट मॉडल)
जॉनी स्कोवडाल

50

मुझे संबंधित प्रश्न में उत्तर मिला । यह उत्तर डेविड एब्बो के ब्लॉग पोस्ट पर निर्दिष्ट है जो गुमनाम वस्तुओं को एमवीसी के विचारों से गुजरता है और गतिशील का उपयोग करके उन्हें एक्सेस करता है

इसका कारण यह है कि अनाम प्रकार को आंतरिक में नियंत्रक में पारित किया जा रहा है, इसलिए इसे केवल उस विधानसभा के भीतर से एक्सेस किया जा सकता है जिसमें यह घोषित किया गया है। चूंकि विचारों को अलग-अलग संकलित किया जाता है, गतिशील बांधने वाला शिकायत करता है कि यह उस विधानसभा सीमा पर नहीं जा सकता है।

लेकिन अगर आप इसके बारे में सोचते हैं, तो गतिशील बांधने की मशीन से यह प्रतिबंध वास्तव में काफी कृत्रिम है, क्योंकि यदि आप निजी प्रतिबिंब का उपयोग करते हैं, तो कुछ भी आपको उन आंतरिक सदस्यों तक पहुंचने से रोक रहा है (हाँ, यह मध्यम विश्वास में भी काम करता है)। तो डिफ़ॉल्ट गतिशील बांधने की मशीन सी # संकलन नियमों (जहां आप आंतरिक सदस्यों तक नहीं पहुंच सकते हैं) को लागू करने के लिए अपने रास्ते से बाहर जा रहे हैं, बजाय आपको यह बताने के कि सीएलआर रनटाइम की अनुमति क्या देता है।


मुझे मारो इसे :) मैं अपने रेजर इंजन ( razorengine.codeplex.com पर एक के लिए अग्रदूत ) के साथ इस समस्या में भाग गया
निर्मित

यह वास्तव में एक उत्तर नहीं है, "स्वीकृत उत्तर" के बारे में अधिक नहीं कह रहा है!
अडाप्टाबी

4
@ डोटनेटवाइज: यह बताता है कि त्रुटि ऑकुर्र्स क्यों होती है, यह सवाल था। तुम भी एक अच्छा समाधान प्रदान करने के लिए मेरा उत्थान :)
लुकास

FYI करें: यह उत्तर अब बहुत पुराना है - जैसा कि लेखक ने संदर्भित ब्लॉग पोस्ट की शुरुआत में खुद को लाल कहा है
Simon_Weaver

@Simon_Weaver लेकिन पोस्ट अपडेट यह नहीं बताता है कि इसे MVC3 + में कैसे काम करना चाहिए। - मैं एमवीसी में एक ही समस्या को मारता हूं 4. गतिशील उपयोग करने के वर्तमान 'धन्य' तरीके पर कोई संकेत?
क्रिस्चियन डियाकोन्सकू

24

ToExpando का उपयोग करना विधि का सबसे अच्छा उपाय है।

यहाँ संस्करण है कि System.Web विधानसभा की आवश्यकता नहीं है :

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
    {
        var obj = propertyDescriptor.GetValue(anonymousObject);
        expando.Add(propertyDescriptor.Name, obj);
    }

    return (ExpandoObject)expando;
}

1
इसका बेहतर जवाब है। यह निश्चित नहीं है कि यदि वैकल्पिक उत्तर में HtmlHelper अंडरस्कोर के साथ क्या करता है।
डेन

सामान्य प्रयोजन के उत्तर के लिए +1, यह ASP / MVC के बाहर उपयोगी है
कोडेनहेम

नेस्टेड डायनेमिक गुणों के बारे में क्या? वे गतिशील बने रहेंगे ... उदाहरण के लिए: `{foo:" foo ", nestedDynamic: {blah:" blah "}}
खेल

16

एक गुमनाम प्रकार से एक मॉडल बनाने के बजाय और फिर अनाम वस्तु को ExpandoObjectइस तरह बदलने की कोशिश कर रहा है ...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

आप बस ExpandoObjectसीधे बना सकते हैं:

dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;

return View(model);

फिर आपके विचार में आप मॉडल प्रकार को गतिशील के रूप में सेट करते हैं @model dynamic और आप सीधे गुणों तक पहुँच सकते हैं:

@Model.Profile.Name
@Model.Foo

मैं आमतौर पर अधिकांश दृश्यों के लिए दृढ़ता से टाइप किए गए व्यू मॉडल की सिफारिश करता हूं, लेकिन कभी-कभी यह लचीलापन काम आता है।


@ निहाल आप निश्चित रूप से कर सकते हैं - मुझे लगता है कि यह व्यक्तिगत प्राथमिकता है। मैं आमतौर पर पेज मॉडल से असंबंधित पृष्ठ डेटा के लिए ViewBag का उपयोग करना पसंद करता हूं - शायद टेम्पलेट से संबंधित है और मॉडल को प्राथमिक मॉडल के रूप में रखता है
Simon_Weaver

2
BTW आपको @model डायनामिक नहीं जोड़ना है, क्योंकि यह डिफ़ॉल्ट है
yoel halb

वास्तव में मुझे क्या चाहिए, विस्तार करने के लिए विधि को लागू करने के लिए ऑब्जेक्ट्स को एक्सपो में बदलने में बहुत अधिक समय लग रहा था ...... धन्यवाद ढेर
h-rai

5

आप फ्रेमवर्क इंप्रोप्टु इंटरफ़ेस का उपयोग कर सकते हैं एक इंटरफ़ेस में एक अनाम प्रकार को लपेटने के का ।

आप बस वापस IEnumerable<IMadeUpInterface>आएँगे और अपने लाइनक के अंत में .AllActLike<IMadeUpInterface>();इस काम का उपयोग करेंगे क्योंकि यह गुमनाम संपत्ति को डीएलआर का उपयोग करके विधानसभा के संदर्भ के साथ कहता है जो गुमनाम प्रकार की घोषणा करता है।


1
बहुत कम ट्रिक :) पता नहीं कि यह सार्वजनिक संपत्तियों के एक समूह के साथ सिर्फ एक सादे वर्ग से बेहतर है, हालांकि कम से कम इस मामले में।
एंड्रयू बैकर

4

एक कंसोल एप्लिकेशन लिखें और संदर्भ के रूप में Mono.Cecil जोड़ें (अब आप इसे NuGet से जोड़ सकते हैं ), फिर कोड का टुकड़ा लिखें:

static void Main(string[] args)
{
    var asmFile = args[0];
    Console.WriteLine("Making anonymous types public for '{0}'.", asmFile);

    var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters
    {
        ReadSymbols = true
    });

    var anonymousTypes = asmDef.Modules
        .SelectMany(m => m.Types)
        .Where(t => t.Name.Contains("<>f__AnonymousType"));

    foreach (var type in anonymousTypes)
    {
        type.IsPublic = true;
    }

    asmDef.Write(asmFile, new WriterParameters
    {
        WriteSymbols = true
    });
}

ऊपर दिए गए कोड को इनपुट आर्ग से असेंबली फ़ाइल मिलेगी और आंतरिक से सार्वजनिक तक पहुंच बदलने के लिए Mono.Cecil का उपयोग करें, और इससे समस्या का समाधान होगा।

हम कार्यक्रम को वेबसाइट के पोस्ट बिल्ड इवेंट में चला सकते हैं। मैंने इसके बारे में चीनी में एक ब्लॉग पोस्ट लिखा था, लेकिन मुझे विश्वास है कि आप कोड और स्नैपशॉट पढ़ सकते हैं। :)


2

स्वीकृत उत्तर के आधार पर, मैंने इसे सामान्य रूप से और पर्दे के पीछे काम करने के लिए नियंत्रक में ओवरराइड किया है।

यहाँ कोड है:

protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
    base.OnResultExecuting(filterContext);

    //This is needed to allow the anonymous type as they are intenal to the assembly, while razor compiles .cshtml files into a seperate assembly
    if (ViewData != null && ViewData.Model != null && ViewData.Model.GetType().IsNotPublic)
    {
       try
       {
          IDictionary<string, object> expando = new ExpandoObject();
          (new RouteValueDictionary(ViewData.Model)).ToList().ForEach(item => expando.Add(item));
          ViewData.Model = expando;
       }
       catch
       {
           throw new Exception("The model provided is not 'public' and therefore not avaialable to the view, and there was no way of handing it over");
       }
    }
}

अब आप केवल एक अनाम वस्तु को मॉडल के रूप में पारित कर सकते हैं, और यह उम्मीद के मुताबिक काम करेगा।



0

RuntimeBinderException का कारण ट्रिगर हुआ, मुझे लगता है कि अन्य पोस्ट में अच्छा जवाब है। मैं सिर्फ यह समझाने के लिए ध्यान केंद्रित करता हूं कि मैं वास्तव में इसे कैसे काम करता हूं।

द्वारा @DotNetWise और जवाब देने के लिए उल्लेख ASP.NET MVC में बेनामी प्रकार संग्रह के साथ बाइंडिंग विचारों ,

सबसे पहले, विस्तार के लिए एक स्थिर वर्ग बनाएं

public static class impFunctions
{
    //converting the anonymous object into an ExpandoObject
    public static ExpandoObject ToExpando(this object anonymousObject)
    {
        //IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
        IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (var item in anonymousDictionary)
            expando.Add(item);
        return (ExpandoObject)expando;
    }
}

नियंत्रक में

    public ActionResult VisitCount()
    {
        dynamic Visitor = db.Visitors
                        .GroupBy(p => p.NRIC)
                        .Select(g => new { nric = g.Key, count = g.Count()})
                        .OrderByDescending(g => g.count)
                        .AsEnumerable()    //important to convert to Enumerable
                        .Select(c => c.ToExpando()); //convert to ExpandoObject
        return View(Visitor);
    }

दृश्य में, @model IEnumerable (गतिशील, मॉडल वर्ग नहीं), यह बहुत महत्वपूर्ण है क्योंकि हम अनाम प्रकार की वस्तु को बांधने जा रहे हैं।

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>x=@item.nric, y=@item.count</div>
}

फोरचेक में प्रकार, मुझे कोई भी त्रुटि नहीं है var या डायनामिक

वैसे, एक नया ViewModel बनाएं जो नए फ़ील्ड से मेल खा रहा हो, परिणाम को दृश्य में पास करने का तरीका भी हो सकता है।


0

अब पुनरावर्ती स्वाद में

public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        new RouteValueDictionary(obj).ForEach(o => expandoObject.Add(o.Key, o.Value == null || new[]
        {
            typeof (Enum),
            typeof (String),
            typeof (Char),
            typeof (Guid),

            typeof (Boolean),
            typeof (Byte),
            typeof (Int16),
            typeof (Int32),
            typeof (Int64),
            typeof (Single),
            typeof (Double),
            typeof (Decimal),

            typeof (SByte),
            typeof (UInt16),
            typeof (UInt32),
            typeof (UInt64),

            typeof (DateTime),
            typeof (DateTimeOffset),
            typeof (TimeSpan),
        }.Any(oo => oo.IsInstanceOfType(o.Value))
            ? o.Value
            : o.Value.ToExpando()));

        return (ExpandoObject) expandoObject;
    }

0

ExpandoObject एक्सटेंशन का उपयोग करते हुए नेस्टेड अनाम ऑब्जेक्ट्स का उपयोग करते समय टूट जाता है।

जैसे कि

var projectInfo = new {
 Id = proj.Id,
 UserName = user.Name
};

var workitem = WorkBL.Get(id);

return View(new
{
  Project = projectInfo,
  WorkItem = workitem
}.ToExpando());

इसे पूरा करने के लिए मैं इसका उपयोग करता हूं।

public static class RazorDynamicExtension
{
    /// <summary>
    /// Dynamic object that we'll utilize to return anonymous type parameters in Views
    /// </summary>
    public class RazorDynamicObject : DynamicObject
    {
        internal object Model { get; set; }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name.ToUpper() == "ANONVALUE")
            {
                result = Model;
                return true;
            }
            else
            {
                PropertyInfo propInfo = Model.GetType().GetProperty(binder.Name);

                if (propInfo == null)
                {
                    throw new InvalidOperationException(binder.Name);
                }

                object returnObject = propInfo.GetValue(Model, null);

                Type modelType = returnObject.GetType();
                if (modelType != null
                    && !modelType.IsPublic
                    && modelType.BaseType == typeof(Object)
                    && modelType.DeclaringType == null)
                {
                    result = new RazorDynamicObject() { Model = returnObject };
                }
                else
                {
                    result = returnObject;
                }

                return true;
            }
        }
    }

    public static RazorDynamicObject ToRazorDynamic(this object anonymousObject)
    {
        return new RazorDynamicObject() { Model = anonymousObject };
    }
}

कंट्रोलर में उपयोग एक ही है सिवाय इसके कि आप ToExpando () के बजाय ToRazorDynamic () का उपयोग करते हैं।

आपके द्वारा पूरी अनाम वस्तु को पाने के लिए आपके विचार में अंत में ".AnonValue" जोड़ें।

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;

0

मैंने ExpandoObject की कोशिश की लेकिन यह इस तरह एक नेस्टेड गुमनाम जटिल प्रकार के साथ काम नहीं किया:

var model = new { value = 1, child = new { value = 2 } };

तो मेरा समाधान एक मॉडल को देखने के लिए अस्वीकार करना था:

return View(JObject.FromObject(model));

और गतिशील .cshtml में परिवर्तित करें:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.