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


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

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

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

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

11 153

11 ответов:

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

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

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

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

С помощью ToExpando метод является лучшим решением.

вот версия, что не требует система.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;
}

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

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

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

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

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

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

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

вот код:

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

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

Я собираюсь сделать немного кражи из https://stackoverflow.com/a/7478600/37055

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

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

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

причина 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>
}

тип в foreach, у меня нет ошибки с помощью ВАР или динамический.

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

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

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

использование расширения 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;