Динамический анонимный тип в Razor вызывает исключение RuntimeBinderException - PullRequest
154 голосов
/ 25 февраля 2011

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

«объект» не содержит определения для «RatingName»

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

Screenshot of Error

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

Ответы [ 11 ]

237 голосов
/ 15 апреля 2011

Анонимные типы, имеющие внутренние свойства, на мой взгляд, являются плохим решением для разработки .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>
}
50 голосов
/ 25 февраля 2011

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

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

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

23 голосов
/ 29 марта 2013

Использование 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;
}
16 голосов
/ 06 февраля 2013

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

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

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

4 голосов
/ 06 сентября 2011

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

2 голосов
/ 04 июля 2013

Исходя из принятого ответа, я переопределил в контроллере, чтобы он работал в целом и за кадром.

Вот код:

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 голосов
/ 09 октября 2018

Использование расширения 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 () вместо ToExpando ().

По вашему мнению, чтобы получить весь анонимный объект, вы просто добавляете ".AnonValue" в конец.

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
0 голосов
/ 13 июля 2015

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

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 голосов
/ 27 мая 2015

Причина 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 IEnumerable<dynamic>

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

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

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

...