Please be gentle on me. I've read plenty of MVC answers for others on this site but now I've got something where I cannot find an answer.
Going through lessons I created a LoginDetails project where a viewmodel was passed from the controller to each view and everything went fine.
Model (from a SQL table):
namespace MVC_EFCRUD.Models
{
using System;
using System.Collections.Generic;
public partial class LoginDetail
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
Here's a snippet from the Controller:
using MVC_EFCRUD.Models;
namespace MVC_EFCRUD.Controllers
{
public class LoginDetailsController : Controller
{
private tapnamron_PNMEntities db = new tapnamron_PNMEntities();
// GET: LoginDetails
public ActionResult Index()
{
return View(db.LoginDetails.ToList());
}
Here's a snippet from the index view of the LoginDetails folder:
@model IEnumerable<MVC_EFCRUD.Models.LoginDetail>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.UserName)
</th>
After that I decided to create a menu which is built dynamically from a SQL table then a viewmodel is passed to the _Layout.cshtml so the menu will be on all pages. So as not to clutter this code with the working LoginDetails code I created new folder called SiteMenu with an index page where I would test the menu. It too, worked great but then I decided to re-run the LoginDetails portion just to make sure it still worked and got the below error:
Exception Details: System.InvalidOperationException: The model item passed into the dictionary is of type System.Collections.Generic.List 1[MVC_EFCRUD.Models.LoginDetail], but this dictionary requires a model item of type System.Collections.Generic.IEnumerable 1[MVC_EFCRUD.Models.uspGetSiteMenu_Results].
So, from what I read on this site (thank you very much) I figured that since I am passing a viewmodel to the _Layout page where I have my menu this conflicts with the model on the LoginDetails pages since they're all accessing the same _Layout page. Here is the SiteMenu model (retrieved using a stored procedure):
namespace MVC_EFCRUD.Models
{
using System;
public partial class uspGetSiteMenu_Results
{
public int MenuID { get; set; }
public int ParentID { get; set; }
public string SiteName { get; set; }
public string Url { get; set; }
public string Target { get; set; }
}
}
Snippet from Controller:
namespace MVC_EFCRUD.Controllers
{
public class SiteMenuController : Controller
{
// GET: SiteMenu
public ActionResult Index()
{
tapnamron_PNMSiteMenu ent = new tapnamron_PNMSiteMenu();
return View(ent.GetSiteMenu().ToList());
}
}
}
Snippet from _Layout.cshtml:
@model IEnumerable<MVC_EFCRUD.Models.uspGetSiteMenu_Results>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="~/Scripts/modernizr-2.6.2.js"></script>
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
@using System.Web.Optimization;
@Styles.Render("~/Content/css")
</head>
<body>
<nav class="navbar navbar-inverse navbar-static-top marginBottom-0" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="collapse navbar-collapse" id="navbar-collapse-1">
<ul class="nav navbar-nav">
@foreach (var item in Model.Where(m => m.ParentID == 0))
{
switch (item.SiteName)
{
I tried several ways to fix this but no luck. Here is a bunch of remmed out code I had in the HomeController:
namespace MVC_EFCRUD.Controllers
{
public class HomeController : Controller
{
//tapnamron_PNMEntities m1 = new tapnamron_PNMEntities();
//tapnamron_PNMSiteMenu m2 = new tapnamron_PNMSiteMenu();
//LoginDetail modelLoginDetail = new LoginDetail();
//uspGetSiteMenu_Results ModeluspGetSiteMenu_Results = new uspGetSiteMenu_Results();
// GET: Home
public ActionResult Index()
{
//List<object> model = new List<object>();
//model.Add(modelLoginDetail);
//model.Add(ModeluspGetSiteMenu_Results);
//var model = Tuple.Create<IEnumerable<string>, IEnumerable<string>>(LoginDetail, uspGetSiteMenu_Results);
//return View(model);
//var viewmodel = new MyModels
//{
// uspGetSiteMenu_Results = uspGetSiteMenu_Results,
// LoginDetail = LoginDetail
//};
//var myModels = new MyModels();
return View();
}
}
}
I also had this in the _Layout but removed it in case it caused confusion for you (it was passed from the HomeController):
@*@model IEnumerable<object>
@{
List<MVC_EFCRUD.Models.LoginDetail> lstLoginDetails = Model.ToList()[0]
as List<MVC_EFCRUD.Models.LoginDetail>;
List<MVC_EFCRUD.Models.uspGetSiteMenu_Results> lstSiteMenu = Model.ToList()[1]
as List<MVC_EFCRUD.Models.uspGetSiteMenu_Results>;
}*@
I realized I really do not want to add multiple models to the Layout page since there's going to be more stuff for the site not related to LoginDetails but will of course use the menu on every page. I thought maybe some partial views or nested views but from what I found there will still be conflicts.
So, what I want is a way to use multiple models and still include the dynamically created menu on every page (which is also built using a model)?
Thanks much!
You taking the wrong approach. Instead of passing models to the layout, in the layout page, use Html.RenderAction()
to generate the menu. Mark the Index()
method of your SiteMenuController
controller with the [ChildActionOnly]
attribute (so that it cannot be called directly from the browser) and change it to return a partial View of your menu.
Then in the _Layout.cshtml
file, use
@{ Html.RenderAction("Index", "SiteMenu"); }
to output the menu in the layout page.
_Layout.cshtml (no @model
)
<head>
....
</head>
<body>
@{ Html.RenderAction("Index", "SiteMenu"); }
@RenderBody()
</body>
/Views/SiteMenu/Index.cshtml (partial view containing all the html relating to the menu)
@model IEnumerable<MVC_EFCRUD.Models.uspGetSiteMenu_Results>
<nav class="navbar navbar-inverse navbar-static-top marginBottom-0" role="navigation">
....
@foreach (var item in Model.Where(m => m.ParentID == 0))
{
....
}
</nav>
Controller
[ChildActionOnly]
public ActionResult Index()
{
tapnamron_PNMSiteMenu ent = new tapnamron_PNMSiteMenu();
return PartialView(ent.GetSiteMenu().ToList()); // .ToList() not necessary as the view is IEnemerable<T>
}