Alternatives to Partial Views - asp.net-mvc

There is a requirement to share list items (li) among multiple unordered lists (ul).
The current solution is to create a PartialView for each group of list items to be shared, for example:
#*PartialViewOne*#
<li>item1</li>
<li>item2</li>
<li>item3</li>
#*PartialViewTwo*#
<li>item1</li>
<li>item2</li>
#*ViewOne*#
<ul class="CustomClassONE">#Html.Partial("PartialViewOne")</ul>
#*ViewTwo*#
<ul class="CustomClassTWO">#Html.Partial("PartialViewOne")</ul>
Since there are many list items groups, this will require creating many PartialViews. Not that it is difficult to create them but just looking for more efficent way of accomplishing the same thing. What alternatives are out there for using the above technique?

I would advice to create those lists using Controllers and add them to viewmodels for the pages you use, probably create some class that will return a collection of items on demand and put these items to viewmodel. Then display them using .ComboBoxFor(x=>...)
Something like this:
public ActionResult Index() {
var viewModel = new ViewModel();
viewModel.ListItems = listItemFactory.GetItemsForFirstCase(); //populate items you use in your example as PartialViewOne
return View(viewModel);
}
public ActionResult Search() {
var viewModel = new ViewModel();
viewModel.ListItems = listItemFactory.GetItemsForSecondCase();//populate items you use in your example as PartialViewTwo
return View(viewModel);
}
This way your view code will be much cleaner - you will need 1 partial / DisplayTemplate / EditorTemplate for all cases, and all data can be combined in any way you will need.

If the type of data you want to display in the partial are same, you may create a viewmodel to represent that,
public class MyCustomList
{
public string ListHeader {set;get;}
public List<string> Items {set;get;}
public MyCustomList()
{
Items= new List<string>();
}
}
Then add new properties of this type to your other viewmodels as needed
public class CustomerDetailsVm
{
public string FirstName {set;get;}
public MyCustomList RecentOrders {set;get;}
}
public class ProductDetails
{
public string ProductCode {set;get;}
public MyCustomList SimilarProducts {set;get;}
}
And in your Action methods, you will set your view/action specific values to this property
public ActionResult CustomerDetail(int id)
{
var vm = new CustomerDetailsVm { FirstName = "Shyju" };
vm.RecentOrders = new MyCustomList
{
ListHeader = "Recent Orders",
Items = new List<string>
{
"IPad",
"Beer"
}
};
return View(vm);
}
public ActionResult ProductDetail(int id)
{
var vm = new ProductDetails { ProductCode = "Accord" };
vm.SimilarProducts = new MyCustomList
{
ListHeader = "Similar Products",
Items =new List<string>
{
"Altima",
"Camry"
}
};
return View(vm);
}
And in your views
#model ProductDetails
<h2>#Model.ProductCode</h2>
#Html.Partial("ListPartial",Model.SimilarProducts)
and
#model CustomerDetailsVm
<p>#Model.FirstName</p>
#Html.Partial("ListPartial",Model.RecentOrders)
And your partial view will be strongly typed to our MyCustomList viewmodel
#model MyCustomList
<h1>#Model.ListHeader</h1>
<ul>
#foreach(var item in Model.Items)
{
<li>#item</li>
}
</ul>

This is not an alternative to PartialViews but rather to the way PartialViews are traditionaly used. Tested and was inspired by the following thread. Very interesting approach for extracting static html fragments from a single PartialView.
<!-- ViewOne -->
<ul>
#Html.Partial("SharedPartialView", "ListA")
</ul>
<!-- ViewTwo -->
<ul>
#Html.Partial("SharedPartialView", "ListA")
</ul>
<!-- SharedPartialView that includes all list items -->
#if (Model == "ListA")
{
<li>item1</li>
<li>item2</li>
<li>item3</li>
}
#if (Model == "ListB")
{
<li>item1</li>
<li>item2</li>
}

