In the previous recipe, we took a look at how you can split up the view code into multiple partial views to make managing presentation code easier and more reusable. In most cases, this is exactly what you will need, as a view usually has a view model that is specially built just for it. In our previous recipe, we took pieces of the overall view's model and pushed bits of it off to the partial views.
In other cases though you may have totally separate models that are displayed by separate views. Sometimes they may even be handled by different actions in different controllers. In this case, you might try to render an action directly and put the result of that view into another view. In this way, we won't have to worry about cross-pollinating models and views from various controllers.
Post
class to represent some blog posts. And we will create a Product
class to represent a list of suggested products, which we will display next to our blog posts.Models/Post.cs:
public class Post { public DateTime Created { get; set; } public string Title { get; set; } public string Body { get; set; } }
Models/Product.cs:
public class Product { public string Name { get; set; } public string Description { get; set; } public double Cost { get; set; } }
ProductService
class and a BlogService
class. Each of these classes will have Get
methods on them to get a list of the specific objects we need.Models/BlogService.cs:
public class BlogService { public List<Post> GetPosts(int count) { List<Post> result = new List<Post>(); for (int i = 0; i < count; i++) { Post p = new Post(); p.Created = DateTime.Now; p.Title = "A really great post"; p.Body = @"Lorem ipsum dolor sit amet, ..."; result.Add(p); } return result; } }
Models/ProductService.cs:
public class ProductService { public List<Product> GetProducts(int count) { List<Product> result = new List<Product>(); Random r = new Random(); for (int i = 0; i < count; i++) { Product p = new Product(); p.Cost = r.Next(5, 50); p.Name = "Really great product"; p.Description = @"Lorem ipsum ..."; result.Add(p); } return result; } }
BlogController
and a ProductController
. The BlogController
will expose an Index
action to show a list of recent blog posts. The ProductController
will have a SuggestedProducts
action that will return a list of products.Models/BlogController.cs:
public class BlogController : Controller { public ActionResult Index() { return View(new BlogService().GetPosts(5)); } }
Models/ProductController.cs:
public class ProductController : Controller { public ActionResult SuggestedProducts() { return View(new ProductService().GetProducts(7)); } }
ProductController
, as its view is the easiest. For this controller, we will generate a strongly typed partial view based on a Product
using the details view. Once the view is generated, we have to change the model from a single instance of Product
to a List of Product
. Then we need to wrap the details view that was generated with a loop.Views/Product/SuggestedProducts.aspx:
<%@ Page Title="" Language="C#" Inherits= "System.Web.Mvc.ViewPage<List<MvcApplication1.Models.Product>>" %> <%@ Import Namespace="MvcApplication1.Models" %> <fieldset> <legend>Suggested Products</legend> <% foreach (Product p in Model) { %> <div class="display-field"><b><%: p.Name%></b></div> <div class="display-field"> <i><%: String.Format("{0:C}", p.Cost)%></i> </div> <div class="display-field"><%: p.Description%></div> <% } %> </fieldset>
Index
action of the BlogController
. This will be a strongly typed details view based on the Post
class. Once it is generated, we will need to wrap the generated view code with a table so that the list of blog posts can sit next to a list of suggested products. I also added a bit of styling to get things to line up a bit better.Views/Blog/Index.aspx:
<h2>My Blog</h2> <table style="width:800px;"> <tr> <td style="width:200px;vertical-align:top;"> <!-- Suggested products... --> </td> <td style="vertical-align:top;"> <fieldset> <legend>Recent Posts</legend> <% foreach (Post p in Model) { %> <div class="display-field"><b><%: p.Title%></b></div> <div class="display-field"><i> <%: String.Format("{0:g}", p.Created)%></i></div> <div class="display-field"><%= p.Body%></div> <% } %> </fieldset> </td> </tr> </table>
Html.RenderAction
where we currently have this comment <!-- Suggested products...-->
. In order to make this call, we only need to specify the name of the action and the controller that we want to render.If, while rendering this recipe, you see two master pages, then the odds are that you didn't generate a partial view for your SuggestedProducts
view!
Views/Blog/Index.aspx:
... <td style="width:200px;vertical-align:top;"> <% Html.RenderAction("SuggestedProducts", "Product"); %> </td> ...
/Blog/Index
and you should see a list of blog posts. Next to that you should also see a list of suggested products. Notice that our blog controller didn't have to know anything about products in order for it to use the SuggestedProducts
view. This is the power of the RenderAction
helper method.In this recipe, we saw how we can separate the logic that views require into multiple controllers while still being able to use those views together. This functionality is built into the MVC framework and exposed through the HtmlHelper
class.
Be aware that using this method has one gotcha. If you want to use output caching on the rendered view—don't. It won't work (at the time of this writing). You can put an OutputCache
attribute on the SuggestedProducts
view, but when you render the SuggestedProducts
partial view from within the product's Index, the OutputCache
attribute is simply ignored from the parent view. Check out the chapter on caching and you will find a workaround to this issue!