Prior to the preview 3 release of ASP.NET MVC, whenever you wanted to pass data to your view user controls, you only had the option of passing a specific object, or using it's parent's view data. This was all great, and it worked wonderfully, but the problem existed that your view user control would always be dependant upon some parent view data. ASP.NET MVC preview 3 introduced the concept of SubDataItems off of ViewDataDictionary in which you could specify a keyed ViewDataDictionary to use. This helps in the true separation of your view user controls, or child views, and will hopefully later lead into more interesting solutions such as sub-controllers. So lets get into an example.
Hold It! Yeah, we have to patch the MVC framework first before doing anything further. I've logged a bug with the ASP.NET MVC team regarding this, and it's unfortunate to say that this shouldn't have slipped through - but that's what you get without test cases. But I digress. Okay, open the MVC Preview 3 source and open the file "System.Web.Mvc/Mvc/Extensions/UserControlExtensions.cs". Go to the "DoRendering" method, and replace the if block starting on line 127 with the following:
1: if (controlData != null) {
2: instance.ViewData.Model = controlData;
3: }
4: else if (!string.IsNullOrEmpty(instance.SubDataKey)) {
5: instance.ViewData = context.ViewData.SubDataItems[instance.SubDataKey];
6: }
7: else {
8: instance.ViewData = context.ViewData;
9: }
Okay, onto the example. Say you have a product detail on your website, and you need to display the product information on the listing page as well as on the order summary page. Because we want to display the same information in the same way on each page, it leads us into using a ViewUserControl. There's other information listed on each page itself, so there is definitely no need to create or duplicate our view data model object just for display purposes. First, we should define our model.
1: public class Product
2: {
3: public Product(int id, string name, decimal price)
4: {
5: Id = id;
6: Name = name;
7: Price = price;
8: }
9:
10: public int Id { get; set; }
11: public string Name { get; set; }
12: public decimal Price { get; set; }
13: }
Next, let's work on our controllers. To show the true separation, I'll have both a ProductController and OrderController.
1: public class ProductController : Controller
2: {
3: public ActionResult Index()
4: {
5: ViewData["Category"] = "Bicycles";
6: ViewData["SubCategory"] = "Mountain Bikes";
7:
8: Product product = new Product(1, "16 Speed", 499.99m);
9:
10: ViewDataDictionary<Product> subViewData =
11: new ViewDataDictionary<Product>(product);
12: subViewData["ShippingCost"] = 24.99m;
13: ViewData.SubDataItems.Add("ProductData", subViewData);
14:
15: return View();
16: }
17: }
18:
19: public class OrderController : Controller
20: {
21: public ActionResult Index()
22: {
23: Product product = new Product(1, "16 Speed", 499.99m);
24: decimal shipping = 24.99m;
25: decimal tax = (product.Price * .08m);
26: decimal total = product.Price + shipping + tax;
27:
28: ViewData["OrderAmount"] = product.Price;
29: ViewData["Tax"] = tax;
30: ViewData["Total"] = total;
31:
32: ViewDataDictionary<Product> subViewData =
33: new ViewDataDictionary<Product>(product);
34: subViewData["ShippingCost"] = shipping;
35: ViewData.SubDataItems.Add("ProductData", subViewData);
36:
37: return View();
38: }
39: }
As you can see, within both of the controller actions, I'm creating a new instance of a ViewDataDictionary<Product> and adding it to the ViewData.SubDataItems. This will allow me to later extract that specific ViewDataDictionary when rendering the ViewUserControl. Granted, this scenario is very rudimentary - but I wanted to show that each action / view has it's own data, but wanted to show the "sharing" of the SubDataItem data. Now, we implement our ViewUserControl (I won't show the HTML for this, it's in the download though). The ViewUserControl simply needs the following
1: public partial class ProductInfo
2: : System.Web.Mvc.ViewUserControl<Models.Product> { }
Now, in both our Index views for product & order we can call the helper method to render our view user control. Please note that within this, we're not specifying any control data (model) but we are specifying the SubDataKey. As you've probably figured out, that SubDataKey is the key the framework uses to extract the correct ViewDataDictionary to set on the ViewUserControl when rendering. Should you not specify a SubDataKey, it'll use the parent ViewDataDictionary - so in this case, our ViewPage's ViewDataDictionary.
1: <%= this.RenderUserControl("~/views/shared/ProductInfo.ascx",
2: null,
3: new { SubDataKey = "ProductData" }) %>
And that's it. Of course, SubDataItems can be used for other purposes like keeping your ViewData "componentized", but the best use for this so far is so that your ViewUserControl's can live without the knowledge or sharing of parent ViewPage's or ViewUserControl's. If you'd like the source for this demo, you can download it here. It already has the patched MVC assembly. Enjoy!
56119f64-3866-42b1-9347-4d53548e5260|1|5.0