Active Sitemaps - An MVC Manifesto, Part 3 - Site Content Structure

08.11.2018

This is the third in a series of articles describing a powerful design pattern that provides several application services in a convenient form while also encapsulating a high-level description of the application's functionality. The previous installment saw the creation of classes for each route, which serve as attributes to decorate the corresponding action methods as well as utility objects that generate route links and redirect actions. It also saw the creation of the Active Sitemap object, which has static properties for each of the route classes as well as a utility method to turn relative links into absolute links. In case you missed it, you can read it here: Active Sitemaps Part 2.

A major part of the vision for this pattern was to encapsulate and describe the overall structure of the application functionality, as a hierarchy. At the moment, we have a collection of classes that describe the application functionality, but they are not organized in any way other than a general collection. What we have built so far is useful, so we will keep it all and just add to it.

We need to organize the route objects into a hierarchy which will describe our application as a node tree. That means that each route object will need to be able to maintain a collection of its child nodes. In addition, for some of the functionality we will add later, it will be handy for each route object to maintain a reference to its parent. We will start with changes to the interface that describes our route objects:

public interface ILogicalRouteTemplateProvider
: IRouteTemplateProvider {

ILogicalRouteTemplateProvider
AddChild(ILogicalRouteTemplateProvider child);

ILogicalRouteTemplateProvider
SetParent(ILogicalRouteTemplateProvider parent);

IEnumerable<ILogicalRouteTemplateProvider>
Children { get; }
}

The (previously empty) interface now has a read-only property for the collection of children, as well as methods to add children and set the parent reference. Both methods will return the object upon which they are called, to provide a Fluent-style method chaining capability which will come in handy when building the hierarchy. No property has been added for the parent reference, as that will be only required internally by the route object.

Next we turn our attention to the abstract class from which all route objects inherit, implementing both methods and the property:

public abstract class RouteTemplateBaseAttribute
: Attribute, ILogicalRouteTemplateProvider {

//existing code omitted here...

private readonly IList<ILogicalRouteTemplateProvider>
children = new List<ILogicalRouteTemplateProvider>();

public IEnumerable<ILogicalRouteTemplateProvider>
Children => children.ToImmutableArray();

public ILogicalRouteTemplateProvider
AddChild(ILogicalRouteTemplateProvider child) {

children.Add(child);
child.SetParent(this);
return this;
}

private ILogicalRouteTemplateProvider parent;
public ILogicalRouteTemplateProvider
SetParent(ILogicalRouteTemplateProvider newParent) {

parent = newParent;
return this;
}
}

Note that the parent reference is kept private, and the collection of children is kept private and a read-only, immutable copy of the collection is exposed.

The final changes will need to be made to the Active Sitemap object, where we will build the hierarchy of route objects. All of the route object instances are already created, they just need to be assembled. The rest of the code changes below are all made to the AppMap class.

private static IList<ILogicalRouteTemplateProvider> _routes;

A private variable needs to be created to hold the hierarchical structure of the application functionality. A collection is used just in case your application logically has multiple top-level nodes. Next we need to start building the hierarchy pieces:

private static ILogicalRouteTemplateProvider
BuildProductHierarchy() {

var productRoot = ProductListRoute;

productRoot.AddChild(ProductsInCategoryRoute);
productRoot.AddChild(ProductDetailsRoute);

return productRoot;
}

It is convenient to build a node tree for each functional subset of the site in its own function, for easier maintenance. Then assemble all of it into one hierarchy:

private static void ConfigureHierarchy() {

var siteRoot = (SiteHomeRoute)
.AddChild(BuildProductHierarchy())
.AddChild(AboutUsRoute)
.AddChild(ContactUsRoute);

_routes = new List<ILogicalRouteTemplateProvider> {
siteRoot
};
}

The node hierarchies for each functional area of the site are assembled along with any standalone route objects in a configuration function. This is where the Fluent chained-method style is handy! This also provides one single place to make modifications when the logical structure of the application needs to change. Finally, this method needs to be invoked. It needs to run just once, when the application is starting up, so the existing Configure() method is a good place to do so.

public static void Configure(IHttpContextAccessor httpContextAccessor) {
_httpContextAccessor = httpContextAccessor;
ConfigureHierarchy();
}

Going forward, as you develop new functionality for the application it needs to be incorporated into this scheme - but that is a straightforward process:

  1. Create a class for a route object representing the new functionality
  2. Add a property for that route object to the AppMap
  3. Add that property to the hierarchy of route objects
  4. Apply the route object as an attribute to the controller method for the new functionality

So now we have an abstract, hierarchical description of the application functionality - but it is not doing anything for us. Stay tuned! In the next couple of installments in this series we will put this Active Sitemap to good use in some powerful ways. You can view/clone the finished code for this step of the series at this GitHub repo.

In the meantime, may all your code be groovy!

 

Author Avatar
Share this: