4

I have a controller that is building a query from Linq to Sql to pass into the ViewBag.products object. The problem is, I cannot loop using a foreach on it as I expected I could.

Here's the code in the controller building the query, with the .ToList() function applied.

var products = from bundles in db.Bundle
    join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
    join product in db.Products on bProducts.productID equals product.productID
    join images in db.Images on product.productID equals images.productID
    where bundles.bundleInactiveDate > DateTime.Now
          select new {
              product.productName,
              product.productExcerpt,
              images.imageID,
              images.imageURL
           };
ViewBag.products = products.ToList();

Since I am using a different model on the Index.cshtml for other items needed, I thought a simple Html.Partial could be used to include the viewbag loop. I have tried it with the same result with and without using the partial and simply by using the foreach in the index.cshtml. A snippet that includes the partial is below:

<div id="bundle_products">
<!--build out individual product icons/descriptions here--->
@Html.Partial("_homeBundle")
</div>

In my _homeBundle.cshtml file I have the following:

@foreach (var item in ViewBag.products)
{ 
    @item
}

I am getting the ViewBag data, but I am getting the entire list as output as such:

{ productName = Awesomenes Game, productExcerpt = <b>Awesome game dude!</b>, imageID = 13, imageURL = HotWallpapers.me - 008.jpg }{ productName = RPG Strategy Game, productExcerpt = <i>Test product excerpt</i>, imageID = 14, imageURL = HotWallpapers.me - 014.jpg }

What I thought I could do was:

@foreach(var item in ViewBag.Products)
{
    @item.productName
}

As you can see, in the output, productName = Awesomenes Game. However, I get the error 'object' does not contain a definition for 'productName' when I attempt this.

How can I output each "field" so to say individually in my loop so I can apply the proper HTML tags and styling necessary for my page?

Do I need to make a whole new ViewModel to do this, and then create a display template as referenced here: 'object' does not contain a definition for 'X'

Or can I do what I am attempting here?

*****UPDATE*****

In my Controller I now have the following:

        var bundle = db.Bundle.Where(a => a.bundleInactiveDate > DateTime.Now);
        var products = from bundles in db.Bundle
                       join bProducts in db.BundleProducts on bundles.bundleId equals bProducts.bundleID
                       join product in db.Products on bProducts.productID equals product.productID
                       join images in db.Images on product.productID equals images.productID
                       where bundles.bundleInactiveDate > DateTime.Now
                       select new {
                           product.productName,
                           product.productExcerpt,
                           images.imageID,
                           images.imageURL
                       };
        var bundleContainer = new FullBundleModel();

        bundleContainer.bundleItems = bundle;

        return View(bundleContainer);

I have a model, FullBundleModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace JustBundleIt.Models
{
    public class FullBundleModel
    {
        public IQueryable<Bundles> bundleItems { get; set; }
        public IQueryable<Images> imageItems { get; set; }
    }
}

and my View now has

@model IEnumerable<JustBundleIt.Models.FullBundleModel>

@foreach (var item in Model) 
{
<div class="hp_bundle">

    <h3>@Html.Display(item.bundleName)</h3>
    </div>
}       

If I remove IEnumerable from the model reference, the foreach errors out that there is no public definition for an enumerator.

In the @Html.Display(item.bundleName) it errors out that the model has no definition for bundleName. If I attempt

@foreach(var item in Model.bundleItems)

I get an error that bundleItems is not defined in the model.

So what don't I have wired up correctly to use the combined model?

Community
  • 1
  • 1
SouthPlatte
  • 297
  • 2
  • 7
  • 17
  • 1
    First: Don't define your Model as IEnumerable. Just use `@model JustBundleIt.Models.FullBundleModel`. Second: change your for loop to `@foreach(var item in Model.bundleItems)` and your HTML helper to `@Html.DisplayFor(modelItem => item.bundleName)`. – Kittoes0124 Sep 18 '12 at 03:54
  • Thank you. This is how I can tell I've been at it for too long today. Much appreciated. – SouthPlatte Sep 18 '12 at 03:58
  • lol, no problem. We've all been there. It doesn't help that there is usually FAR more than one way to do things in our world. You could probably tackle this problem from 100+ different ways and still end up with the same end result. – Kittoes0124 Sep 18 '12 at 04:01

2 Answers2

2

Do I need to make a whole new ViewModel to do this, and then create a display template as referenced here...

Darin's answer that you linked states the important concept: Anonymous types are not intended for use across assembly boundaries and unavailable to Razor. I would add that it's rarely a good idea to expose anonymous objects outside of their immediate context.

Creating a view model specifically for view consumption is almost always the correct approach. View models can be reused across views if you are presenting similar data.

It's not necessary to create a display template, but it can be useful if you want to reuse the display logic. HTML helpers can also fill a similar function of providing reusable display logic.

Having said all of that, it's not impossible to pass an anonymous type to a view, or to read an anonymous type's members. A RouteValueDictionary can take an anonymous type (even across assemblies) and read its properties. Reflection makes it possible to read the members regardless of visibility. While this has its uses, passing data to a View is not one of them.

More reading:

Community
  • 1
  • 1
Tim M.
  • 53,671
  • 14
  • 120
  • 163
0

Why not create a new model that contains all of the data that you need?

Example One:

public class ContainerModel
{
    public IQueryable<T> modelOne;
    public IQueryable<T> modelTwo;
}

This will allow you to access either of your queries in Razor:

@model SomeNamespace.ContainerModel

@foreach (var item in Model.modelOne)
{
    //Do stuff
}

I personally avoid using ViewBag at all and store everything I need in such models because it's NOT dynamic and forces everything to be strongly typed. I also believe that this gives you a clearly defined structure/intent.

And just for clarity's sake:

public ViewResult Index()
{
    var queryOne = from p in db.tableOne
                   select p;

    var queryTwo = from p in db.tableTwo
                   select p;

    var containerModel = new ContainerModel();
    containerModel.modelOne = queryOne;
    containerModel.modelTwo = queryTwo;

    return View(containerModel);
}

Example Two:

public class ContainerModel
{
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
    public Nullable<DateTime> startDate { get; set; }
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] //Format: MM/dd/yyyy (Date)
    public Nullable<DateTime> endDate { get; set; }
    public SelectList dropdown { get; set; }
    public IQueryable<T> modelOne { get; set; }
    public IQueryable<T> modelTwo { get; set; }
}

In this case you've stored 3 other items in the model with your 2 queries. You can use the Html helpers to create a Drop Down List in Razor:

@Html.DropDownList("dropdown", Model.dropdown)

And you can use the DisplayFor helper to display your dates as defined in your model with Data Annotations:

@Html.DisplayFor(a => a.startDate)

This is advantageous IMO because it allows you to define all of the data that you want to make use of in your View AND how you plan to format that data in a single place. Your Controller contains all of the business logic, your Model contains all of the data/formatting, and your View is only concerned with the content of your page.

Kittoes0124
  • 4,930
  • 3
  • 26
  • 47
  • So a model that does nothing but contain other models, then I can access the multiple models within the view without issue? – SouthPlatte Sep 18 '12 at 02:54
  • @SouthPlatte I wouldn't say that. Your Model can contain whatever you want it to. I put everything that I need in a particular View in a Model. I'll add an example in a moment. – Kittoes0124 Sep 18 '12 at 02:55
  • @SouthPlatte Added second example with more than queries. – Kittoes0124 Sep 18 '12 at 03:03
  • Thanks. Still having some issues. I might update with what I have done to clarify my attempt at your example. – SouthPlatte Sep 18 '12 at 03:32