Kiểu ẩn danh động trong Dao cạo gây ra RuntimeBinderException


156

Tôi nhận được lỗi sau:

'đối tượng' không chứa định nghĩa cho 'RatingName'

Khi bạn nhìn vào loại động ẩn danh, nó rõ ràng có RatingName.

Ảnh chụp màn hình của Lỗi

Tôi nhận ra tôi có thể làm điều này với một Tuple, nhưng tôi muốn hiểu tại sao thông báo lỗi xảy ra.

Câu trả lời:


240

Theo tôi, các kiểu ẩn danh có một quyết định thiết kế khung .NET kém.

Đây là một phần mở rộng nhanh chóng và tốt đẹp để khắc phục vấn đề này, tức là bằng cách chuyển đổi đối tượng ẩn danh thành ExpandoObject ngay lập tức.

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

Nó rất dễ sử dụng:

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

Tất nhiên theo quan điểm của bạn:

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

2
+1 Tôi đặc biệt tìm kiếm HtmlHelper.AnonymousObjectToHtmlAttribution Tôi biết điều này hoàn toàn phải nướng trong đó và không muốn phát minh lại bánh xe với mã số được điều khiển tương tự.
Chris Marisic

3
Hiệu suất như thế này là gì, so với việc đơn giản tạo ra một mô hình sao lưu được gõ mạnh?
GONeale

@DotNetWise, Tại sao bạn lại sử dụng HtmlHelper.AnonymousObjectToHtmlAttribution khi bạn chỉ có thể thực hiện IDadata <string, object> nodeDixi = new RouteDipedia (object)?
Jeremy Boyd

Tôi đã thử nghiệm HtmlHelper.AnonymousObjectToHtmlAttribution và hoạt động như mong đợi. Giải pháp của bạn cũng có thể làm việc. Sử dụng bất cứ điều gì có vẻ dễ dàng hơn :)
Adaptabi

Nếu bạn muốn nó là một giải pháp lâu dài, bạn cũng có thể ghi đè hành vi trong bộ điều khiển của mình, nhưng nó đòi hỏi một vài cách giải quyết khác, như có thể tự xác định các loại ẩn danh và tạo từ điển chuỗi / đối tượng từ loại đó. Nếu bạn làm điều đó, bạn có thể ghi đè lên: ghi đè được bảo vệ System.Web.Mvc.ViewResult View (chuỗi viewName, chuỗi masterName, mô hình đối tượng)
Johny Skovdal

50

Tôi tìm thấy câu trả lời trong một câu hỏi liên quan . Câu trả lời được chỉ định trên bài đăng trên blog của David Ebbo Chuyển các đối tượng ẩn danh sang chế độ xem MVC và truy cập chúng bằng cách sử dụng động

Lý do cho điều này là loại ẩn danh được truyền trong bộ điều khiển bên trong, vì vậy nó chỉ có thể được truy cập từ bên trong tổ hợp mà nó được khai báo. Vì các khung nhìn được biên dịch riêng, nên chất kết dính động phàn nàn rằng nó không thể đi qua ranh giới lắp ráp đó.

Nhưng nếu bạn nghĩ về nó, hạn chế này từ chất kết dính động thực sự khá giả tạo, bởi vì nếu bạn sử dụng phản xạ riêng tư, không có gì ngăn bạn truy cập vào các thành viên nội bộ đó (vâng, nó thậm chí hoạt động ở mức tin cậy trung bình). Vì vậy, chất kết dính động mặc định sẽ không sử dụng các quy tắc biên dịch C # (nơi bạn không thể truy cập các thành viên nội bộ), thay vì cho phép bạn làm những gì thời gian chạy CLR cho phép.


Hãy đánh bại tôi :) Tôi đã gặp phải vấn đề này với Công cụ Dao cạo của tôi (tiền thân của một trong số razorengine.codeplex.com )
Buildstarted

Đây không thực sự là một câu trả lời, không nói thêm về "câu trả lời được chấp nhận"!
Adaptabi

4
@DotNetWise: Nó giải thích tại sao các lỗi phát sinh, đó là câu hỏi. Bạn cũng nhận được upvote của tôi để cung cấp một cách giải quyết tốt đẹp :)
Lucas

FYI: câu trả lời này hiện đã lỗi thời - vì tác giả nói rằng bản thân anh ta màu đỏ ở đầu bài viết trên blog được tham chiếu
Simon_Weaver

@Simon_Weaver Nhưng bản cập nhật bài đăng không giải thích cách hoạt động của nó trong MVC3 +. - Tôi gặp vấn đề tương tự trong MVC 4. Có con trỏ nào trên cách sử dụng động hiện tại 'may mắn' không?
Cristian Diaconescu

24

Sử dụng phương pháp ToExpando là giải pháp tốt nhất.

Đây là phiên bản không yêu cầu lắp ráp 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
Đó là một câu trả lời tốt hơn. Không chắc chắn nếu như những gì HtmlHelper làm với dấu gạch dưới trong câu trả lời thay thế.
Den

+1 cho câu trả lời cho mục đích chung, điều này hữu ích bên ngoài ASP / MVC
codenheim

