Динамический анонимный тип в Razor вызывает RuntimeBinderException

Я получаю следующую ошибку:

'object' не содержит определения для 'RatingName'

Когда вы смотрите на анонимный динамический тип, у него явно есть RatingName.

Screenshot of Error

Я понимаю, что могу сделать это с помощью Tuple, но я хотел бы понять, почему возникает сообщение об ошибке.

Ответ 1

Анонимные типы, имеющие внутренние свойства, - это, по-моему, плохое решение .NET Framework.

Ниже приведено быстрое и хорошее расширение, чтобы устранить эту проблему, например, путем прямого преобразования анонимного объекта в объект 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

Я нашел ответ в связанном вопросе. Ответ указан в сообщении блога Дэвида Эббо Передача анонимных объектов в представления MVC и их доступ с помощью динамического

Причиной этого является то, что анонимный тип передается в контроллер внутри, поэтому он может получить доступ изнутри сборки в котором его заявили. Поскольку мнения скомпилировать отдельно, динамический связующее лицо жалуется, что оно не может пройти что граница сборки.

Но если вы подумаете об этом, ограничение от динамического связующего на самом деле довольно искусственно, потому что если вы используете частное отражение, ничего не позволяя вам получить доступ к этим внутренних членов (да, он даже работает в Среднее доверие). Таким образом, динамика по умолчанию связующее применять правила компиляции С# (где вы не можете получить доступ к внутренним членам), вместо того, чтобы позволить вам делать то, что CLR позволяет время выполнения.

Ответ 3

Использование метода 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;
}

Ответ 4

Вместо того, чтобы создавать модель из анонимного типа, а затем пытаться преобразовать анонимный объект в 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

Я бы рекомендовал строго типизированные модели просмотра для большинства просмотров, но иногда эта гибкость удобна.

Ответ 5

Вы можете использовать интерфейс impromptu, чтобы обернуть анонимный тип в интерфейсе.

Вы просто вернете IEnumerable<IMadeUpInterface> и в конце использования Linq .AllActLike<IMadeUpInterface>(); это работает, потому что оно вызывает анонимное свойство, используя DLR с контекстом сборки, объявившего анонимный тип.

Ответ 6

Написал консольное приложение и добавило 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 для изменения доступности из внутреннего в общедоступный, и это решит проблему.

Мы можем запустить программу в событии Post Build на веб-сайте. Я написал сообщение в блоге об этом на китайском языке, но я считаю, что вы можете просто прочитать код и моментальные снимки.:)

Ответ 7

Основываясь на принятом ответе, я переопределил в контроллере, чтобы он работал вообще и за кулисами.

Вот код:

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

Теперь вы можете просто передать анонимный объект в качестве модели, и он будет работать, как ожидалось.

Ответ 8

Я собираюсь немного украсть из fooobar.com/questions/40741/...

Если вы устанавливаете-пакет dynamitey, вы можете сделать это:

return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));

И крестьяне радуются.

Ответ 9

Причина запуска RuntimeBinderException, я думаю, что есть хорошие ответы в других сообщениях. Я просто сосредоточусь, чтобы объяснить, как я на самом деле заставляю его работать.

Обратитесь к answer @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>[email protected], [email protected]</div>
}

Тип foreach, у меня нет ошибки либо с использованием var или dynamic.

Кстати, создать новый ViewModel, соответствующий новым полям, также может быть способ передать результат в представление.

Ответ 10

Теперь в рекурсивном вкусе

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