Site map is an essential feature of any internet application. Microsoft has provided this control for ASP pages but not for MVC. If we search for MVC Sitemap provider, there is one code available on Github published by “Maarten Balliauw”. It contains almost everything we need to create Sitemap in our web site. But there is one problem. It is written to read structure only from *.sitemap file. What if our sitemap data is in database? This requirement generally occurs especially when you want to combine authentication with the sitemap. In short as many times our authentication model resides in databases we may need to show our sitemap nodes as per this authenticity defined in database. We needed a solution to combine these two – authentication and sitemap. So we extended the MVC sitemap provider to include this

Advantages:

  1. Sitemap can be defined in the database same place where the site membership is defined and necessary relationship can be setup easily
  2. Authentication and sitemap work from single model. We do not need to maintain separate models for them
  3. It avoids any issues occur because of manual errors like typos. E.g. we write role names for sitemap nodes inside the *.sitemap file then there are chances that there will be mismatch in name because of spelling. Using database using foreign keys we can avoid such issues
  4. We can easily combine other useful information which may reside in database tables which might be needed to pass in the query string
  5. It is easier to update the sitemap in database in different scenarios. E.g. instead of manually modifying sitemap file we can create page from where Admin can change roles for different nodes

Limitations:

URL for nodes should be unique. There are situations when we require multiple nodes to redirect to same page. If two nodes with same URL are found then error will be thrown. In my next blog I will show a solution for this limitation.

Solution:

We will extend the existing code “DefaultSitemapProvider” provided by “Maarten Balliauw” to make it work with database without affecting the functionality in “DefaultSitemapProvider”. We will combine the membership module with sitemap module to use the same framework for both of them. Apart from this we will also check how we can overcome the limitation of the unique URLs

Implementation:

Step 1. Create tables in the database which is used for membership

Following are the scripts can be used create tables

a) Create_SiteAuthorization_Table

b) Create_SiteAuthorization_Table

After creating these tables structure will look like below. “Role” is the table which you are using for role provider in the membership

ADO.NET Entity Data Model

Step 2. Here is the code of our extended sitemap provider – XPDSitemapProvider

Following are the basic methods and variables to generate site map created under MVC SQL Sitemap Provider

Variables

i. _nodes – It is used to keep track of the site nodes. It helps to find parent for any given node

//Helps to keep track of the site nodes. It helps to find parent for any given node

private Dictionary<int, SiteMapNode> _nodes = new Dictionary<int, SiteMapNode>(16)

ii. _root – This is the root element which is actually returned on calling of site map. All other nodes are present as its children

//Actual object which gets returned on calling of site map. All other nodes are present as its children

private SiteMapNode _root

Methods

i. GetRootNodeCore – This is the main method which is called to provide site map. It returns the Root node. It creates the site map calling BuildSiteMap

/// <summary>

/// This is the main method which is called to provide site map. It returns the Root node.

/// It creates the site map calling BuildSiteMap

/// </summary>

/// <returns>Root node of the sitemap object</returns>

protected override SiteMapNode GetRootNodeCore()

ii. BuildSiteMap – This is the method where logic to get node and weave them in a parent – child relationship is written. It calls GetSiteMapNode to create Node object

/// <summary>

/// This is the method where logic to get node and weave them in a parent – child relationship is written.

/// It calls GetSiteMapNode to create Node object

/// </summary>

/// <returns>Rootnode of the sitemap object</returns>

public override SiteMapNode BuildSiteMap()

iii. GetSiteMapNode – This method creates and returns the Node as the different properties provided. It uses ‘GetSiteMapNodeFromXmlElement’ defined in ‘DefaultSiteMapProvider’ to create node from XML element, which is passed as an input in it

/// <summary>

/// Creates the Node as per different properties provided in the parameters.

/// It created the XML object and prepare the structure for the node

/// </summary>

/// <param name=”sitemapID”>Id of the node</param>

/// <param name=”url”>URL of the node if exists</param>

/// <param name=”title”>Title which actually appears in the page</param>

/// <param name=”area”>Area under which the page exists</param>

/// <param name=”controller”>Controller name as string</param>

/// <param name=”action”>Action name as string</param>

/// <param name=”visible”>Boolean to check if node should appear in the sitemap</param>

/// <param name=”clickable”>Boolean to check if the node will clickable. Otherwise URL will be blank</param>

/// <param name=”roles”>List of role names who have access over the node</param>

/// <param name=”parameter”>Any query string which needs to append in the URL</param>

/// <param name=”parentNode”>SitemapNode object of parent node to which new node will be linked</param>

/// <returns>Returns newly created node</returns>

private SiteMapNode GetSiteMapNode(int sitemapID, string url, string title, string area, string controller, string action, bool visible, string clickable, List<string> roles, string parameter, SiteMapNode parentNode)