những gì về tính chất động lồng nhau? họ sẽ tiếp tục năng động ... ví dụ: `{foo:" foo ", lồng nhauDocate: {blah:" blah "}}
thể thao

16

Thay vì tạo một mô hình từ một loại ẩn danh và sau đó cố gắng chuyển đổi đối tượng ẩn danh thành một ExpandoObject...

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

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

Bạn chỉ có thể tạo ExpandoObjecttrực tiếp:

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

return View(model);

Sau đó, trong chế độ xem của bạn, bạn đặt loại mô hình là động @model dynamicvà bạn có thể truy cập các thuộc tính trực tiếp:

@Model.Profile.Name
@Model.Foo

Tôi thường khuyên dùng các kiểu xem được gõ mạnh cho hầu hết các khung nhìn, nhưng đôi khi tính linh hoạt này rất tiện dụng.


@yohal bạn chắc chắn có thể - Tôi đoán đó là sở thích cá nhân. Tôi thích sử dụng ViewBag cho dữ liệu trang linh tinh thường không liên quan đến mô hình trang - có thể liên quan đến mẫu và giữ Mô hình làm mô hình chính
Simon_Weaver

2
BTW bạn không phải thêm @model động, vì nó là mặc định
yoel halb

chính xác những gì tôi cần, thực hiện phương pháp chuyển đổi anon objs sang các đối tượng mở rộng đã mất quá nhiều thời gian ...... cảm ơn rất nhiều
h-rai

5

Bạn có thể sử dụng giao diện ngẫu hứng khung để bọc một loại ẩn danh trong giao diện.

Bạn chỉ cần trả lại IEnumerable<IMadeUpInterface>và ở cuối Linq của bạn sử dụng .AllActLike<IMadeUpInterface>();công việc này bởi vì nó gọi thuộc tính ẩn danh bằng DLR với ngữ cảnh của hội đồng khai báo loại ẩn danh.


1
Tuyệt vời chút lừa :) Đỗ không biết nếu nó là bất kỳ tốt hơn so với chỉ một lớp đơn giản với một loạt các tài sản công cộng, mặc dù ít nhất là trong này trường hợp.
Andrew Backer

4

Đã viết một ứng dụng bảng điều khiển và thêm Mono.Cecil làm tài liệu tham khảo (bây giờ bạn có thể thêm nó từ NuGet ), sau đó viết đoạn mã:

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

Đoạn mã trên sẽ lấy tệp lắp ráp từ các đối số đầu vào và sử dụng Mono.Cecil để thay đổi khả năng truy cập từ nội bộ sang công khai và điều đó sẽ giải quyết vấn đề.

Chúng tôi có thể chạy chương trình trong sự kiện Post Build của trang web. Tôi đã viết một bài đăng trên blog về điều này bằng tiếng Trung Quốc nhưng tôi tin rằng bạn chỉ cần đọc mã và ảnh chụp nhanh. :)


2

Dựa trên câu trả lời được chấp nhận, tôi đã ghi đè lên bộ điều khiển để làm cho nó hoạt động nói chung và đằng sau hậu trường.

Đây là mã:

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

Bây giờ bạn có thể chỉ cần vượt qua một đối tượng ẩn danh làm mô hình và nó sẽ hoạt động như mong đợi.



0

Lý do của RuntimeBinderException được kích hoạt, tôi nghĩ rằng có câu trả lời tốt trong các bài viết khác. Tôi chỉ tập trung để giải thích làm thế nào tôi thực sự làm cho nó hoạt động.

Bằng cách tham khảo để trả lời các chế độ xem @DotNetWise và Binding với bộ sưu tập kiểu ẩn danh trong ASP.NET MVC ,

Thứ nhất, Tạo một lớp tĩnh để mở rộng

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

Trong bộ điều khiển

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

Trong View, @model IEnumerable (động, không phải lớp mô hình), điều này rất quan trọng vì chúng ta sẽ liên kết đối tượng loại ẩn danh.

@model IEnumerable<dynamic>

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

Kiểu trong foreach, tôi không có lỗi khi sử dụng var hoặc động .

Nhân tiện, tạo một ViewModel mới phù hợp với các trường mới cũng có thể là cách để chuyển kết quả cho chế độ xem.


0

Bây giờ trong hương vị đệ quy

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

Sử dụng tiện ích mở rộng ExpandoObject hoạt động nhưng bị hỏng khi sử dụng các đối tượng ẩn danh lồng nhau.

Nhu la

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

var workitem = WorkBL.Get(id);

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

Để thực hiện điều này tôi sử dụng nó.

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

Cách sử dụng trong bộ điều khiển là như nhau ngoại trừ bạn sử dụng ToRazorDocate () thay vì ToExpando ().

Theo quan điểm của bạn để có được toàn bộ đối tượng ẩn danh, bạn chỉ cần thêm ".AnonValue" vào cuối.

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

0

Tôi đã thử ExpandoObject nhưng nó không hoạt động với loại phức tạp ẩn danh lồng nhau như thế này:

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

Vì vậy, giải pháp của tôi là trả về mô hình JObject cho View:

return View(JObject.FromObject(model));

và chuyển đổi thành động trong .cshtml:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.