asp.net mvc - multiple databound components on a page - asp.net-mvc

Im new to asp.net mvc and just learning the basics right now.
Im wondering how pages with muliple databound items would work with mvc views.
for example say there is a page that lists a bunch of "news articles" from a "NewsArticles" table.
and in the side of the page there is a another list which contains a list of "CaseStudies" for example.
then how would that be achieved in mvc?

You'd create your own view model class:
public MyPageViewModel
{
public IEnumerable<NewsArticles> Articles{get;set;}
public IEnumerable<CaseStudies> CaseStudies{get;set;}
}
Return it as the model in your action:
public ActionResult MyPage()
{
var model = new MyPageViewModel();
model.Articles = ArticleManager.GetArticles();
model.CaseStudies = CaseStudyManager.GetCaseStudies();
return View(model);
}
Then you can use a strongly typed view of type ViewPage<MyPageViewModel>, and output them like this:
<ul>
<% foreach(NewsArticle article in Model.Articles){%>
<li><%=article.Title%></li>
<%}%>
</ul>
<ul>
<% foreach(CaseStudy caseStudy in Model.CaseStudies){%>
<li><%=caseStudy.Title%></li>
<%}%>
</ul>

One option would be to specify the ViewData in the action method:
ViewData["NewsArticles"] = GetNewsAticles();
ViewData["CaseStudies"] = GetCaseStudies();

Related

ASP.NET MVC Database Driven Menu in shared view

I need some guidance on inserting a menu in the _layout.cshtml file. I have hit two problems:
1) Even when I create an additional model to include two data models, I am unable to pass both the models to the layout file
eg:
Model:
public class IndexModel
{
public tblMenu tblMenu { get; set; }
public tblSite tblSite { get; set; }
}
I need info from the Menu table and the site table on the same page.
2) When I create a partial view to pass the menu data I continually get exceptions telling me that I can't use the model in this way.
My Partialview:
#model mvcSPS.Models.IndexModel
<li>#Model.tblMenu.MenuName</li>
My Controller:
public ActionResult _menu()
{
return PartialView(db.IndexModels.ToList());
}
My _layout.cshtml
<ul id="navigation">
#foreach (var item in Model)
{
#Html.Partial("_menu")
}
</ul>
I have trawled the net and to be quite frank.. I am having a really difficult transition from ASP classic (yes I know) to ASP.net and MVC.
Your gentle guidance would be much appreciated.
Thanks
Andrew
change your #Html.Partial in your _layout.cshtml to call the controller function and render the result of the Action method.
#foreach (var item in Model)
{
Html.RenderAction("_menu", "Home");
}
note: you shouldn't need a prepending '#' since it's in the context of the foreach loop
EDIT: Based on my comment suggestion below
HomeController:
public ActionResult Menu() {
return PartialView("_menu", db.IndexModels.ToList());
}
_layout.cshtml
#{Html.RenderAction("Menu", "Home");} //be sure to fully-qualify the controller since it's layout, otherwise it'll look to the current controller based on route values collection
_menu.cstml
<nav>
<ul>
#foreach(var item in Model) {
Html.Partial("_menuItem", item)
}
</ul>
</nav>
_menuItem.cshtml
#foreach(var item in Model) {
<li>
text
#if(item.Children.Any())
{
<ul>
Html.Partial("_menuItem", item.Children)
</ul>
}
</li>
}

ASP.NET Error: "Unable to cast object of type '<>f__AnonymousType1`2"

