
Oftentimes, you will encounter a scenario where your view starts to get a bit overwhelming. This might happen in the case of a shopping cart where you are displaying the items in a customer's cart, along with suggested products, products from their wish list, and various other items pertaining to a customer's order. In order to simplify a complex view of this nature, you might choose to put individual concerns of the larger view into separate partial views to keep the code of your view segments nice and small. You can then reassemble the partial views into one complex view.
- Start by creating a new MVC application.
- Then we will create a quick (simple) object model that will contain a
Cart, Address, Product, Account, OrderHeader
, andLineItem
, which we will use to populate a cart display page.Models/Cart.cs:
public class Cart { public OrderHeader Header { get; set; } public List<LineItem> Items { get; set; } public double Total { get { return Items.Sum(i => i.SubTotal); } } }
Models/Address.cs:
public class Address { public string Street1 { get; set; } public string Street2 { get; set; } public string City { get; set; } public int Zip { get; set; } public string State { get; set; } }
Models/Product.cs:
public class Product { public double Price { get; set; } public string Name { get; set; } public double Tax { get; set; } }
Models/Account.cs:
public class Account { public string Username { get; set; } public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
Models/OrderHeader.cs:
public class OrderHeader { public Account Account { get; set; } public Address Billing { get; set; } public Address Shipping { get; set; } }
Models/LineItem.cs:
public class LineItem { public Product Product { get; set; } public int Quantity { get; set; } public double SubTotal { get { return ((Product.Price * Product.Tax) + Product.Price) * Quantity; } } }
- With this model created, we are now ready to create some fake data. We will do this by creating a
CartFactory
class, which we will use to generate the data and create a fully populated instance of ourCart
object. We will create aGetCart
method where we will create an instance of all the classes that we need to display in our cart.Models/CartFactory.cs:
public Cart GetCart() { Cart c = new Cart(); c.Header = new OrderHeader(); c.Header.Account = new Account() { Email = "asiemer@hotmail.com", FirstName = "Andrew", LastName = "Siemer", Username = "asiemer" }; c.Header.Billing = new Address() { City = "Lancaster", State = "CA", Street1 = "Some Street", Street2 = "Apt 2", Zip = 93536 }; c.Header.Shipping = new Address() { City = "Fresno", State = "CA", Street1 = "This street", Street2 = "Front step", Zip = 93536 }; List<LineItem> items = new List<LineItem>(); for (int i = 0; i < 10; i++) { Product p = new Product(); p.Name = "Product " + i; p.Price = 2*i; p.Tax = .0875; LineItem li = new LineItem(); li.Product = p; li.Quantity = i; items.Add(li); } c.Items = items; return c; }
- Now that we have our model and a factory class to generate the data that we need, we are ready to start creating the partial views we need. While we could manually create views for each of our objects, there is a much quicker way—we can create actions in our
HomeController
that returns an instance of an object from which we can generate a View. By doing this, we can quickly generate the markup that is required to display each of ourCart
classes. To do this, open up theHomeController
and add anAddress() ActionResult
. In this method, we will return a new instance ofAddress
.Controllers/HomeController.cs:
public ActionResult Address() { return View(new Address()); }
- Then we can generate a new partial
Address
details view that is strongly typed, based on theAddress
model.Views/Home/Address.ascx:
<fieldset> <legend><%: ViewData["AddressType"] %></legend> <div class="display-label">Street1</div> <div class="display-field"><%: Model.Street1 %></div> <div class="display-label">Street2</div> <div class="display-field"><%: Model.Street2 %></div> <div class="display-label">City</div> <div class="display-field"><%: Model.City %></div> <div class="display-label">State</div> <div class="display-field"><%: Model.State %></div> <div class="display-label">Zip</div> <div class="display-field"><%: Model.Zip %></div> </fieldset>
- Now we can do the same thing for a list of
Cart
items. We will add an action calledItems
in theHomeController
that returns aList<LineItem>
.Controllers/HomeController.cs:
public ActionResult Items() { return View(new List<LineItem>()); }
- Then we can generate another strongly typed partial view called
Items
that will be based on an enumerable list ofLineItem
. We will also add a couple of columns to the generated view to display the name and price.Views/Home/Items.aspx:
<table style="width:600px;"> <tr> <th></th> <th> Name </th> <th> Price </th> <th> Quantity </th> <th> Sub Total </th> </tr> <% foreach (var item in Model) { %> <tr> <td> Delete </td> <td> <%: item.Product.Name %> </td> <td> <%: String.Format("{0:C}", item.Product.Price) %> </td> <td> <%: item.Quantity %> </td> <td> <%: String.Format("{0:C}", item.SubTotal) %> </td> </tr> <% } %> </table>
- With our partial views created and the ability to get a populated
Cart
class, we are now ready to display our shopping cart and all of its complexity. We will start by adding anotherActionResult
calledDisplayCart
. This result will return a new instance of aCart
, which we will get from our CartFactory class that we created earlier.Controllers/HomeController.cs:
public ActionResult DisplayCart() { Cart c = new CartFactory().GetCart(); return View(c); }
- Then we can generate a strongly typed empty view called
DisplayCart
. Inside of this view we will display the user's first and last name, as well as their e-mail address. We will then load theAddress
partial view and pass in thebilling
address. Next we will load theAddress
partial view and pass in the shipping address. The last view we will load is theItems
partial view, which we will pass in the collection ofLineItems
. At the end of this view, we will display the total cost of the shopping cart.Views/Home/DisplayCart.aspx:
<p>Display Cart</p> <div> <%: Model.Header.Account.FirstName %> <%: Model.Header.Account.LastName %><br /> <%: Model.Header.Account.Email %> </div><br /> <table style="width:600px"> <tr> <td> <% Html.RenderPartial("Address", Model.Header.Billing, new ViewDataDictionary() {new KeyValuePair<string, object>("AddressType", "Billing")}); %> </td> <td> <% Html.RenderPartial("Address", Model.Header.Shipping, new ViewDataDictionary() {new KeyValuePair<string, object>("AddressType", "Shipping")}); %> </td> </tr> </table> <% Html.RenderPartial("Items", Model.Items); %> <br /> <div> <b>Total:</b> <%: String.Format("{0:C}", Model.Total ) %> <div>
- You are now ready to run the application. You can then navigate to the
/Home/DisplayCart
view, where you should see something of this nature:
While this recipe appears to be complex at first, it really isn't. I had to create some complexity to be able to appropriately demonstrate how and why we would use the RenderPartial
method. As you can see, moving the complexity of our display logic into partial views not only allows us to reduce the amount of code we maintain in one file, but it also provides us with the opportunity to reuse our code (as seen by using the Address
partial view for two different address instances).