ASP.NET MVC Master Page Data - asp.net-mvc

The more I use ASP.NET MVC, the more I love it. However, in the case of showing model data on master pages there seems several ways of doing it. I am unsure as to the best solution.
My example would be a commerce site where I want to output a list of product categories on every page and also show the status of the visitors cart.
In asp.net web forms I would typically do this using user controls each doing their own databinding to retrieve the required data.
In MVC all data should be passed by the controller.
So regarding the categories the simplest solution would seem to be to pass this in View data in the controller action:
ViewData["Categories"] = _service.GetCategories();
However, doing this for every action isn't very DRY so following this article I created a base controller that adds the required data to my ViewData:
public class AppController : Controller
{
IAppService _service;
public AppController() { }
public AppController(IAppService appService)
{
_service = appService;
SetSiteData();
}
private void SetSiteData()
{
ViewData["Categories"] = _service.GetCategories();
}
}
I then created an extension for ViewMasterPage:
public static void RenderCategoryList(this ViewMasterPage pg) {
pg.Html.RenderPartial("CategoryList", pg.ViewData["Categories"]);
}
And in my MasterPage:
<div>
<%this.RenderCategoryList(); %>
</div>
This seems quite a clean approach. However, is this the best way as I have also seen suggestions of creating a ViewModel for your MasterPage. I could see that perhaps as your ViewModel data grows, this may be a better solution.
Regarding the cart status, I suppose I would do something similar but am unsure whether RenderAction would be more appropriate (When to use RenderAction vs RenderPartial with ASP.NET MVC).
Thanks,
Ben

That works, although it's not the way I would do it for 2 reasons:
I don't like sticking data into ViewState since you essentially cast it as object
By requiring a base controller you're limiting the functionality to controllers that inherit from this basecontroller (which might not be an issue though)
I think this would be a perfect use of RenderAction (part of the MvcFutures project). This helper can be used to render an Action on another controller. So you might have a ProductController with a ListCategories action, you could just do something like:
<% Html.RenderAction<ProductController>(x => x.ListCategories()); %>
ListCategories would call
_service.GetCategories();
and might stick the info into its own Model. Then it would pass that model to the View would would be a Partial Page.

Thank you - RenderAction was perfect the job.
I got more information from here.
So for the benefit of others, here is an example of how I am outputting cart status:
Action:
[ChildActionOnly]
public ActionResult CartStatus()
{
return PartialView(_service.GetCartSummary());
}
Partial View (bound to Models.Cart)
<div class="cartSummary">
<%if (Model.HasItems) { %>
Cart Items: <%=Model.Items.Count() %> | Total: <%=Model.TotalItems %>
<%} else {%>
Your cart is empty. Please buy stuff!
<%} %>
Helper method for Master Page:
public static void RenderCartStatus(this ViewMasterPage pg) {
pg.Html.RenderAction("CartStatus", "Catalog", null);
}
And finally master page:
<%this.RenderCartStatus(); %>
Thank you for the advice.

Related

In a big and complex ASP.NET MVC application is created a model of all other model classes?

I'll explain my point:
The best practice is to create views strongly typed with a Model. You only can stronly type one Model.
If you need two models in a view you can created two views and use Partial Render, but it seems not to be the very best option.
Another approach is to create another type model that encapsulates the other pieces of the model what you need; this make much more sense for me.
Then, my question is, in a complex proyect when a page needs to communicate with all the models and they are not direct realted, developers create a type that encapsulates all the other things?
For non-related parts of your view, you may use Html.Action() to invoke an action that returns a partial view.
This way, the logic of the "area" will be encapsulated in its own action and/or controller.
Update: I don't know if it's really the best practice, but I prefer composition over complex views & view models. Even for related information, I prefer to break it to smaller partial views and child actions. As I see it, it has the following flexibility:
Ability to easily move some of the partial views/child actions to a layout page
Load the partial view asynchronously via AJAX query
Reduced controller action complexity and increased maintainability.
Better support for conditioned rendering
Separation of concerns
In (4) I mean that you can easily do the following without complicating your view model:
<div class="header">
#if (loggedInUser.ShowAds) {
#Html.Action("Header", "Ads")
}
</div>
Answering the question in your comment.
Considering twitter. There's the content pane and the users box on the left.
So here's our TweetsController:
public class TweetsController: Controller {
public ActionResult Index() {
var tweets = ...;
return View(tweets);
}
}
The Tweets/Index view may look like:
#model Tweet[]
<div class="leftPane">
#Html.Action("Index", "Users");
</div>
<div class="mainContent">
#foreach var t in Model {
#t.User - #t.Text
}
</div>
Note that the left pane just calls the Index action in UsersController to display the users list.
Here's how it may look like:
public class UsersController: Controller {
public ActionResult Index() {
var users = ...;
return PartialView(users);
}
}
And here's the partial view (Users/Index):
#model User[]
#foreach var u in Model {
<img src="#u.IconUrl"/> #u.Name
}
So what will actually happen, when the Tweets view will be rendered Html.Action will put the partial view returned by UsersController.Index in the left pane.
Of course you may move this logic into a layout if this behavior is common for a number of pages.
Hope that helps.
Then, my question is, in a complex proyect when a page needs to
communicate with all the models and they are not direct realted,
developers create a type that encapsulates all the other things?
Yes some of them do, and the name for these is ViewModels