I very new to .NET and Entity Framework, and I have a problem with my code (below). I am getting the following error:
Unable to cast object of type '<>f__AnonymousType1`2[
SamWinInterface.Models.tbl_interface_category,
SamWinInterface.Models.tbl_interface_menu]' to type
'SamWinInterface.Models.tbl_interface_menu'.
This is my code:
public ActionResult Index(int id=-1)
{
ViewBag.Menus = from menu in _db.tbl_interface_menu
join cat in _db.tbl_interface_category on
menu.fld_category_id equals cat.id where
cat.fld_customer_id == id select new { cat, menu };
return View();
}
I'm trying to get menus depending on which category is chosen.
Something like:
<% foreach (tbl_interface_menu m in (IEnumerable)ViewBag.Menus)
{ %>
<%= m.fld_section2_title %>
<% } %>
but I'm getting the above error. How can I get the menus?
You cannot pass anonymous objects to views. This doesn't work because anonymous types are emitted as internal. And because ASP.NET views are compiled into a separate assembly at runtime they cannot access those times because they reside in a different assembly. This basically means that an anonymous object that you have defined in your controller action cannot be accessed in your view.
So as always in an ASP.NET MVC application start by defining view a model:
public class MyViewModel
{
public Category Category { get; set; }
public Menu Menu { get; set; }
}
then have your controller action fill this view model and pass it to the view:
public ActionResult Index(int id=-1)
{
var model =
from menu in _db.tbl_interface_menu
join cat in _db.tbl_interface_category
on menu.fld_category_id equals cat.id
where cat.fld_customer_id == id
select new MyViewModel { Category = cat, Menu = menu };
return View(model);
}
and finally have a strongly typed view:
<%# Page
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<AppName.Models.MyViewModel>>"
%>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% foreach (var item in Model) { %>
<%= item.Menu.fld_section2_title %>
<% } %>
</asp:Content>
As Darin said, you cannot pass anonymous types to views, but you could convert them to Expando objects, and that would prevent you from having to define viewmodels.
Personally I would probably just define viewmodels, but this option is handy in a pinch.

Where should I put this code?

Here's the code, written to be written in a controller:
CategoryRepository categoryRepo = new CategoryRepository();
var categories = categoryRepo.FindAllCategories();
ViewBag.Categories = categories;
Now I'd like to use this to create a nice list of Categories dynamically.
<div id="leftnavigationbar">
<ul>
#foreach (var category in ViewBag.Categories)
{
//Create li here.
}
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
<li>#Html.ActionLink("Peliculas, Musica & Juegos", "Index", "Anuncios")</li>
<li>#Html.ActionLink("Computadoras", "Index", "Usuarios")</li>
<li>#Html.ActionLink("Bienes Raices", "Index", "Ayuda")</li>
<li>#Html.ActionLink("Bolsa de Trabajo", "Index", "Contacto")</li>
<li>#Html.ActionLink("Deportes y Fitness", "Index", "Contacto")</li>
<li>#Html.ActionLink("Electronicos y Celulares", "Index", "Contacto")</li>
</ul>
</div>
Right now I'm writing this code to the _Layout.cshtml file. But I'd like to know where to write this so it runs always, sort of like a MasterPage.
Any suggestions?
Edit:
It appears that my initial intent isn't possible.
#foreach (var category in ViewBag.Categories)
{
<li>#Html.ActionLink(category.Name, "Index", "Home")</li>
}
Any suggestions on how accomplish what I'm trying to do? Just pull a list of categories and render them using a foreach loop. Then have it placed somewhere so it's viewable on all pages.
As always you start by creating a view model:
public class CategoryViewModel
{
public string CategoryName { get; set; }
}
Then in your controller you fill the view model:
public ActionResult Index()
{
var categories = categoryRepo.FindAllCategories();
var model = MapModelToViewModel(categories);
return View(model);
}
And finally your strongly typed view could use a display template. Obviously the view will be strongly typed to IEnumerable
<ul>
#Html.EditorForModel()
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
...
</ul>
And your display template (~/Views/Home/DisplayTemplates/CategoryViewModel.cshtml):
#model YourApp.Models.CategoryViewModel
<li>#Model.CategoryName</li>
As you can see with strongly typed views and display templates you don't even need to write loops in your views.
As an alternative to display templates you could use Html.Action or Html.RenderAction helers. Phil Haack wrote a nice blog post about them. I suspect that you were forced to use the ugly untyped ViewBag because this code is situated in your master template and you don't have a view model. No problem, with Html.Action you could have a specific controller that will do this:
public class CategoriesController: Controller
{
public ActionResult Index()
{
var categories = categoryRepo.FindAllCategories();
var model = MapModelToViewModel(categories);
return View(model);
}
}
And then have a corresponding partial view which will take care of rendering the categories (~/Views/Categories/Index.cshtml):
#model IEnumerable<YourApp.Models.CategoryViewModel>
#Html.DisplayForModel()
And finally use the same display template.
Now in your master page you could simply include this child action:
<ul>
#Html.Action("index", "categories")
<!-- ActionLink goes: Text, ActionName, Controller -->
<li>#Html.ActionLink("Libros", "Index", "Home")</li>
...
</ul>
You're looking for something called a 'partial view'.

ASP.NET MVC: Cities list and store selected city in cookie

