Dynamic Anonymous type in Razor causes RuntimeBinderException

后端 未结 12 799
不思量自难忘°
不思量自难忘° 2020-11-22 07:45

I\'m getting the following error:

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

When you look at the anonymous dyn

相关标签:
12条回答
  • 2020-11-22 08:10

    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;
        }
    
    0 讨论(0)
  • 2020-11-22 08:13

    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>x=@item.nric, y=@item.count</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.

    0 讨论(0)
  • 2020-11-22 08:14

    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.

    0 讨论(0)
  • 2020-11-22 08:17

    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.

    0 讨论(0)
  • 2020-11-22 08:18

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 08:18

    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;
    
    0 讨论(0)
提交回复
热议问题