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.
Cart, Address, Product, Account, OrderHeader
, and LineItem
, 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; } } }
CartFactory
class, which we will use to generate the data and create a fully populated instance of our Cart
object. We will create a GetCart
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 = "[email protected]", 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; }
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 our Cart
classes. To do this, open up the HomeController
and add an Address() ActionResult
. In this method, we will return a new instance of Address
.Controllers/HomeController.cs:
public ActionResult Address() { return View(new Address()); }
Address
details view that is strongly typed, based on the Address
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>
Cart
items. We will add an action called Items
in the HomeController
that returns a List<LineItem>
.Controllers/HomeController.cs:
public ActionResult Items() { return View(new List<LineItem>()); }
Items
that will be based on an enumerable list of LineItem
. 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>
Cart
class, we are now ready to display our shopping cart and all of its complexity. We will start by adding another ActionResult
called DisplayCart
. This result will return a new instance of a Cart
, 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); }
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 the Address
partial view and pass in the billing
address. Next we will load the Address
partial view and pass in the shipping address. The last view we will load is the Items
partial view, which we will pass in the collection of LineItems
. At the end of this view, we will display the total cost of the shopping cart.Views/Home/DisplayCart.aspx:
<h2>Display Cart</h2> <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>
/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).