I have list of cities on my site, this list placed in Site.Master and look like:
<a id="<%= selectedCity.CityId %>"><%= selectedCity.Name %></a>
<ul>
...
<li id="<%= city.CityId %>" >
<%= Html.ActionLink(city.Name,"ChangeCity",new{ newCityId = city.CityId })%>
</li>
...
</ul>
Next, all my controller are based from BaseController, it contain next code:
public int CityId {get;set;}
protected override void OnActionExecuting(ActionExecutingContext filterContext){
if (filterContext.ActionDescriptor.ActionName.ToLower() != "changecity"){
this.CityId = Convert.ToInt32(Request.Cookies["SiteCityId"]);
var city = LoadCity(this.CityId);
var cities = LoadAllCities();
CitiesModel model = new CitiesModel() { selectedCity=city, allCities=cities};
ViewData["citiesModel"] = city;
}
else{
Response.Cookies["SiteCityId"]=filterContext.ActionParameters["newCityId"];
}
}
In all my controllers I was add next action:
[HttpGet]
public ActionResult ChangeCity(string newCityId)
{
return RedirectToAction(this.MyDefaultAction);
}
Main question: this schema not so good work. In IE8 sometimes I cant change current city use links like next:
http://www.mysite.com/home/changecity/?newCityId=3
And what you think about this schema at all? May be you use other methods for create functionality?
Interesting idea; it might be better served by using the new Html.Action() feature in MVC 2, where you can, in your view, call <%= Html.Action("ChangeCity", "Common") %> to display the change city action in a common controller, and in there is the logic to do this, rather than doing it for every action.
HTH.

Modelbinding using Interfaces in ASP.NET MVC 2

I have the following View Data:
public class ShoppingCartViewData
{
public IList<IShoppingCartItem> Cart
{
get;
set;
}
}
I populate the viewdata in my controller:
viewData.Cart = CurrentSession.CartItems;
return View(viewData);
And send the data to the view and display it using:
<% for (int i = 0; i < Model.Cart.Count; i++ ) { %>
<%= Html.TextBoxFor(m => m.Cart[i].Quantity)%>
<%= Html.HiddenFor(m => m.Cart[i].Id) %>
<% } %>
I want to be able to catch the viewdata on the post. When I try:
[HttpPost]
public ActionResult UpdateCart(ShoppingCartViewData viewData)
{
...
}
When I run this I get a: System.MissingMethodException: Cannot create an instance of an interface.
Can anyone shed some light on this. What would I have to do to get this to work?
Many Thanks
You could try adding the formcollection as a parameter. And shouldn't viewdata be the viewmodel you're using?
[HttpPost]
public ActionResult UpdateCart(ShoppingCartViewModel viewModel, FormCollection collection)
{
...
}
Not sure if this is the exact solution, i'm also busy learning MVC2.0 and .NET4 ;-)
I'd create a model binder for your ViewModel, and then you can instantiate a concrete type that implements the appropriate interface when it binds to the method parameters.
You can insert logic into your model binder to read the form fields as appropriate and then instantiate the right IList or IShoppingCartItem data, so no need to worry about being pinned to a single implementation of the interface either.
Given my two comments this is how I would do it:
// you don't need this
// viewData.Cart = CurrentSession.CartItems;
// return View(viewData);
// do it like this
return View(CurrentSession.CartItems);
Then have a strongly typed View either this:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Administration.Master" Inherits="System.Web.Mvc.ViewPage<ShoppingCartViewData>" %>
or this:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Administration.Master" Inherits="System.Web.Mvc.ViewPage<List<IShoppingCartItem>>" %>
Also this code won't work. This will generate you a bunch of textboxes with the same name and id. You need to generate textboxes with a count and for that you won't be able to use
Html.TextBoxFor(). You will have to revert to Html.TextBox() or create a new extension TextBoxFor() method which would also accept a number (for you count).
<% for (int i = 0; i < Model.Cart.Count; i++ ) { %>
<%= Html.TextBoxFor(m => m.Cart[i].Quantity)%> // this won't work, if you want to post back all textboxes after they are edited
<%= Html.HiddenFor(m => m.Cart[i].Id) %>
<% } %>
HTH
I got the same exception after adding an 'int' parameter to an action method.
I discovered by putting a break point in the controllers constructor that one of the other action methods (not the one specified in the forms post arguments) was being called instead.

Resources