ASP.NET MVC partial view that updates without controller

I have a partial view that shows a list of Categories. I'd like to put that partial view on any page, but I'd like to have it to call to the service and get a list of categories by itself without me having to do that in every controller action. Something like webforms in which you can put a code-behind on it.
For eg.
Actions
public ActionResult Index()
{
JobListViewModel model = new JobListViewModel();
model.Categories= jobService.GetCategories();
return View(model);
}
public ActionResult Details(int id)
{
Job job = jobService.GetJob(id);
return View(job);
}
I created a partial that will take the model.Categories model and display a list. As you can see, the Index page will work fine, but I do not want to call it again in the Details page. Is there a way to make my partialview call to the GetCategories() service by itself?
Use Html.RenderAction - that gives the partial view its own controller action.
You should also mark you partial action with the attribute [ChildActionOnly].
DVark,
As noted in the accepted answer, for your scenario, RenderAction is the most appropriate.
I thought I'd link a little article that distils my thinking on the topic (i.e. when to use RenderPartial vs RenderAction):
http://cbertolasio.wordpress.com/2010/09/21/mvc-html-renderaction-vs-html-renderpartial/
hope it helps
[edit] - as an aside. a year or so ago, i got myself into a few scrapes by not appreciating the power of RenderAction, in favour of RenderPartial. as a result, i had littered the shared view space with lots of partialviews in order to access them from a variety of sources. the moral of the story: know your 'territory' before planting your flag.

Problem with strongly typed partial view

I've a problem with Partial View.
I am developing a blog in asp.net mvc and I would make in my masterpage a list of categories, last post, last comments.
I think that the best solution is to use strongly typed partial view, and in each partial view pass the necessary model.
MY problem is that the model in View.. in any view (connected to the masterpage's contentplaceholder) enter in conflict with the models in partial views and I get an error like this:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Blog.Models.Articoli]' but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Blog.Models.Categorie]'.
I found on web a dirty solution that consist of to pass togheter the model of any view, some viewdata, one for every model to pass in partial view. But this solution don't respect DRY Principle..because you must repeat this code for each action!
So, my question is: Can I create a model that contain also partial view's model?
If, yes, in that way?
It Exist another solution more simple?
Thanks for help
How about the View Model Pattern?
I've created wrapper classes that are passed to my views rather than whatever object I would normally use
public class MyCreateUserView
{
public User CreatingUser { get; set; }
public MyPartialViewObject Blah { get; set; }
}
In your view write:
public ActionResult CreateUser()
{
MyCreateUserView createUser = new MyCreateUserView()
{
CreatingUser = GetUserFromSomewhere(),
Blah = GetPartialViewObject();
}
return View(createUser);
}
Then your page header looks like so:
<%# Page Language="C#" Inherits="ViewPage<MyCreateUserView>" %>
and when you render your partial write:
<% Html.RenderPartial("../MyPartialViewObject ", Model.Blah); %>
Instead of solving that with the pattern you describe (which is generally a great pattern), I solve that with calls to RenderAction and have it return a partial view. That way the code is in one place as each call to each view does not have to worry about marshalling all the data you need. If you want to see a short discussion on how to use it, I would check Haack's blog here: http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx. You can also check out the discussion on this other post here on SO: ASP.NET MVC Master Page Data

How do I know if the OutputCache for a given control is being used?

