2

OK, after searching and searching here, Google and other programming sites, it's time for me to ask my first question.

I have a view, Index.cshtml, that I need two Models in, so I created a ViewModel, ImageViewModel.cs, the two children models, ImageModel.cs and ProductModel.cs and have a controller, ImageController.cs.

For the life of me I cannot get the IEnumerable to work, I should be getting a set of data, one may have one item (product) and the other will have many (images). The images are tied to the productID.

After playing with it, I am now to a point where in the view, @Html.DisplayFor(modelItem => item.imageTitle) throws an error that imageTitle does not exist.

If I revert any of the numerous IEnumerable items scattered throughout, I continue to the get ImageViewModel being passed in is not IEnumerable and the view expects it to be.

For what it is, here is the complete code listings:

Index.cshtml

@model IEnumerable<JustAdminIt.Areas.JBI.ViewModels.ImageViewModel>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
    <th>
        Image Title
    </th>
    <th>
        Image Excerpt
    </th>
    <th>
        Image Description
    </th>
    <th>
        Image 
    </th>
    <th>
       Product ID
    </th>
    <th></th>
</tr>
@foreach (var item in Model) {

<tr>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageTitle)
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageExcerpt)
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageDescription)
    </td>
    <td>
        <img src="@Url.Content("~/Content/images/product/")@Html.DisplayFor(modelItem => item.Image.imageURL)" alt="Product Image" width="150" />
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Product.productID)
    </td>

    <td>
        @Html.ActionLink("Edit", "Edit", new { id = item.Image.imageID }) |
        @Html.ActionLink("Details", "Details", new { id = item.Image.imageID }) |
        @Html.ActionLink("Delete", "Delete", new { id = item.Image.imageID })
    </td>
</tr>
}

</table>

ProductModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Web.Mvc;

namespace JustAdminIt.Areas.JBI.Models
{
public class Product
{
    [Key]
    public int productID { get; set; }

    [Required]
    [DisplayName("Product Name")]
    public string productName { get; set; }

    [Required]
    [AllowHtml]
    [DisplayName("Product Excerpt")]
    public string productExcerpt { get; set; }

    [Required]
    [AllowHtml]
    [DisplayName("Product Description")]
    public string productDescription { get; set; }

    [DisplayName("Mark As Active")]
    public bool productActive { get; set; }

    [DisplayName("Product Add Date")]
    public DateTime? productAddDate { get; set; }

    [DisplayName("Product Inactive Date")]
    public DateTime? productInactiveDate { get; set; }

}
}

ImageModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Web.Mvc;

namespace JustAdminIt.Areas.JBI.Models
{
public class Image
{
    [Key]
    public int imageID { get; set; }

    [Required]
    public string imageTitle { get; set; }

    [AllowHtml]
    public string imageExcerpt { get; set; }

    [AllowHtml]
    public string imageDescription { get; set; }

    [AllowHtml]
    public string imageURL { get; set; }

    [Required]
    public int productID { get; set; }
}
}

ImageController.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using JustAdminIt.Areas.JBI.Models;
using JustAdminIt.Areas.JBI.ViewModels;
using JustAdminIt.Areas.JBI.DAL;
using System.IO;

namespace JustAdminIt.Areas.JBI.Controllers
{ 
public class ImageController : Controller
{
    private ImageContext db = new ImageContext();
    private ProductContext pb = new ProductContext();

    //
    // GET: /JBI/Image/

    public ViewResult Index()
    {
        //attempt new things
        int id = 1;
        IEnumerable<Image> image = db.Image.TakeWhile(x => x.productID == id);
        IEnumerable<Product> product = pb.Product.TakeWhile(x => x.productID == id);

        ImageViewModel piViewModel = new ImageViewModel(image, product);
        return View(piViewModel);
        //return View(db.Image.ToList());
    }

    //
    // GET: /JBI/Image/Details/5

