Dynamic Anonymous type in Razor causes RuntimeBinderException

Dynamicasp.net Mvc-3RazorAnonymous Types

Dynamic Problem Overview


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.

Dynamic Solutions


Solution 1 - Dynamic

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

Solution 2 - Dynamic

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.

Solution 3 - Dynamic

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

Solution 4 - Dynamic

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.

Solution 5 - Dynamic

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.

Solution 6 - Dynamic

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. :)

Solution 7 - Dynamic

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.

Solution 8 - Dynamic

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.

Solution 9 - Dynamic

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.

Solution 10 - Dynamic

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

Solution 11 - Dynamic

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;

Solution 12 - Dynamic

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJarrettVView Question on Stackoverflow
Solution 1 - DynamicAdaptabiView Answer on Stackoverflow
Solution 2 - DynamicJarrettVView Answer on Stackoverflow
Solution 3 - DynamicalexeyView Answer on Stackoverflow
Solution 4 - DynamicSimon_WeaverView Answer on Stackoverflow
Solution 5 - DynamicjbtuleView Answer on Stackoverflow
Solution 6 - DynamicJeffrey ZhaoView Answer on Stackoverflow
Solution 7 - Dynamicyoel halbView Answer on Stackoverflow
Solution 8 - DynamicChris MarisicView Answer on Stackoverflow
Solution 9 - DynamicV-SHYView Answer on Stackoverflow
Solution 10 - DynamicMatas VaitkeviciusView Answer on Stackoverflow
Solution 11 - DynamicDonny V.View Answer on Stackoverflow
Solution 12 - DynamicGuilherme MunizView Answer on Stackoverflow