Another robust alternative as well is to use JQuery to extract html fragments from the SharedPartialView and inject them into the Views:
<!-- LayoutView -->
<!-- load fragments/lists PartialView in a hidden div-->
<div style="display:none">#Html.Partial("SharedPartialView")</div>
<!-- append appropriate fragments to multiple elements using JQuery -->
<script type="text/javascript">
$(document).ready(function () {
var test = $("#ListA").contents();
test.appendTo("#ulOne, #ulTwo");
});
</script>
<!-- ViewOne -->
<ul id="ulOne">
</ul>
<!-- ViewTwo -->
<ul id="ulTwo">
</ul>
<!-- SharedPartialView that includes all lists -->
<ul id="ListA">
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
<ul id="ListB">
<li>item1</li>
<li>item2</li>
</ul>

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>
}

I use viewmodel to bass to list of objects one to use in partialview of strongly type but this error is occurred

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MvcApplication1.Models.News]', but this dictionary requires a model item of type 'MvcApplication1.Models.News'.
//my Controller
public class HomeController : Controller
{
CouncilDb _db=new CouncilDb() ;
public ActionResult Index()
{
var News = _db.News
.Take(10);
var Banner = (from r in _db.Banner orderby r.id descending select r).FirstOrDefault();
maz model = new maz();
model.Banner = Banner;
model.News = News.ToList();
return View(model);
}
protected override void Dispose(bool disposing)
{
if (_db != null)
{
_db.Dispose();
}
base.Dispose(disposing);
}
}
public class maz
{
public List<News> News { get; set; }
public Banner Banner { get; set; }
}
//Index view
#model MvcApplication1.Controllers.maz
#{
ViewBag.Title = "Home Page";
}
<!-- Banner -->
<!-- Banner -->
<div id="banner">
<h2> #Model.Banner.H2</h2>
<span class="byline"> #Model.Banner.Span </span>
</div>
#Html.Partial("_News",Model.News )
//Partialview
#model MvcApplication1.Models.News
<!-- Carousel -->
<div class="carousel">
<div class="reel">
<article>
<a class="image featured">
<img src="#Model.ImgUrl " alt="" /></a>
<header>
<h3>#Html.ActionLink(#Model.Title , "serch", "Home")</h3>
</header>
<p>#Model.Body </p>
</article>
</div>
</div>
The .News property of your model is of type List<News> not News so the model declaration in your partial view and the model you are passing to it don't match.
Depending on what you want to acheive, you can either loop through the List inside the index view:
#foreach (var news in Model.News)
{
#Html.Partial("_News", news)
}
or adjust the partial view model declaration and loop there
#model List<News>
....
#for (var news in Model)
{
<article>
<a class="image featured">
<img src="#news.ImgUrl" alt="" /></a>
<header>
<h3>#Html.ActionLink(#news.Title , "serch", "Home")</h3>
</header>
<p>#news.Body</p>
</article>
}
The error is pretty straightforward, once you get past the generic syntax. In C#-speak it's saying this:
The model item passed into the dictionary is of type 'List<News>',
but this dictionary requires a model item of type 'News'.
Your partial view is declared with a #model clause that specifies an item of type News:
#model MvcApplication1.Models.News
but when you pass in data to that partial view, what you're passing in is a List<News>:
#Html.Partial("_News",Model.News)
What you probably want is a loop (a #foreach or similar) that creates one partial view for each element in your News list.

MVC 3 using #model twice

I would like to using two times #model to get data from another part of my website is it possible? Because now I have got error but if I have only this first #model everything working correct.
Look -> MVC 3 - Exception Details: System.InvalidOperationException
Error 2 'SportsStore.Entities.Kategorie' does not contain a definition for 'Opis' and no extension method 'Opis' accepting a first argument of type 'SportsStore.Entities.Kategorie' could be found (are you missing a using directive or an assembly reference?) c:\Users\Rafal\Desktop\MVC ksiązka\moj projekt\sklep\SportsStore.WebUI\Views\Product\List.cshtml 16 4 SportsStore.WebUI
#model IEnumerable<SportsStore.Entities.Towar>
#model IEnumerable<SportsStore.Entities.Kategorie>
#{
ViewBag.Title = "List";
}
<h2>List</h2>
#foreach (var p in Model)
{
<div class="item">
<h3>#p.Nazwa</h3>
#p.Opis
<h4>#p.Cena.ToString("c")</h4>
</div>
}
You only can have one Model per View. But you can use another object to declarate the model:
public class SomeViewModel
{
public IEnumerable<Towar> Towars;
public IEnumerable<Category> Categories;
public SomeViewModel(IEnumerable<Towar> towars, IEnumerable<Category> categories) {
Towars = towars;
Categories = categories;
}
}
And then use it in your view like this:
#model SportsStore.Entities.SomeViewModel
#foreach (var item in Model.Towars)
{
<div class="item">
<h3>#p.Nazwa</h3>
#p.Opis
<h4>#p.Cena.ToString("c")</h4>
</div>
}
#foreach (var item in Model.Categories) {
#item.Name #* or what you need down here *#
}
I would also recommend you to use english names in MVC. It's more clear to read and understand ;).
I think this would be a case to create a ViewModel (to combine the two entities you have) and then base a View off that ViewModel.
It is best to create a view model to represent your data. Your view model must only contain what you need to display on your view. Anything that is not used you can remove, no point of it being there. Your view model can look like this:
using SportsStore.Entities;
public class YourViewModel
{
public IEnumerable<Towar> Towars { get; set; }
public IEnumerable<Kategorie> Categories { get; set; } // I assume this is categories
}
Lets say that you have to use this view model in a create view then your create action can look something like this:
public class YourController : Controller
{
private readonly ITowarRepository towarRepository;
private readonly ICategoryRepository categoryRepository;
public YourController(ITowarRepository towarRepository, ICategoryRepository categoryRepository)
{
this.towarRepository = towarRepository;
this.categoryRepository = categoryRepository;
}
public ActionResult Create()
{
YourViewModel viewModel = new YourViewModel
{
Towars = towarRepository.GetAll(),
Categories = categoryRepository.GetAll()
};
return View(viewModel);
}
}
And then in your view:
#model YourProject.DomainModel.ViewModels.YourViewModel
#foreach (var towar in Model.Towars)
{
// Do whatever
}
#foreach (var category in Model.Categories)
{
// Do whatever
}