    public ViewResult Details(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // GET: /JBI/Image/Create

    public ActionResult Create()
    {
        return View();
    } 

    //
    // POST: /JBI/Image/Create
    //this simply populates the DB values, but does not handle the file upload

    [HttpPost]
    public ActionResult Create(Image image)
    {
        if (ModelState.IsValid)
        {
            db.Image.Add(image);
            db.SaveChanges();
            return RedirectToAction("Index");  
        }

        return View(image);
    }

    [HttpPost]
    public ActionResult UploadFile(HttpPostedFileBase Filedata)
    {
        // Verify that the user selected a file
        if (Filedata != null && Filedata.ContentLength > 0)
        {
            // extract only the fielname
            var fileName = Path.GetFileName(Filedata.FileName);
            // store the file inside ~/App_Data/uploads folder
            var path = Path.Combine(Server.MapPath("~/Content/images/product/"), fileName);
            Filedata.SaveAs(path);
        }
        // redirect back to the index action to show the form once again
        return RedirectToAction("Index");
    }

    //
    // GET: /JBI/Image/Edit/5

    public ActionResult Edit(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // POST: /JBI/Image/Edit/5

    [HttpPost]
    public ActionResult Edit(Image image)
    {
        if (ModelState.IsValid)
        {
            db.Entry(image).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(image);
    }

    //
    // GET: /JBI/Image/Delete/5

    public ActionResult Delete(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // POST: /JBI/Image/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {            
        Image image = db.Image.Find(id);
        db.Image.Remove(image);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}
}

ImageViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Web.Mvc;
using JustAdminIt.Areas.JBI.Models;

namespace JustAdminIt.Areas.JBI.ViewModels
{
public class ImageViewModel
{
    public IEnumerable<ImageViewModel> Image { get; set; }
    public IEnumerable<ImageViewModel> Product { get; set; }

    public ImageViewModel(IEnumerable<Image> image, IEnumerable<Product> product)
    {
        IEnumerable<Image> Image = image;
        IEnumerable<Product> Product = product;
    }
}
}
SouthPlatte
  • 297
  • 2
  • 7
  • 17

3 Answers3

1

In your Index action method, you create a single new ImageViewModel() and then pass it to your view, but your view expects an IEnumerable<ImageViewModel>.

What you are doing is creating a single ImageViewModel, then passing the two collections to it. This is like using a single box to hold two groups of items, rather than having a bunch of boxes that each hold 1 of two different items.

You need to redesign your app to generate items in the proper way. I'm not sure why you have two different data contexts here, but that's going to complicate things a great deal.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • Mystere Man - gotcha on the double contexts, I see what I need to do on that. I guess what I am confused on, I thought the ViewModel was a way to pull only needed items from two or more Models - and basically combine them for use in one view, avoiding unnecessary data being pushed to the UI. – SouthPlatte Jul 30 '12 at 00:42
  • @SouthPlatte - Yes, a ViewModel can be used to combine multiple models, but your problem is there these are two entirely different collections from two different contexts, so there's no easy way to get which image goes with which product. A better solution is to tie the image to the product, then get it from your product model (product.Image) this way you only pass in one collection, and get the related image by navigating to the related image. Since I don't know what kind of data technology you're using here, it's hard to comment further. – Erik Funkenbusch Jul 30 '12 at 00:47
  • @Mistere Man - Using SQL2K8, EF 4 and some LINQ. I can reduce the contexts to one, I know my error there - so maybe that will help out - as I think then I can do a LINQ query to do a join and return what I need. I will keep working and post results as I find them. Thanks for the information so far. – SouthPlatte Jul 30 '12 at 00:58
  • @Mistere Man - Thanks for the input, the context was the starting point, and from there it snowballed into refactoring the ViewModel and doing the LINQ query in the controller to perform the join properly. – SouthPlatte Jul 30 '12 at 03:27
1

So after reading numerous resource, with code "snippets" out of context, with the help of @Mystere Man & @Travis J I have the solution.

First off, I was creating a new DbContext for every model. I know, dumb. So those are all wrapped in their own context file, and my web.config is much happier.

Sample DbContext showing multiple sources in one context:

public class MyDataContext : DbContext
{
    public DbSet<Bundle> Bundle { get; set; }
    public DbSet<Image> Image { get; set; }
    public DbSet<Product> Product { get; set; }
    public DbSet<Siteconfig> Siteconfig { get; set; }
}

The updated files (only posting files that changed, if it is not listed here, it's the same as in the original question):

ImageViewModel.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Web.Mvc;
using JustAdminIt.Areas.JBI.Models;

namespace JustAdminIt.Areas.JBI.ViewModels
{
    public class ImageViewModel
    {
        public Image Image { get; set; }
        public Product Product { get; set; }
    }
}

ImageController.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using JustAdminIt.Areas.JBI.Models;
using JustAdminIt.Areas.JBI.ViewModels;
using JustAdminIt.Areas.JBI.DAL;
using System.IO;

namespace JustAdminIt.Areas.JBI.Controllers
{ 
public class ImageController : Controller
{
    //private ImageContext db = new ImageContext();
    //private ProductContext pb = new ProductContext();

    private JustBundleItContext db = new JustBundleItContext();

    //
    // GET: /JBI/Image/

    public ViewResult Index()
    {
        var model = from a in db.Product
                    join b in db.Image
                    on a.productID equals b.productID
                    select new ImageViewModel
                    {
                        Product = a,
                        Image = b
                    };
        return View(model.ToList());

    }

    //
    // GET: /JBI/Image/Details/5

    public ViewResult Details(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // GET: /JBI/Image/Create

    public ActionResult Create()
    {
        return View();
    } 

    //
    // POST: /JBI/Image/Create
    //this simply populates the DB values, but does not handle the file upload

    [HttpPost]
    public ActionResult Create(Image image)
    {
        if (ModelState.IsValid)
        {
            db.Image.Add(image);
            db.SaveChanges();
            return RedirectToAction("Index");  
        }

        return View(image);
    }

    [HttpPost]
    public ActionResult UploadFile(HttpPostedFileBase Filedata)
    {
        // Verify that the user selected a file
        if (Filedata != null && Filedata.ContentLength > 0)
        {
            // extract only the fielname
            var fileName = Path.GetFileName(Filedata.FileName);
            // store the file inside ~/App_Data/uploads folder
            var path = Path.Combine(Server.MapPath("~/Content/images/product/"), fileName);
            Filedata.SaveAs(path);
        }
        // redirect back to the index action to show the form once again
        return RedirectToAction("Index");
    }

    //
    // GET: /JBI/Image/Edit/5

    public ActionResult Edit(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // POST: /JBI/Image/Edit/5

    [HttpPost]
    public ActionResult Edit(Image image)
    {
        if (ModelState.IsValid)
        {
            db.Entry(image).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(image);
    }

    //
    // GET: /JBI/Image/Delete/5

    public ActionResult Delete(int id)
    {
        Image image = db.Image.Find(id);
        return View(image);
    }

    //
    // POST: /JBI/Image/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {            
        Image image = db.Image.Find(id);
        db.Image.Remove(image);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}
}

Index.cshtml

@model List<JustAdminIt.Areas.JBI.ViewModels.ImageViewModel>

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
    <th>
        Image Title
    </th>
    <th>
        Image Excerpt
    </th>
    <th>
        Image Description
    </th>
    <th>
        Image 
    </th>
    <th>
       Product ID
    </th>
    <th></th>
</tr>

@foreach (var item in Model) {

<tr>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageTitle)
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageExcerpt)
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Image.imageDescription)
    </td>
    <td>
        <img src="@Url.Content("~/Content/images/product/")@Html.DisplayFor(modelItem => item.Image.imageURL)" alt="Product Image" width="150" />
    </td>
    <td>
        @Html.DisplayFor(modelItem => item.Product.productName)
    </td>

    <td>
        @Html.ActionLink("Edit", "Edit", new { id = item.Image.imageID }) |
        @Html.ActionLink("Details", "Details", new { id = item.Image.imageID }) |
        @Html.ActionLink("Delete", "Delete", new { id = item.Image.imageID })
    </td>
</tr>
}

</table>

Using the LINQ query in the controller allowed me to the desired join to find what product an image was associated with, while simplifying the ViewModel drastically. Now in the view I can choose from the item.Image group or the item.Products group knowing they are joined properly.

SouthPlatte
  • 297
  • 2
  • 7
  • 17
0

The issue could be due to your use of TakeWhile.

"Returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements." -http://msdn.microsoft.com/en-us/library/system.linq.enumerable.takewhile.aspx

You may want to use Where there ( https://stackoverflow.com/a/5031771/1026459 )

db.Image.Where(x => x.productID == id).ToList();
pb.Product.Where(x => x.productID == id).ToList();

That is only part of it. You are passing in a class which contains these two IEnumerable sets. However, your view is accepting an IEnumerable of that class. You are properly passing in, but not properly receiving.

@model JustAdminIt.Areas.JBI.ViewModels.ImageViewModel

However there is another problem here. Because your products and images are not related when passed you cannot use a foreach on both at the same time

@foreach (var item in Model) {

could be

@foreach (var item in Model.Product) {

but for the reason just listed this will not mean that there are as many images. In order to make this relation, you will need to slightly refactor. It is up to you in how to accomplish that. Basically you will need to create a virtual relationship in your model, or use a class which wraps both.

Community
  • 1
  • 1
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • Travis - hmmm...using db.Image.While... throws an error that While is not defined in the ImagesModel Maybe some context should be shared as to what I am attempting to accomplish: I have xx products which contain any xx number of images each. I have a basic listing of all images in the image manager, which shows all images, and I merely need to join to the products based on the fk_productID in images table to show the product name/ID the image is associated with. – SouthPlatte Jul 30 '12 at 00:50
  • Thanks for the input, I think I got it, and will be posting a solution. I was just being dumb on my DbContext and ViewModel items. Seeing when you pointed out the Products and Images would not be related got me to thinking. Thanks again! – SouthPlatte Jul 30 '12 at 02:58
  • @SouthPlatte - Oops! That should be `.Where()`, I made an edit to reflect the proper call. – Travis J Jul 30 '12 at 18:56