Question

I have an Mvc4 SPA Project I am working on where I need the ability to have multiple views nested within each other. The views and view models are linked up using Durandal.

In Mvc3 I had previously done this by using a partial view within partial view within a view, and passing parameters down to the partials. Doing this I was able to have multiple partials on a one-to-many relationship that in turn had multiple partials displaying one-to-many relationships all linked back to the parent view.

Example previously used in Mvc3 =

Public class ParentsController
{
public ActionResult Parent(int id)
{
    Parent parent = Db.Parents.Find(id);
    ViewBag.ParentId = parent.Id;
    return View(parent)
}

public PartialViewResult Child(int id)
{
    Child childs = Db.Childs.Where(w => w.ParentId = id);
    return PartialView("ChildPartial", childs.ToList());
}

public PartialViewResult GrandChild(int id)
{
    GrandChild grandChilds = Db.GrandChilds.Where(w => w.ChildId = id);
    return PartialView("GrandChildPartial", grandChilds.ToList());
}
}

And views something like

Parent.cshtml
@{
    ViewBag.Title = "Parent";
}
@Html.DisplayFor(Model.ParentName)
@{Html.RenderAction("ChildPartial", new { id = ViewBag.ParentId});}


ChildPartial.cshtml
@{
    ViewBag.Title = "Children";
}
{ foreach (var child in childs)
{
@{Html.DisplayFor(child.ChildsName)}
@{Html.RenderAction("GrandChildPartial", new { id = ViewBag.ChildId});}
}}


GrandChildPartial.cshtml
@{
    ViewBag.Title = "Grand Children";
}
{ foreach (var grandChild in GrandChilds)
{
@{Html.DisplayFor(grandChild.GrandChildsName)}
}}

Again, the above is a brief example of a pattern I have used in the past, don't need help with that.

I can use Breeze to load Children and display children relatively easy from the parent view model, but when I get into the grand children it feels like I am getting into 'spaghetti' code and loading up too much from one view model. I was thinking the correct thing to do would be create a view model for the parent, child's, and grandchild's to separate them out and make them reusable.

My question is should the parent view model load up the child's view model, and then have the child's view model load the grand child's, or should there be one view which composes and cascades down from a separate parent to child to grandchild? Ie should one view load up 3 view models separately or should there be a dedicated parent view model which calls all three view models and loads them more or less independent of each other? I am looking for a best practice here as the SPA idea is relatively new to me.

Was it helpful?

Solution

It's a little early to proclaim a best practice here ... particularly when the actual workflow and performance characteristics of your application are unknown (perhaps unknowable at this point).

In general, we think it is better if each VM loads what it needs rather than relying on a master VM to provide for subordinate VMs. The instinct here is that you broke it up into separate VMs so they could execute their own responsibilities ... so let them do it. The master becomes the coordinator of its child VMs and stays out of their implementation details as much as possible.

We intend to release a new sample called "TempHire", rooted in "Hot Towel(ette)", in the next month or so that may offer some guidance. The timing is probably not right for you. But you can find the code on GitHub) and I will summarize (and oversimplify) the features of that sample that seem pertinent to your question:

  1. The master VM is associated with the root entity (in your case, a Parent).

  2. The master VM receives a "datacontext" (a Unit-of-Work [UoW]) that is dedicated to that root entity's workflow and labeled with the root entity's ID.

  3. A "Unit of Work Manager" creates and tracks UoWs by root entity ID. If you ask for the UoW for Parent-1, it will either give you the one it's tracking or create a new one for you.

  4. This makes it easy for child VMs to share data context with the master and with each other in a fairly decoupled way. The master doesn't have to pass the UoW along through a chain of nested VMs. Instead, each VM is injected with the "Unit of Work Manager" and can ask the "Unit of Work Manager" for a UoW by Parent ID; it will get the UoW that "everyone else is using".

  5. Now any VM can load any data it needs (or benefit from data in the UoW's cache) by asking the UoW for that data. The VM doesn't have to "think" about whether it is the first or the last to ask for data, whether the data are in cache or not, whether the data were retrieved all at the beginning or as needed. The UoW can encapsulate those details, exposing simple "query" methods for the VMs to consume.

  6. Of course the UoW should offer "refresh" methods as appropriate to cope with staleness ... which has a different definition for every type in every application.

  7. We tend to "sandbox" the UoW for each task which is typically oriented around a root entity. Therefore, the master VM for Parent-1 has its own UoW with its own EntityManager while the master VM for Parent-2 has its own UoW with its own EntityManager. In this way, the user could work on Parent-1 and Parent-2 simultaneously (e.g., create order #123 while updating order #456) and independently without commingling changes.

  8. The "Unit of Work Manager" creates UoWs with embedded EntityManagers (EMs). It creates new EMs with a helper service called the "EntityManagerProvider". The "EntityManagerProvider" is responsible for minting new EMs and populating them with "global data" such as reference lists (e.g., States, StatusCodes, Colors, etc).

  9. The "EntityManagerProvider" (EMP) keeps internally a read-only "master EM" with the source MetadataStore and the canonical versions of those static reference lists. The new EMs it creates are typically copies of this hidden master EM. Thus the overall system makes exactly one request for metadata and one request for those static reference lists. The EMP takes care of distributing that source material to the new EMs that it creates.

How much of this do you need? I don't know.

OTHER TIPS

My buddy, Steve Schmidt, offers an alternative avenue of attack which must be considered: One ViewModel / Multiple Views.

In this approach, you compose distinct Views, each dedicated to a particular perspective on the root entity and its graph of child objects. These are much the same (if not identical) to the Views of your original proposal.

What's different? Only one ViewModel! It's easy with Durandal's "ko compose" custom Knockout binding to attach multiple views to the same ViewModel. You get nice separation at the layout level without having to stand-up parallel VMs.

This approach works well when the non-visual presentation logic that delivers the model is simple but you want to look at that model from different perspectives (AKA, "pivoting around the data").

Master/Detail can be a simple example. Imagine a reference list editor with the list items in a "grid" at the top and a simple form on the right showing the details of the selected item. There isn't much logic here. Why bother with two VMs and having to coordinate the item list in the "List VM" with the selected item in the "Editor VM"? Much simpler as a single VM.

This works marvelously well in simple cases and there are a lot of simple cases in the typical application.

Think of this as a complement to the heavy-duty, multi-VM technique described in my other answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top