42

I had this problem once before and didn't resolve it. I have a list (generated in an MVC3 controller):

ViewBag.Languages = db.Languages
    .Select(x => new { x.Name, x.EnglishName, x.Id })
    .ToList();

and on my page (Razor) I try to iterate through it:

foreach (var o in ViewBag.Languages)
{
    string img = "Lang/" + o.EnglishName + ".png";
    @* work *@
}

but the reference to o.EnglishName fails with the error:

'object' does not contain a definition for 'EnglishName'

though the curious thing is that if I type into the Immediate Window (whilst debugging):

o
{ Name = བོད་སྐད་, EnglishName = Tibetan, Id = 31 }
    EnglishName: "Tibetan"
    Id: 31
    Name: "བོད་སྐད་"

so obviously the field is there. What is my problem here?

ekkis
  • 9,804
  • 13
  • 55
  • 105

4 Answers4

79

You are using an anonymous object here:

ViewBag.Languages = db.Languages
    .Select(x => new { x.Name, x.EnglishName, x.Id })
    .ToList();

Anonymous objects are emitted as internal by the compiler. The Razor views are automatically compiled into a separate assembly by the ASP.NET runtime. This means that you cannot access any anonymous objects generated in your controllers.

So in order to fix your issue you could define a view model:

public class LanguageViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string EnglishName { get; set; }
}

and then in your controller use this view model:

ViewBag.Languages = db.Languages
    .Select(x => new LanguageViewModel
    { 
        Name = x.Name, 
        EnglishName = x.EnglishName, 
        Id = x.Id 
     })
    .ToList();

And now that you have a view model the next improvement to your code is of course to get rid of this crap of ViewBag that I am sick of seeing and simply use view models and strong typing:

public ActionResult Foo()
{
    var model = db
        .Languages
        .Select(x => new LanguageViewModel
        { 
            Name = x.Name, 
            EnglishName = x.EnglishName, 
            Id = x.Id 
        })
        .ToList();
    return View(model);
}

and then of course have a strongly typed view:

@model IEnumerable<LanguageViewModel>
@Html.DisplayForModel()

and then define the corresponding display template which will automatically be rendered by the ASP.NET MVC engine for each element of the view model so that you don't even need to write a single foreach in your views (~/Views/Shared/DisplayTemplates/LanguageViewModel.cshtml):

@model LanguageViewModel
... generate the image or whatever you was attempting to do in the first place
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    wow. awesome reply (+1 for that). thanks so much - and yes, I'm in the process of removing all references to the ViewBag in favour of view-models... – ekkis Oct 04 '11 at 19:33
  • 1
    very well but we may need ViewBags sometimes. We can only pass one model to one view. However we may want to send some other small data collection, don't we? and in this case, it is easier to send that small data collection with ViewBags. Off course you can send it with PartialViews as well. I dont know is there any other recommendations. – oneNiceFriend May 27 '16 at 11:24
  • @Darin - I didn't know Anonymous objects, specifically ExpandoObject properties, are inaccessible in another assembly. They're not seeing as "properties" of "object". Which makes sense when you say they're internal. This helped me fix my problem in a VSTO scenario. – Leo Gurdian Aug 23 '18 at 20:17
  • That new { } is the real deal. Thanks !! – Mihai May 20 '20 at 13:55
4

This was driving me spare until I checked my code and found this:

class AdsViewModel
{
    public int Id { get; set; }
    public  string City { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
    public string UserEmail { get; set; }
    public string ContactPhone { get; set; }
    public string ShortTitle { get; set; }
    public string AdUrl { get; set; }
}

Changing it to:

public class AdsViewModel 

Fixed it.

Norbert Norbertson
  • 2,102
  • 1
  • 16
  • 28
1

Please use ViewData instead of ViewBag like.

ViewData["Lang"] = db.Languages
.Select(x => new { x.Name, x.EnglishName, x.Id })
.ToList();

Then

foreach (var o in (dynamic) ViewData["Lang"])
{
string img = "Lang/" + o.EnglishName + ".png";
@* work *@
}
0

It can happen as well if the name of your class doesn't match the name of the file (I know it's stupid but it might help you)