ประเภท Dynamic Anonymous ใน Razor ทำให้ RuntimeBinderException


156

ฉันได้รับข้อผิดพลาดต่อไปนี้:

'object' ไม่มีคำนิยามสำหรับ 'RatingName'

เมื่อคุณดูประเภทไดนามิกนิรนามจะมี RatingName อย่างชัดเจน

สกรีนช็อตของข้อผิดพลาด

ฉันรู้ว่าฉันสามารถทำได้ด้วย Tuple แต่ฉันต้องการที่จะเข้าใจว่าทำไมข้อผิดพลาดเกิดขึ้น

คำตอบ:


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.AnonymousObjectToHtmlAttributes โดยเฉพาะฉันรู้ว่านี่ต้องอบอย่างแน่นอนและไม่ต้องการบูรณาการล้อด้วยรหัส handrolled ที่คล้ายกัน
Chris Marisic

3
ประสิทธิภาพในการทำงานแบบนี้คืออะไรเมื่อเทียบกับการสร้างโมเดลสำรองที่แข็งแกร่ง
GONeale

@DotNetWise ทำไมคุณถึงใช้ HtmlHelper.AnonymousObjectToHtmlAttributes เมื่อคุณสามารถทำ IDictionary <string, object> anonymousDictionary = new RouteDictionary (object) ได้หรือไม่
Jeremy Boyd

ฉันได้ทดสอบ HtmlHelper.AnonymousObjectToHtmlAttributes และทำงานตามที่คาดไว้ โซลูชันของคุณยังสามารถทำงานได้ ใช้แล้วแต่จำนวนใดจะดูเหมือนง่าย :)
Adaptabi

ถ้าคุณต้องการให้มันเป็นทางออกถาวรคุณก็สามารถแทนที่พฤติกรรมในคอนโทรลเลอร์ของคุณได้ แต่ก็ต้องใช้วิธีการแก้ไขเพิ่มเติมอีกสองสามอย่างเช่นความสามารถในการระบุประเภทที่ไม่ระบุตัวตนและสร้างพจนานุกรมสตริง / วัตถุจากประเภทด้วยตัวเอง หากคุณทำเช่นนั้นคุณสามารถแทนที่ได้ใน: การป้องกันทับ System.Web.Mvc.ViewResult View (สตริงชื่อดู, สตริงต้นแบบชื่อรูปแบบวัตถุ)
Johny Skovdal

50

ผมพบคำตอบในคำถามที่เกี่ยวข้อง คำตอบนั้นถูกระบุไว้ในบล็อกโพสต์ของ David Ebbo การส่งวัตถุที่ไม่ระบุตัวตนไปยังมุมมอง MVC และเข้าถึงได้โดยใช้ไดนามิก

เหตุผลนี้เป็นเพราะประเภทที่ไม่ระบุชื่อถูกส่งผ่านในตัวควบคุมภายในดังนั้นจึงสามารถเข้าถึงได้จากภายในแอสเซมบลีที่มีการประกาศเท่านั้น เนื่องจากมุมมองถูกรวบรวมแยกกันตัวยึดแบบไดนามิกบ่นว่ามันไม่สามารถผ่านขอบเขตการประกอบได้

แต่ถ้าคุณคิดเกี่ยวกับมันข้อ จำกัด จาก dynamic binder นี้เป็นสิ่งที่ประดิษฐ์ขึ้นมาจริง ๆ เพราะถ้าคุณใช้การไตร่ตรองแบบส่วนตัวไม่มีอะไรหยุดคุณจากการเข้าถึงสมาชิกภายในเหล่านั้น (ใช่มันยังทำงานได้ใน Medium trust) ดังนั้นค่าเริ่มต้น binder แบบไดนามิกกำลังจะออกไปเพื่อบังคับใช้กฎการรวบรวม C # (ซึ่งคุณไม่สามารถเข้าถึงสมาชิกภายใน) แทนที่จะปล่อยให้คุณทำสิ่งที่ CLR รันไทม์อนุญาต


เอาชนะฉันได้ :) ฉันพบปัญหานี้ด้วยมีดโกนเครื่องยนต์ของฉัน (ผู้ตั้งต้นไปที่razorengine.codeplex.com )
Buildstarted

นี่ไม่ใช่คำตอบจริงๆไม่ใช่พูดเพิ่มเติมเกี่ยวกับ "คำตอบที่ยอมรับ"!
Adaptabi