How can I bind a IEnumerable sequence to a container with multiple columns?

I believe that I couldn't find a proper title to explain my problem but I think this is the best possible short explanation.
Please let me explain the details.
I want to show a list of my pictures on a page and using a #foreach loop on MVC 3.
Partial View of this list as below:
#model IEnumerable<Picture>
#foreach (var item in Model)
{
<a href="#item.PicId">
<img height="35px" style="padding-top:3px" src="ImageHandler.ashx?id=#item.PicId" id="pictureMy" />
</a>
}
As you may understand I am sending a list to this partialview and it is placing the pictures on a single column.
It is working without any problem but I want to show 3 pictures for each row but couldn't manage.
Any guidance will be very appreciated.
Thanks in advance for your helps.
You could group them by 3:
#model IEnumerable<Picture>
#foreach (var item in Model.Select((value, index) => new { value, index }).GroupBy(x => x.index / 3))
{
<div>
#foreach (var picture in item)
{
<a href="#picture.value.PicId">
<img height="35px" style="padding-top:3px" src="ImageHandler.ashx?id=#picture.value.PicId" id="pictureMy" />
</a>
}
</div>
}
But honestly this grouping is not something that should be done in the view. You should define a view model and then have your controller action perform the grouping and return the view model.
So let's start by defining our view models:
public class PictureViewModel
{
public int PicId { get; set; }
}
public class GroupedPicturesViewModel
{
public IEnumerable<PictureViewModel> Pictures { get; set; }
}
then the controller action:
public ActionResult Index()
{
// fetch the pictures from the DAL or something
IEnumerable<Picture> pictures = ...
// Now build the view model
var model = pictures
.Select((value, index) => new { value, index })
.GroupBy(x => x.index / 3)
.Select(x => new GroupedPicturesViewModel
{
Pictures = x.Select(p => new PictureViewModel
{
PicId = p.value.PicId
})
}
);
return View(model);
}
then the corresponding view:
#model IEnumerable<GroupedPicturesViewModel>
#Html.DisplayForModel()
then the corresponding display template for the GroupedPicturesViewModel type (~/Views/Shared/DisplayTemplates/GroupedPicturesViewModel.cshtml):
#model GroupedPicturesViewModel
<div>
#Html.DisplayFor(x => x.Pictures)
</div>
and finally the display template for the PictureViewModel type (~/Views/Shared/DisplayTemplates/PictureViewModel.cshtml):
#model PictureViewModel
<a href="#Model.PicId">
<img class="image" src="#Url.Content("~/ImageHandler.ashx?id=" + Model.PicId)" alt="" />
</a>
One final thing that's bugging me is this anchor. Looks ugly. Don't you think? Looks like spaghetti code.
Let's improve it by writing a custom, reusable HTML helper which will render those pictures:
public static class HtmlExtensions
{
public static IHtmlString Picture(this HtmlHelper<PictureViewModel> htmlHelper)
{
var anchor = new TagBuilder("a");
var picture = htmlHelper.ViewData.Model;
var id = picture.PicId.ToString();
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
// You probably need another property on your view model here as this
// id is suspicious about href but since that's what you had in your
// original code I don't know what is your intent.
anchor.Attributes["href"] = id;
var image = new TagBuilder("img");
image.Attributes["alt"] = "";
image.Attributes["src"] = urlHelper.Content(
"~/ImageHandler.ashx?id=" + urlHelper.Encode(id)
);
image.AddCssClass("image");
anchor.InnerHtml = image.ToString();
return new HtmlString(anchor.ToString());
}
}
and then in the display template we will simply have:
#model PictureViewModel
#Html.Picture()
And that's pretty much it. No need to write loops. Everything works by convention.

