
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.
- Create a new MVC application.
- Then we will create a quick model to play with. We need two distinctly different models to be able to demonstrate why we would need to render one action from within another. We will create a
Post
class to represent some blog posts. And we will create aProduct
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; } }
- Then we need to create a couple of service classes from which we will generate a list of our new object models. We will create a
ProductService
class and aBlogService
class. Each of these classes will haveGet
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; } }
- Now that we have the ability to generate a list of working data, we can next turn our attention to creating two controllers to handle views for each of our two object types. We will create a
BlogController
and aProductController
. TheBlogController
will expose anIndex
action to show a list of recent blog posts. TheProductController
will have aSuggestedProducts
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)); } }
- The next thing for us to do is to generate a view for each of our controllers. We will start with the
ProductController
, as its view is the easiest. For this controller, we will generate a strongly typed partial view based on aProduct
using the details view. Once the view is generated, we have to change the model from a single instance ofProduct
to a List ofProduct
. 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>
- Now we need to generate our primary view, which will be responsible for showing a list of blog posts. In addition to displaying blog posts, we will also render the list of suggested products. Similar to our previous view, we will start by generating the view from the
Index
action of theBlogController
. This will be a strongly typed details view based on thePost
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:
<p>My Blog</p> <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>
- Now that we have our primary view created, we can turn our attention to rendering a list of suggested products. All that we need to do is add a call to
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.Views/Blog/Index.aspx:
... <td style="width:200px;vertical-align:top;"> <% Html.RenderAction("SuggestedProducts", "Product"); %> </td> ...
- Now you can run the application. Navigate to
/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 theSuggestedProducts
view. This is the power of theRenderAction
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!