4
@DotNetWise: มันอธิบายว่าทำไมข้อผิดพลาดเกิดขึ้นซึ่งเป็นคำถาม นอกจากนี้คุณยังจะได้รับ upvote ของฉันสำหรับการให้การแก้ปัญหาดี :)
ลูคัส

FYI: คำตอบนี้ล้าสมัยมาก - ตามที่ผู้เขียนบอกว่าตัวเองเป็นสีแดงที่จุดเริ่มต้นของการโพสต์บล็อกอ้างอิง
Simon_Weaver

@Simon_Weaver แต่การอัปเดตโพสต์ไม่ได้อธิบายว่าควรทำงานอย่างไรใน MVC3 + - ฉันประสบปัญหาเดียวกันใน MVC 4. พอยน์เตอร์ใด ๆ เกี่ยวกับวิธีการ 'มีความสุข' ในการใช้ไดนามิก?
Cristian Diaconescu

24

การใช้วิธีTo To Toเป็นทางออกที่ดีที่สุด

นี่คือรุ่นที่ไม่จำเป็นต้องมีการประกอบ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
codenheim

คุณสมบัติแบบไดนามิกที่ซ้อนกันคืออะไร พวกเขาจะยังคงเป็นพลวัต ... เช่น: `{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

ปกติแล้วฉันจะแนะนำให้ใช้แบบจำลองมุมมองที่พิมพ์ออกมาอย่างรุนแรงสำหรับมุมมองส่วนใหญ่ แต่บางครั้งความยืดหยุ่นนี้ก็มีประโยชน์


@yohal คุณทำได้อย่างแน่นอน - ฉันเดาว่ามันเป็นความชอบส่วนตัว ฉันชอบที่จะใช้ ViewBag สำหรับข้อมูลหน้าเบ็ดเตล็ดโดยทั่วไปไม่เกี่ยวข้องกับโมเดลหน้า - อาจเกี่ยวข้องกับเทมเพลตและเก็บ Model เป็นโมเดลหลัก
Simon_Weaver

2
BTW คุณไม่ต้องเพิ่ม @model dynamic เนื่องจากเป็นค่าเริ่มต้น
yoel halb

สิ่งที่ฉันต้องการใช้วิธีการแปลง anon objs เพื่อวัตถุ expando ใช้เวลามากเกินไป ...... ขอบคุณกอง
h-

5

คุณสามารถใช้ส่วนต่อประสานเฟรมเวิร์กเพื่อห่อประเภทที่ไม่ระบุชื่อในอินเทอร์เฟซ

คุณเพียงแค่ส่งคืนIEnumerable<IMadeUpInterface>และในตอนท้ายของ Linq ของคุณใช้งาน.AllActLike<IMadeUpInterface>();นี้เพราะมันเรียกคุณสมบัติที่ไม่ระบุชื่อโดยใช้ DLR ด้วยบริบทของการชุมนุมที่ประกาศประเภทที่ไม่ระบุชื่อ


1
เคล็ดลับเล็ก ๆ น้อย ๆ ที่น่ากลัว :) ไม่ทราบว่ามันเป็นเรื่องที่ดีกว่าเพียงแค่ระดับธรรมดาที่มีพวงของคุณสมบัติของประชาชน แต่อย่างน้อยในนี้กรณี
Andrew Backer

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

รหัสข้างต้นจะได้รับไฟล์ชุดประกอบจากอินพุต args และใช้ 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);
    }

ใน View, @model IEnumerable (ไดนามิกไม่ใช่คลาส model) นี่เป็นสิ่งที่สำคัญมากเพราะเราจะทำการผูก object ชนิดที่ไม่ระบุชื่อ

@model IEnumerable<dynamic>

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

ประเภทใน foreach ผมมีข้อผิดพลาดอย่างใดอย่างหนึ่งโดยใช้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 };
    }
}

การใช้งานในคอนโทรลเลอร์นั้นเหมือนกันยกเว้นคุณใช้ ToRazorDynamic () แทนที่จะเป็น To Expando ()

ในมุมมองของคุณเพื่อให้ได้วัตถุที่ไม่ระบุชื่อทั้งหมดคุณเพียงแค่เพิ่ม ".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 } };

ดังนั้นโซลูชันของฉันคือส่งคืนโมเดล JObject to View

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>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.