In ASP.NET MVC, how can you bind several select lists to an IList<string> collection?

I have a model with a List<string> property. I want to present several select lists that bind to that property.
For example, supposed my model is named Favories, and I let the user select several favorite colors.
public class Favorites
{
public List<string> FavoriteColors { get; set;}
}
I tried binding using the indexes to the collection, but I ran into problems, most likely because FavoriteColors was empty. Here's the code that doesn't work (null exception):
#Html.DropDownListFor(m => m.FavoriteColors[0], ColorSelectList, "Select a color (required)")
#Html.DropDownListFor(m => m.FavoriteColors[1], ColorSelectList, "Select a color (optional)")
#Html.DropDownListFor(m => m.FavoriteColors[2], ColorSelectList, "Select a color (optional)")
I realize I could fix this a couple ways.
Populate FavoriteColors with 3 empty values. But this doesn't feel right since my model would have invalid data (empty values) that I'd have to workaround in a bunch of other places in my code.
Change my model so that I have 3 string properties (e.g. FavoriteColor1, FavoriteColor2, FavoriteColor3). Binding would be easier, but I'd still have to work around that deisgn with a bunch of code.
Is there a better way?
Here is a simple solution that will work for you using Razor and standard DropDownListFor<T> helpers.
All based on example data and files which you can change to suit your needs:
HomeController.cs
public class HomeController : Controller
{
public class FavoriteColorModel
{
public List<string> FavoriteColors { get; set; }
}
public ActionResult Index()
{
ViewBag.ColorList = new[]
{
"Blue",
"Red",
"Green",
"Orange"
};
var favoriteColors = new FavoriteColorModel()
{
FavoriteColors = new List<string>()
{
"Color1",
"Color2",
"Color3"
}
};
return View(favoriteColors);
}
[HttpPost]
public ActionResult Save(FavoriteColorModel model)
{
TempData["SelectedColors"] = model.FavoriteColors;
return RedirectToAction("Index");
}
}
Index.cshtml
#model MvcApplication4.Controllers.HomeController.FavoriteColorModel
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
#if (TempData["SelectedColors"] != null)
{
<text>
You have selected the following colors:<br />
<ul>
#foreach (var color in TempData["SelectedColors"] as List<string>)
{
<li style="color:#color">#color</li>
}
</ul>
</text>
}
#using (Html.BeginForm("Save", "Home"))
{
#: Favorite colors:
for (var index = 0; index < Model.FavoriteColors.Count; index++)
{
#Html.DropDownListFor(model => model.FavoriteColors[index], new SelectList(ViewBag.ColorList))
}
<br />
<input type="submit" value="Save" />
}
Your selected colors will appear in a list element upon submit. You can control the amount of colors you wish to save by adding items to the Color1, Color2 array etc.
Peace.
This is how I would do it because I dont like the way MVC handles drop down lists.
foreach (var ColorArray in m.FavoriteColors)
{ %>
<select name='colors'>
<% foreach (var color in ColorArray)
{%>
<option><%= color.ToString() %></option>
<% { %>
</select>
}%>
You could hard code it to just the indexes you want but you get the general idea. Also this would allow you to put in null checks whereever you like to handle those cases selectively.

Resources