I'm trying to build an ASP.NET MVC 2 application. I want to pass data to a view from a controller. I am able to pass it when it is only a single data structure. In the controller:
private ArticlesDBEntities _db = new ArticlesDBEntities();
public ActionResult Articles()
{
return View(_db.ArticleSet.ToList());
}
and in the view, I iterated over the list like so:
<div id="demo1">
<% foreach (var item in Model) { %>
<ul>
<li id="<%= Html.Encode(item.Id) %>">
<%= Html.Encode(item.Title) %>
<ul>
<li id="phtml_2">
Child node 1
</li>
<li id="phtml_3">
Child node 2
</li>
</ul>
</li>
</ul>
<% } %>
</div>
(the child nodes are for testing reasons right now, don't have a real role)
However, I now want to handle a scenario when a user tries to access Home/Articles/Id, and not only pass the article list (used for populating a jsTree), but also the Article itself, so I can show it as well. However, when I tried creating a ViewData object like so:
public ActionResult Articles()
{
ViewData["articlesList"] = _db.ArticleSet.ToList();
return View();
}
I was unable to find out how to iterate over it in the view.
as far as passing multiple data items is concerned u can do it using view models (preferred way) or by viewdata. if u want to pass it through View model u can do something like
public class ArticlVM
{
public Article Myarticle{get;set;}
public IEnumerable<ArticleSet> Artcileset{get; set;}
}
u can populate this view model and pass it to view
in view u can access it like
<%=Model.Article.articlName%>
<%=Model.Article.articlDescription%>
<%foreach(var m in Model.Articleset){%>
<label><%=m.Property1%></label>
<%}%>
to iterate over ViewData["key"] u have to cast it to corresponding object like
<%=foreach(var m in (ArticleSet)ViewData["Articles"]){%>
<label><%=m.Property1%></label>
<%}%>
In your View you should be able to do
<% foreach(var item in ViewData["articlesList"]) %>
An alternative approach would be to create a new view model class that would hold both the article and the list:
class ArticleViewModel {
public Article Article { get;set;}
public IEnumerable<Article> Articles { get;set;}
}
and pass that into your view. Then you can access it through the Model property and you will get strong typing.
Related
I have a layout page which has a partial view. The partial view needs to loop through a property on the view model to show a list of categories. When a category is displayed I need to show a list of documents in that category. /Home/Index works, but when I try to view /Documents/Category/{id}, I get an error:
Additional information: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ViewModels.DocumentViewModel]', but this dictionary requires a model item of type 'ViewModels.HomeViewModel'.
_Layout.cshtml
...
<body>
#Html.Partial("_CategoryViewModel")
<div class="content">
#RenderBody()
</div>
HomeViewModel.cs
public class HomeViewModel {
...
public ICollection<DocumentCategory> Categories { get; set; }
public ICollection<Documents> Documents { get; set; }
...
}
_CategoryViewModel.cshtml (this should show a list of all categories)
#model ViewModels.HomeViewModel
...
#foreach (DocumentCategory item in Model.Categories)
{
<li>
<a href="#Url.Action("Category", "Documents", new { #id = #item.CategoryId })" title="View documents in the #item.Name category">
<span class="fa fa-files-o"></span> #item.Name
</a>
</li>
}
DocumentsController.cs
public ActionResult Category(int id)
{
var thisCategory = _ctx.Categories.Get(c => c.CategoryId == id).FirstOrDefault();
IEnumerable<DocumentViewModel> docs = null;
if(thisCategory == null)
{
TempData.Add("errorMessage", "Invalid category");
} else {
docs = thisCategory.Documents.ToList();
}
return View("Category", docs);
}
What's happening kind of makes sense - the PartialView on the Layout page needs to enumerate over a collection which isn't present in the ViewModel I'm using. I have no idea how to achieve this - the only way would seem to be to add a Categories property to every ViewModel in my site.
By default, using #Html.Partial() will pass the current model to the partial view, and because your Category.cshtml view uses #model List<DocumentViewModel>, then List<DocumentViewModel> is passed to a partial that expects HomeViewModel.
If you want to render a partial view for HomeViewModel on every page, then use #Html.Action() to call a ChildActionOnly method that returns the partial
[ChildActionOnly]
public ActionResult Categories
{
var model = new HomeViewModel()
{
.... // initialize properties
}
return PartialView("_CategoryViewModel", model)
}
and in the layout
#Html.Action("Categories", yourControllerName)
// or
#{ Html.RenderAction("Categories", yourControllerName); }
As I see it you have a few different alternatives.
1. Use Html.Action and create an Action that returns your view.
#Html.Action("Index", "Category") // Or your controller name.
I believe that there are some performance draw-backs with this approach because the whole MVC lifecycle will run again in order to render the result of the action. But then you can render the result of an action without having the correct model in the view that called it.
One may also argue that this breaks the MVC pattern, but it might be worth it.
2. Use a generic model (or an interface) in your _Layout.cshtml, and let your viewmodels inherit from that model.
In your _Layout.cshtml:
#model IBaseViewModel
And let all your viewmodels implement this interface.
public interface IBaseViewModel
{
ICollection<DocumentCategory> Categories { get; set; }
}
public interface IBaseViewModel<T> : IBaseViewModel
{
T ViewModel {get; set;}
}
Since you're placing #Html.Partial("_CategoryViewModel") in _Layout.cshtml I assume that it should be visible in all pages, so I think it's logical that all the controllers that are using _Layout.cshtml make sure that it gets the information it needs, and thus adding Categories to the model.
I use this approach all the time for stuff like breadcrumbs and menu-information (stuff that is used in all pages). Then I have a basecontroller that makes sure that Categories is populated with the correct info.
I have a partial view in which I have a model specified as such:
#model IEnumerable<AbstractThinking2015.Models.BlogModel>
#foreach (var item in Model.Take(5))
{
<li>#Html.ActionLink(item.Title, "Details", new { id = item.BlogModelId })</li>
}
And I'm calling this using:
<div class="widget">
<h4>Recent Posts</h4>
<ul>
#Html.Partial("View")
</ul>
</div>
But I'm getting the error:
The model item passed into the dictionary is of type
'AbstractThinking2015.Models.BlogModel', but this dictionary requires
a model item of type
'System.Collections.Generic.IEnumerable`1[AbstractThinking2015.Models.BlogModel]'.
I'm sure this is because the model being passed into the view is the single blog but I want to use the list that's defined in the partial view. Is there a way to do this?
If you do not pass a model t the partial explicitly, It will take the model of the parent view. So from your error message, It is clear that you are passing a single object of the BlogModel to your view from your action method, hence getting the error.
You need to make sure that your main view (where you are calling the partial), is also strongly typed to a collection of BlogModel objects.
public ActionResult Index()
{
var blogList=new List<Models.BlogModel>();
// or you may read from db and load the blogList variable
return View(blogList);
}
And the Index view, where you are calling the Partial will be strongly typed to a collection of BlogModel.
#model List<Models.BlogModel>
<h1>Index page which will call Partial</h1>
<div class="widget">
<h4>Recent Posts</h4>
<ul>
#Html.Partial("View")
</ul>
</div>
I have a shared _Layout.cshtml where I call the following #Html.Partial("TopMenu");
in TopMenu.cshtml I have an markup for website top menu.
<ul class="menu">
<li class="menu-item">menu item 1</li>
<li class="menu-item">menu item 2</li>
<li class="menu-item">menu item 3</li>
<li class="menu-item">menu item 4</li>
</ul>
I want to bind that menu from database.
I'm new to MVC. Should I create controller for that view?
This menu should be present in all of my pages. Give me an idea, how it's implemented in mvc. I'm from asp.net web forms country.
FUNCTION
public IEnumerable<Category> GetCategories(Guid? id)
{
return context.Categories.Where(c => c.CategoryID == id || !id.HasValue).ToList();
}
UPDATE
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public PartialViewResult TopMenu()
{
using (var ctx = new eventzEntities())
{
CategoryManager catMan = new CategoryManager(ctx);
var listOfCat = catMan.GetCategories(null);
return PartialView(listOfCat);
}
}
}
AND
#Html.Action("TopMenu","Home")
<div class="secondary-navigation">
<nav id="navigation">
<ul id="menu-main-navigation" class="menu sf-js-enabled">
#foreach (var cat in Model)
{
<li class="menu-item menu-item-type-taxonomy menu-item-object-category ">
#cat.CategoryName
</li>
}
...
Since all your views/pages will require this I suggest you create a base controller which will fetch the menu structure from the database and then provided it to all of your inheriting controllers. So for example you will make a base controller like this:
public class BaseController : Controller
{
public MenuModel Menu { get; set; }
protected override void Initialize(RequestContext requestContext)
{
// This get's your menu structure from the database via some service layer you need to create yourself.
// Keep in mind that this call will happen on every postback so if performance is an issue you might want to cache the menu in some way, maybe per user.
Menu = _menuServiceLogic.GetMenuItems();
ViewBag.Menu = Menu;
}
Once you have this then all your controller which generate views that contain the menu need to inherit from this base controller:
public class HomeController : BaseController
{
// Controller logic in here...
}
so now you have access to the menu on all of your controllers so all that is left is to pass it on to the view in some good way. I suggest you look into how to make a custom razor view class. There is a great article by Phil Haacked on this topic:
http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx
But in short every Razor view by default inherits from the System.Web.Mvc.WebViewPage. We are going to replace that class with our own implementation:
namespace YourApp.Website.Views
{
public abstract class CustomWebViewPage : WebViewPage
{
private MenuModel _menu;
public MenuModel Menu
{
get
{
try
{
_menu = (MenuModel)ViewBag.Menu;
}
catch (Exception)
{
_menu = null;
}
return _menu;
}
}
}
public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> where TModel : class
{
private MenuModel _menu;
public MenuModel Menu
{
get
{
try
{
_menu = (MenuModel)ViewBag.Menu;
}
catch (Exception)
{
_menu = null;
}
return _menu;
}
}
}
}
Now all you need to do is modify this line in your web.config:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
to this:
<pages pageBaseType="YourApp.Website.Views.CustomWebViewPage">
and you can now do the following in your views in order to reference your menu:
#Menu
The Menu variable is strongly typed (in my example it's of type MenuModel) and you can access all the properties of your menu on every controller actions/views that inherits from your base controller.
You can do this by passing strongly typed model to the partial view _TopMenu
#model IEnumerable<Category>
<ul class="menu">
#foreach(var category in Model)
{
<li class="menu-item">#category.Category.Name</li>
}
</ul>
How to pass?
You will simply do it as below:
#Html.Action("TopMenu","ControllerName")
Here, controllerName is the name of the controller where you have defined this method. I recommend you keeping this controller away from other controllers. You can simply call it as a PageController or something.
Have no idea how it should work?
You will define a controller method that returns the partial view with strongly typed model as below:
public ActionResult TopMenu()
{
return PartialView(db.Categories.ToList());
}
You need to make sure the menu is a partial view then call
#Html.Partial("location/to/view", modeL)
and then in the view code it should be
#model someDataModel.Models.Thing
<ul class="menu">
<li class="menu-item">#Model.item1</li>
<li class="menu-item">#Model.item2</li>
<l
i class="menu-item">#Model.item3
#Model.item4
A good tutorial for this can be found here
http://mvc4beginner.com/Tutorial/MVC-Partial-Views.html
You do not need a controller necessarily for a partial view.
If you want that TopMenu to be a view then you need to get a controller made for that
You can store your menu items in a ViewBag and access it in your view.
So in your controller you will have this :
ViewBag.Menus = ...
And in your TopMenu.cshtml
<ul class="menu">
#foreach(string menu in ViewBag.Menus){
<li class="menu-item">#menu</li>
}
</ul>
I'm using VS2012 RC with MVC4, bot for all intents and purposes let's pretend it's MVC3. I would like to know what the standard best practice(s) is on how to handle PartialViews with a form that uses a different model than the parent View.
For example, here is a view that displays a table of all the available Roles and also has a form that allows the user to add more roles.
Main View - Roles.cshtml:
#model IEnumerable<RobotDog.Models.RoleModel>
<table>
#foreach(var role in Model) {
<tr>
<td class="roleRow">#role.Role</td>
</tr>
}
</table>
<div class="modal hide">
#Html.Partial("_AddRolePartial")
</div>
_AddRolePartial.cshtml
#model RobotDog.Models.RoleModel
#using(Html.BeginForm("AddRole","Admin", FormMethod.Post)) {
#Html.TextBoxFor(x => x.Role, new { #class = "input-xlarge", #placeholder = "Role"})
<input type="submit" value="Submit" class="btn btn-primary btn-large"/>
}
Model:
public class RoleModel {
[Required]
[DataType(DataType.Text)]
[Display(Name = "Role")]
public string Role { get; set; }
}
Controller for View:
public ActionResult Roles() {
var model = from r in System.Web.Security.Roles.GetAllRoles()
select new RoleModel {Role = r};
return View(model);
}
Controller for PartialView:
[HttpPost]
public ActionResult AddRole(RoleModel model) {
try {
System.Web.Security.Roles.CreateRole(model.Role);
RedirectToAction("Roles");
} catch(Exception) {
ModelState.AddModelError("", "Role creation unsuccessful.");
}
return ????; // not sure how to pass ModelState back to partialView
}
I thought about creating a ViewModel that held RoleModel and IEnumerable<RoleModel> but it seems like there would be a more stream lined way to accomplish what I wanted without having to create a ViewModel everytime I wanted to use this PartialView.
I think you are asking how to pass a RoleModel to the add RoleModel modal popup. Since you are creating a new Role, I am assuming you are needing an empty model. You can either pass it in like below:
<div class="modal hide">
#Html.Partial("_AddRolePartial", new RoleModel())
</div>
Or just do a #Html.RenderAction("AddRole") with the supporing GET method of the controller to support populating the item.
public ActionResult AddRole() {
var model = new RoleModel();
//populate with any items needed for the Add Role Model View
return View(model);
}
I personally don't like using Partial views with forms, because Partial Views do not render submodels correctly (ie, they don't take into account the hierarchy of the model).
This is why Display and EditorTemplates exist. They work well for rendering specific data types.
However, in your case, since your view doesn't have any forms of its own, and the end result is just a single item of the collection of your parent model, then a Partial View is actually a better approach simply because you CAN pass a different model to it than the views uses.
As others have pointed out, you can easily pass an empty model to the partial as the second parameter. I don't like newing up new objects in the view, but it doesn't look like there's a lot of choice there, as the alternatives would be pretty messy.
How about the form post is changed to an ajax form post with a target update partial id being the div which you will add to the parent view (effectively surrounding Roles.cshtml).
Add a new action public ActionResult _Roles() which will return PartialView("Roles", model)
Next, in the Post Action, Return RedirectToAction(...Roles Partial Action ...) at the end and remove RedirectToAction("Roles") in the try.
I have a viewmodel class for a create view that consists of 2 properties different classes, and a list of selectitems (for dropdownlist). The dropdown list and the fields for one of the classes are always shown in the view,but the fields for the 2nd class are only shown if a specific option of the list is selected. (via a jQuery load and a controller that returns a partial view, strongly typed for the 2nd class)
The fields in the view for the second class are some normal input=text fields and a dropdown list.
When I submit the form, all of the data will be properly binded, except for the values for the list inside the second class.
The problem is that if there is a validation error, and I return the view with the model (to show the errors) then the dropdownlist (the one for the second class) is empty, and since there is no way to choose from it, then the submit will always have isValid=false :S
This is an example of how I have setup things:
I currently have a view model that is looks like the following:
public class ViewModel
{
public GenericData genericData{get;set;}
public SpecializedData specializedData {get;set;}
public List<SelectListItem> types { get; set; }
}
public class GenericData
{
public Type type {get;set;}
public String name {get;set;}
public String description{get;set;}
}
public class SpecializedData
{
public String field{get;set;}
public int foreignKey{get;set;}
public List<SelectListItem> listOfForeignKeys{ get; set; }
}
The view is strongly typed for ViewModel and is setup this way:
<p>
<label for="genericData.type"> Type: </label>
<%=Html.DropDownList("genericData.type",Model.types, "Choose a type") %>
</p>
<p>
<label for="genericData.name"> Name:</label>
<%: Html.TextBox("genericData.name")%>
<%= Html.ValidationMessage("genericData.name", "*")%>
</p>
//Same thing for Description as for name
<div id="SpecializedFields">
//This will display the specialized info it was already captured before.
<% if (Model.SpecializedData!= null && Model.GenericData.Type == "Specialized")
{
Html.RenderPartial("SpecializedInfoView", Model.specializedData);
} %>
</div>
<p>
<input type="submit" value="Create" />
</p>
$(document).ready(function () {
$("#CatalogItem_Type").change(function () {
var typeVal = $("#GenericData_type").val();
if (typeVal == "specialized") {
$("#SpecializedFields").load("/MyController/SpecializedFields");
}
the controller will do something like this
public ActionResult SpecializedFields()
{
List<SelectListItem> foreignIds = getForeignIdsFromDataBase();
SpecializedData model = new SpecializedData ();
model.listOfForeignIds = foreignIds;
return PartialView("SpecializedInfoView",model);
}
Finally, the SpecialezedInfoView is a strongly typed view for SpecializedData and is similar to the generic one, it goes like this:
<p>
<label for="specializedData.foreignKey"> Key: </label> <%=Html.DropDownList("specializedData.foreignKey",Model.listOfForeignKeys, "Choose key") %>
</p>
<p>
<label for="SpecializedData.field"> Field:</label>
<%: Html.TextBox("specializedData.field")%>
<%= Html.ValidationMessage("specializedData.field", "*")%>
</p>
When I click submit, if its not a specialized item, i will get a null for the specializedData property, which is ok. If it is a specialized item, then it will not be null, I'll get all the properties from the genericData and I'll get the specializedData.field and specializedData.foreignKey value too, but the specialized.listOfForeignKeys will be a NULL which means that if there is a validation error and i return the view with the current model, then everything will have the previously entered values, BUT the DropDownList with the list of foreign keys will be empty, and there is no way to choose an id now.
I know i could check to see if the specializedData is not null in the method that receives the post, and re-generate the list of keys and assign it to the current model (since it is the only data it is missing), but that would mean having to do extra DB calls, instead of the info being there with the model!
I know it is a long question, but I wanted to be explicit on how I am setting up things, do somebody know why is the listOfForeignKeys not being passed back the the model after postback!?
Thanks!
I figured out... the thing is that values for DropDownLists are NOT sent back to the model when you do I binding... however, since the binding automatically calls the constructor of the viewmodel the List<SelectItemList> Types was being filled there on not really because of the binding. The second list is not built in the constructor, thus it came as null. I added the query to get the information for the list in the SpecializedData constructor, and now its working..
Still it kind of sucks that when the user hits post there will be an extra call to the DB, and more times so if there are validation errors. Maybe I'll try to do a cache to store the query results, but for the time being, this is enough.
Thanks!