Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird razor behavior with List<dynamic>

This is my controller's code:

 IQueryable<Foo> foos = dbContext.Foos.Where(...);

 return View(foos);

And this razor code (cshtml) works well:

@model IQueryable<Foo>

@{
     IQueryable<Foo> foos = Model;

     var projected = foos.Select(e => new 
     {
          fooId = e.FooId,
          bar = new 
          {
              barId = e.Foo.BarId
          }
     }).ToList();
}

@foreach (var x in projected)
{
     <span>@x.fooId</span><br />
}

But this razor code (cshtml) doesn't work, being almost the same thing!:

@model IQueryable<Foo>

@{
     IQueryable<Foo> foos = Model;

     var projected = foos.Selected(Foo.Projection()).ToList()
}

@foreach (var x in projected)
{
     <span>@x.fooId</span><br />
}

Foo.Projection() is a static method that I reuse a lot:

    public static Expression<Func<Foo, dynamic>> Projection()
    {
        return e => new
        {
            fooId = e.FooId,
            bar = new 
            {
                barId = e.Foo.BarId
            }
        }
    }

I'm getting that famous error: 'object' does not contain definition for 'fooId', which is discussed in here: MVC Razor dynamic model, 'object' does not contain definition for 'PropertyName' -but none of those answers helped me.

The accepted answer says: "now that MVC 3 has direct support for dynamic, the technique below is no longer necessary", so I also tried to return the projected List<dynamic> to the view ("ready to use, no projection needed") and it didn't work either (getting the same error). This is the code for that attempt:

Controller's code:

 List<dynamic> foos = dbContext.Foos.Select(Foo.Projection()).ToList();

 return View(foos);

View's code:

 @model dynamic

 etc.




Edit: With the debugger I'm able to check (just after the exception is thrown) that the item indeed has "a definition for..." (in the example code the item is x, but here is lot)

Debugger proof

like image 967
sports Avatar asked Mar 17 '23 22:03

sports


1 Answers

When you use dynamic you instruct the compiler to use reflection to call methods and access properties. In your case the objects that you access in this way are anonymous types and anonymous types are internal to the assembly they are created in.

The code generated for the Razor view is in a separate assembly and trying to reflect over an anonymous type created in the controller will fail. The debugger is not affected by this limitation so when the reflection fails and throws an exception you are still able to inspect the properties of the anonymous type in the debugger.

This also explains why your code works when you create the anonymous type in the Razor view. Then the code generated by your use of dynamic is able to reflect over the anonmyous type because it is declared in the same assembly.

Essentially, in MVC Razor you are not able to use anonymous types in a view when they are declared in the controller. Your use of dynamic was hiding this underlying problem by generating a run-time error that was hard to understand.

To fix your problem you can either create specific public types instead of using internal anonymous types or you can convert the anonymous type to an ExpandoObject in the controller.

like image 131
Martin Liversage Avatar answered Mar 26 '23 02:03

Martin Liversage