sports
sports

Reputation: 8147

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

Upvotes: 4

Views: 1340

Answers (3)

HDK
HDK

Reputation: 814

inside the controller List foos = dbContext.Foos.Select(Foo.Projection()).ToList();

 return View(foos);

in side razor view

@model List<dynamic>

Upvotes: 0

Martin Liversage
Martin Liversage

Reputation: 106906

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.

Upvotes: 6

teo van kot
teo van kot

Reputation: 12491

I suppose that View() contructor just don't know what overload to use, since you have dynamic type. You can try to do this:

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

But why do you want to use strongly typed View if you don't use any of strongly typed ViewModel advantages, like type check and IntelliSense?

If you really want to pass dynamic type to your View since MVC 3 you can use ViewBag that already is dynamic.

In your Controller:

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

In your View:

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

Upvotes: 1

Related Questions