iv. GetParentNodeFromNode – This method is used to find the parent of any given node

/// <summary>

/// This method is used to find the parent of any given node

/// </summary>

/// <param name=”item”>Node of which parent needs to be found</param>

/// <returns>Parent node of the sent item</returns>

private SiteMapNode GetParentNodeFromNode(SiteMapModel item)

v. IsAccessibleToUser – This method checks for the authorization of the logged in user on the respective node and returns TRUE/ FALSE

/// <summary>

/// Checks for the authorization of the logged in user

/// </summary>

/// <param name=”context”>Current context under which application is running</param>

/// <param name=”node”>Object for which accessibility needs to be checked</param>

/// <returns>Boolean if node is accesseible to current logged in user or not</returns>

public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)

vi. AddNode – Adds new node in the sitemap object

/// <summary>

/// Adds new node in the sitemap object

/// </summary>

/// <param name=”node”>Object of new which will be added</param>

/// <param name=”parentNode”>Node with which it will associated a child</param>

protected override void AddNode(SiteMapNode node, SiteMapNode parentNode)

Step 3. Adding security facade

Apart from this there is a method ‘GetSiteMapWithRoles’ written under ‘SecurityFacade’. Purpose of this class is to read Nodes and the respective information from the database and join it with the authorised roles. Final composed data is used as an input in the ‘BuildSiteMap’ (Discussed above). It helps to find the access of the user on any particular node in ‘IsAccessibleToUser’ method, as roles are attached as attributes for each node. Following are the steps to read information from database

i. Create Entity – As we have multiple tables to manage data, and there are foreign key relationships between these tables, it becomes easy to query the data if we use “ADO.NET Entity Data Model”. Just add this model with required tables and it will look like similar to shown in step(1)

It automatically generates code to read and write in these tables though this entity. It also defines the relationship, if there are, between them.

ii. Create repository – Then insert the code to fetch data using this entity and create a ‘SecurityFacade’ object which will be used by “BuildSitemap”. Here is the code – SecurityFacade

We have used “ADO.NET Entity Data Model” named XPD_pilotEntities

/// <summary>

/// Creates a list of nodes present in Sitemap table in the DB combined with associated roles

/// </summary>

/// <returns>Returns the complete list of nodes</returns>

public List<SiteMapModel> GetSiteMapWithRoles()

iii. Create Model – We also need to create a model “SiteMapModel” which will define the properties for our fetched data. Here is the code – SiteMapModel

Code Usage:

  1. Add the “MVCSitemapProvider” in the references of the project
  2. Insert our new class extended from “DefaultSitemapProvider” coming through added reference
  3. Create “ADO.NET Entity Data Model” having all required tables for implementation
  4. Insert the “SecurityFacade” class to fetch data from database
  5. Add following entry in the web.config file (type contains location of provider. You may need to change it)

    <siteMap defaultProvider=”XPDSitemapProvider“><providers>

    <add name=”XPDSitemapProvidertype=”XPD.Web.Lib.Providers.XPDSitemapProviderattributesToIgnore=”visibleconnectionStringName=”xpd_pilotEntitiessecurityTrimmingEnabled=”truecacheDuration=”10” />

    </providers>

    </siteMap>

  6. There are 6 templates provided to construct/design the structure of the sitemap at different levels (Comes with DefaultSitemapProvider framework). You can create your own extension method to create the sitemap
    Templates
    Templates

    If we use predefined templates then we can use following methods
    i. Menu– To use Site map add following in the _layout.cshtml

    @Html.MvcSiteMap().Menu(false, true, true)

    3 parameters means:

    • We don’t want it to start from the current node (changing this to true will break it!)
    • We want the starting node to appear in child level
    • We want to show the starting node. Setting this to false will hide the parental “Home” node

    ii. Breadcrumb – In the similar way it can be used for breadcrumbs. Add following in the _layouts.cshtml

    @Html.MvcSiteMap().SiteMapPath()

    Otherwise create your extension method. Here is an example to create an extension using sitemap – HTMLHelpers

  7. Site map nodes are displayed as the authorization of logged in user. E.g. Above if user does not have access on ‘About’ then it will not come in site map

References:

https://github.com/maartenba/MvcSiteMapProvider

http://edspencer.me.uk/2011/02/10/mvc-sitemap-provider-tutorial/

http://nuget.org/packages/MvcSiteMapProvider/

http://blog.maartenballiauw.be/post/2008/08/29/Building-an-ASPNET-MVC-sitemap-provider-with-security-trimming.aspx

http://stackoverflow.com/questions/7350567/mvc-3-sitemap-provider-multiple-paths-pointing-to-same-node

http://msdn.microsoft.com/en-us/library/system.web.sitemapprovider.aspx

http://msdn.microsoft.com/en-us/library/ms178426(v=vs.100).aspx