Razor 中的动态匿名类型导致 RuntimeBinderException

发布于 2024-10-19 00:59:56 字数 238 浏览 1 评论 0原文

我收到以下错误:

“object”不包含“RatingName”的定义

当您查看匿名动态类型时,它显然具有RatingName。

Screenshot of Error

我意识到我可以使用元组来完成此操作,但我想了解为什么会出现错误消息。

I'm getting the following error:

'object' does not contain a definition for 'RatingName'

When you look at the anonymous dynamic type, it clearly does have RatingName.

Screenshot of Error

I realize I can do this with a Tuple, but I would like to understand why the error message occurs.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

ι不睡觉的鱼゛ 2024-10-26 00:59:56

在我看来,具有内部属性的匿名类型是一个糟糕的 .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>
}

Anonymous types having internal properties is a poor .NET framework design decision, in my opinion.

Here is a quick and nice extension to fix this problem i.e. by converting the anonymous object into an ExpandoObject right away.

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

It's very easy to use:

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

Of course in your view:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}
在巴黎塔顶看东京樱花 2024-10-26 00:59:56

我在相关问题。答案在 David Ebbo 的博客文章 将匿名对象传递到 MVC 视图并使用动态访问它们

造成这种情况的原因是
传入的匿名类型
控制器在内部,所以它只能
可从程序集中访问
其中声明了它。自从浏览量
单独编译,动态
活页夹抱怨它无法过去
该装配边界。

但是如果你仔细想想,这
动态绑定器的限制是
实际上很人为,因为如果
你使用私有反射,什么都没有
阻止您访问这些内容
内部成员(是的,它甚至可以在
中等信任度)。所以默认动态
活页夹正在不遗余力地
强制执行 C# 编译规则(其中
您无法访问内部成员),
而不是让你做 CLR 的事情
运行时允许。

I found the answer in a related question. The answer is specified on David Ebbo's blog post Passing anonymous objects to MVC views and accessing them using dynamic

The reason for this is that the
anonymous type being passed in the
controller in internal, so it can only
be accessed from within the assembly
in which it’s declared. Since views
get compiled separately, the dynamic
binder complains that it can’t go over
that assembly boundary.

But if you think about it, this
restriction from the dynamic binder is
actually quite artificial, because if
you use private reflection, nothing is
stopping you from accessing those
internal members (yes, it even work in
Medium trust). So the default dynamic
binder is going out of its way to
enforce C# compilation rules (where
you can’t access internal members),
instead of letting you do what the CLR
runtime allows.

魂ガ小子 2024-10-26 00:59:56

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

Using ToExpando method is the best solution.

Here is the version that doesn't require System.Web assembly:

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;
}
爱人如己 2024-10-26 00:59:56

不必像这样从匿名类型创建模型,然后尝试将匿名对象转换为 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);

然后在您的view 将模型类型设置为动态 @modeldynamic 并且可以直接访问属性:

@Model.Profile.Name
@Model.Foo

我通常建议大多数视图使用强类型视图模型,但有时这种灵活性很方便。

Instead of creating a model from an anonymous type and then trying to convert the anonymous object to an ExpandoObject like this ...

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

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

You can just create the ExpandoObject directly:

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

return View(model);

Then in your view you set the model type as dynamic @model dynamic and you can access the properties directly :

@Model.Profile.Name
@Model.Foo

I'd normally recommend strongly typed view models for most views, but sometimes this flexibility is handy.

〃温暖了心ぐ 2024-10-26 00:59:56

您可以使用框架impromptu 接口将匿名类型包装在接口中。

您只需返回一个 IEnumerable 并在 Linq 末尾使用 .AllActLike(); 这可以工作,因为它使用具有声明匿名类型的程序集上下文的 DLR。

You can use the framework impromptu interface to wrap an anonymous type in an interface.

You'd just return an IEnumerable<IMadeUpInterface> and at the end of your Linq use .AllActLike<IMadeUpInterface>(); this works because it calls the anonymous property using the DLR with a context of the assembly that declared the anonymous type.

眼泪都笑了 2024-10-26 00:59:56

编写一个控制台应用程序并添加 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 事件中运行该程序。我写了一篇关于此的中文博客文章 但我相信你可以只阅读代码和快照。 :)

Wrote a console application and add Mono.Cecil as reference (you can now add it from NuGet), then write the piece of code:

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

The code above would get the assembly file from input args and use Mono.Cecil to change the accessibility from internal to public, and that would resolve the problem.

We can run the program in the Post Build event of the website. I wrote a blog post about this in Chinese but I believe you can just read the code and snapshots. :)

请止步禁区 2024-10-26 00:59:56

根据已接受的答案,我已在控制器中进行了重写,使其在一般情况下和在幕后工作。

代码如下:

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

现在您只需传递一个匿名对象作为模型,它就会按预期工作。

Based on the accepted answer, I have overridden in the controller to make it work in general and behind the scenes.

Here is the code:

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

Now you can just pass an anonymous object as the model, and it will work as expected.

风吹短裙飘 2024-10-26 00:59:56

我将从 https://stackoverflow.com/a/7478600/37055 偷一些东西

如果你 install-package dynamitey 你可以这样做:

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

农民们欢欣鼓舞。

I'm going to do a little bit of stealing from https://stackoverflow.com/a/7478600/37055

If you install-package dynamitey you can do this:

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

And the peasants rejoice.

噩梦成真你也成魔 2024-10-26 00:59:56

触发 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>[email protected], [email protected]</div>
}

foreach 中的类型,我使用 vardynamic 都没有错误。

顺便说一句,创建一个与新字段匹配的新 ViewModel 也可以是将结果传递给视图的方法。

The reason of RuntimeBinderException triggered, I think there have good answer in other posts. I just focus to explain how I actually make it work.

By refer to answer @DotNetWise and Binding views with Anonymous type collection in ASP.NET MVC,

Firstly, Create a static class for extension

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

In controller

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

In View, @model IEnumerable (dynamic, not a model class), this is very important as we are going to bind the anonymous type object.

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>[email protected], [email protected]</div>
}

The type in foreach, I have no error either using var or dynamic.

By the way, create a new ViewModel that is matching the new fields also can be the way to pass the result to the view.

み零 2024-10-26 00:59:56

现在是递归风格

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

Now in recursive flavor

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;
    }
空名 2024-10-26 00:59:56

使用 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;

Using the ExpandoObject Extension works but breaks when using nested anonymous objects.

Such as

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

var workitem = WorkBL.Get(id);

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

To accomplish this I use this.

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

Usage in the controller is the same except you use ToRazorDynamic() instead of ToExpando().

In your view to get the entire anonymous object you just add ".AnonValue" to the end.

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
花之痕靓丽 2024-10-26 00:59:56

我尝试了 ExpandoObject,但它不适用于像这样的嵌套匿名复杂类型:

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

所以我的解决方案是将 JObject 返回到视图模型:

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>

I tried the ExpandoObject but it didn't work with a nested anonymous complex type like this:

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

So my solution was to return a JObject to View model:

return View(JObject.FromObject(model));

and convert to dynamic in .cshtml:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文