4

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

Community
  • 1
  • 1
sports
  • 7,851
  • 14
  • 72
  • 129
  • Have you stepped through in the debugger? I'm curious what types `projected` and `x` come through as. – Justin Morgan - On strike Feb 24 '15 at 21:29
  • Step through in the debugger. Can you guarantee that deferred execution isn't killing you here? (Even though you appear to be materializing the list in your second attempt..) – Simon Whitehead Feb 24 '15 at 21:29
  • when the error jumps in debug mode, I can totally check all the content of "x" in the foreach. I can verify that the `fooId` is there (the debugger is able to read it even though the error is telling me that the object does not contain a definition for it...) – sports Feb 24 '15 at 21:31
  • I've added a debugger screenshot – sports Feb 24 '15 at 21:37

3 Answers3

6

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.

Community
  • 1
  • 1
Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 1
    but why it is said that "starting from mvc 3 dynamic models are supported" ? (thats said in the accepted answer from the post I linked in my question) – sports Feb 27 '15 at 19:01
  • 3
    @sports: Because starting with MVC 3 you can specify `@model dynamic` in your view. But it is not the use of `dynamic` that is the root cause of your problem but rather the anonymous type that is unusable in the view. Trying to get access to the anonymous type by using `dynamic` does not allow you to work around that the anonymous type is `internal`. – Martin Liversage Feb 27 '15 at 19:34
  • 1
    When you say "in MVC Razor you are not able to use anonymous types in a view when they are declared in the controller": well, there is no anonymous type created in the controller in my example. The `IQueryable` is passed correctly to the view, and the `Foo.Projection()` is a static method inside `Foo`. Foo is not a controller. So... at the end: I can't reuse that projection? – sports Mar 05 '15 at 16:11
  • 1
    @sports: I was assuming that the `Foo` class was declared in the same assembly as the controller and I was just trying to keep my explanation simple. The problem is still the same: The anonymous type created in `Foo.Projection` is in the same assembly as the `Foo` class and it is internal making it inaccessible from the assembly created by the view. Anonymous types cannot be used outside the assembly where they have been declared and in MVC Razor the views are in a separate assembly. I have suggested two ways to resolve this problem and the simplest is to switch to non-anonymous types. – Martin Liversage Mar 05 '15 at 18:22
1

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 />
}
teo van kot
  • 12,350
  • 10
  • 38
  • 70
  • 1
    I don't think it doesn't know which constructor of `View()` to use, because the debugger is able to recognize the model correctly (already standing in the view). And if it was a problem, I could use `View(model: ...)`. Regarding the suggested solution, I tried ViewBag but didn't work either :-(, it is again recognized via debugger but the exception thrown is the same: object doesnt contain.... – sports Feb 27 '15 at 19:26
0

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

 return View(foos);

in side razor view

@model List<dynamic>
HDK
  • 816
  • 1
  • 8
  • 13