I am attempting to write a common "menu.ascx" usercontrol in Asp.Net MVC that will generate a properly formatted HTML menu for my application. The menu is generated based on content in the database and a series of Resource resolutions... which are passed to the PartialView through an attribute on a ViewModel.
It would make sense to utilize an OutputCache directive on the menu.ascx control in order to limit the number of round-trips to the database and Resource files. My intention is to mark the OutputCache directive with VaryByParam=none and VaryByCustom attributes, implementing a custom security lookup in global.asax...
My question is: how do we know when the OutputCache for menu.ascx is going to be used, so that we can skip the data fetch operations when constructing the ViewModel in the controller?
Some sample UserControl code:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%# OutputCache VaryByParam="none" VaryByCustom="customstring" %>
<ul>
<% var model = (IMyViewModel)Model;
foreach (var menu in model.Menus) { %>
<li><%= menu.Text %></li>
<% } %>
</ul>
Here's interesting readin on that subject Donut Hole Caching in ASP.NET MVC and here ASP.NET MVC Result Cache , I would basically do this menu by RenderAction method in master page to invoke action which will pull data from database and theb , and then cache the action result
I think I have found a suitable workaround to my problem.
In the Menus property getter for my concrete ViewModel implementation, I am writing proxy code to reach back into the instantiating Controller and request the Menu data. In this way, I can create the Menu data on-the-fly, when the PartialView requests it. If the PartialView is coming out of the OutputCache, the Menu property will not be requested.
So, my IMyViewModel looks a little like this:
public interface IMyViewModel {
IEnumerable<Menu> Menus { get; }
///<summary>
/// A pointer back to the calling controller, which inherits from the abstract MyBaseController
///</summary>
MyBaseController Controller { get; set; }
}
and my concrete implementation of Menus looks a little like this:
public IEnumerable<Menu> Menus
{
get { return Controller.GetMenus(); }
}
Comments? Is this a viable solution?

Function in ASP.NET MVC

A function returns only one view.
what if I want to return multiple views in a function?
For example, I have this code:
Function Index() As ActionResult
Dim _news As DataTable = News.newsSelect()
Dim _announcement As DataTable = Announcement.SelectAnnouncement()
Return View()
End Function
I want to return _news and _announcement to be used in the aspx page. How would I do this?
Are you trying to show both sets at the same time? News and Announcements?
If so then why not implement either a PartialView or two PartialViews?
Then in your main view you can render them and pass the collection to the PartialViews?
There are heaps of samples on this and the one I recommend is in NerdDinner if you haven't already seen it.
I hope this helps. If you want sample code then let me know.
One simple way is just to have those two datasets sent in a ViewData element, which you can access in a field.
example:
ViewData["Elements"] = new SelectList(aElements, "Guid", "Name");
is consumed as:
<%= Html.DropDownList("Elements","Pick an element")%>
Also, I think that if you read between the lines of this blog post here you will find an elegant way of achieving what you want ;) but its a bit more involved..(only because you mentioned Views instead of just variables..
Quote:
We need to create our own implementation of IViewFactory. This
is responsible for locating and
creating an instance of an IView
(which both ViewPage and
ViewUserControl implement).
To “inject” (all you DI fans excuse me borrowing the term without
using a DI framework) our new View
Factory into every Controller we are
going to create our own
IControllerFactory implementation.
We need to configure the framework to use our new Controller
Factory.
Finally we can create two Views – an AJAX version and a pure
HTML version.
Building on that should be all you need
Good luck!
Ric
Assuming what you are trying to do is use both of those DataTables to populate some View, then my recommendation would be to create a wrapper object and then a strongly typed view based on this object.
The wrapper object would contain properties for all of the data elements that you need in order to render your view properly. In your case, it is 2 DataTable objects. I do not really know VB, so all my examples will be in C#. Here is an example of the data wrapper class...
public class IndexViewData
{
public DataTable News { get; set; }
public DataTable Announcement { get; set; }
}
You then might update the Index action in your controller as follows:
public ActionResult Index()
{
var viewData = new IndexViewData();
viewData.News = News.newsSelect();
viewData.Announcement = Announcement.SelectAnouncement();
return View(viewData);
}
Finally, you would need to create/update your view to be strongly typed. This is done by having your page inherit from the generic System.Web.Mvc.ViewPage<T> class. Just substitute the view data wrapper created earlier for T. To do this, you would set the inherits attribute of the <%# Page %> element. In your case, if we assume your root namespace is called "Foo", you might have the following page declaration in your Index.aspx view (added extra line breaks for readability):
<%# Page Title=""
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Foo.Models.MyModelType.IndexViewData>"
%>
Once you have a strongly typed view for your view data wrapper, you can access the wrapper object in your view using the Model property. Here is an example of something you could do in your Index.aspx view
<%-- Output some random data (bad example, for demonstration only) --%>
<%= Model.News[0]["title"] %><br/>
<%= Model.Anouncement[0]["body"] %>
In reality you're probably going to do something like iterate over each row of the data table. Regardless, once you create the strongly typed view, your model object, which was passed to the view in the Index method of the controller, is available in the Model property within the view.
You can find detailed tutorials at the ASP.NET MVC site

Resources