"Knowledge has to be improved, challenged, and increased constantly, or it vanishes."

Site Map Provider using custom business objects

Recently one of the project I was in a situation to build navigation for line of business data, the existing system available was fully designed as object oriented and it connects with some database other than SQL server. While thinking about showing navigation controls to the user, it was not a good idea to drill through objects as it takes lot of resources and more number of connections. So I decided to use a custom SiteMap provider that uses LOB objects to create navigation. In this article I am going to summarize the major steps I went through.

ASP.Net 2.0 introduced the provider model for many things such as membership, roles, navigation etc and with ASP.Net 4.0 the support for the provider model continues. For enterprises the default behavior may not be enough to address the business requirements. The provider model is powerful as you can write your own providers that connect to various data sources for storing and retrieving data. This article discusses how you can create a custom SiteMap Provider that generate SiteMap navigation from objects.

ASP.Net ships with a default sitemap provider that uses an XML SiteMap file (web.sitemap) to generate the navigation. One of the most common requirements is that you need to generate your own sitemap from the SQL Server database or from custom Objects. Creating Sitemap provider from SQL server database is not in the scope of this article, but the steps including in creating a Custom SQL SiteMap provider will be identical to this article.

For the sake of demonstration, I have created a class Person. The following are the properties of class Person

id – for the person
name – name of the person
description – description about the person
parent – object of type Person, that represents the direct parent of this person

Notice the parent object inside the Person class, this makes the Person object extendable to any level. I have created the following methods for the Person class.

public static Person GetRoot() – static method, that returns the top level parent,

public List<Person> GetFirstLevelChildren() – retuns the list of all Person objects that comes directly under the current Person

By using the above two methods, one can easily drill through the entire objects easily, to navigate through all the available person objects, first you need to call the GetTopPerson and then call GetFirstLevelChildren for each person object in a nested manner. In real life, it may not be this straight forward when working with complex data structures. Now let us create a sitemap provider for the Person class. To create a custom navigation provider, you need to create a class that extends StaticSiteMapProvider. I named the SiteMap provider class as PersonSiteMapProvider. The definition of the class is as follows.

public class PersonSiteMapProvider : StaticSiteMapProvider
{
}

When you extend the StaticSiteMapProvider class, you must override the following two methods

GetRootNodeCore – returns the rootNode for the sitemap

BuildSiteMap() – this method loads the sitemap to the memory. In this method, you need to traverse through LOB data structure (in our case it is Person objects) and build the sitemap accordingly.

In addition to this, I have created a private variable in the PersonSiteMapProvider class to keep the track of the rootNode, and initialize it as null. In the BuildSiteMap method, if you find rootNode as null, that means, you need to traverse through the object structure and create the sitemap otherwise, you can assume sitemap is already built.  The sitemap provider is a staic class, once you assign rootNode once, the value will be available throughout your application.

private SiteMapNode rootNode = null;

The GetRootNodeCore method implementation is as follows.

protected override SiteMapNode GetRootNodeCore()
{
    return rootNode;
}

Let us see what is inside BuildSiteMap method.

public override SiteMapNode BuildSiteMap()
{
    lock (this)
    {
        if (rootNode == null )
        {
            Person root = Person.GetRoot();
            rootNode = new SiteMapNode(this, root.id.ToString(), "/person.aspx?id=" + root.id.ToString(), root.name);
            foreach (Person p in root.GetFirstLevelChildren())
            {
                AddNode(GetPersonAsNode(p, rootNode), rootNode);
            }
        }
        return rootNode;
    }
}

The code is self-explanatory. See the lock method, since the implemented provider is static, you need to make the variable rootNode thread safe, lock method will make the code thread safe. First it will call get the root person by calling the Person.GetRoot() method, it will add it as the rootNode. Now for each children of Person, it will call the method GetPersonNode, that recursively build all the sub nodes for each person. Let us evaluate the code for GetPersonAsNode.

private SiteMapNode GetPersonAsNode(Person p, SiteMapNode tmpRoot)
{
    SiteMapNode thisNode = new SiteMapNode(this, p.id.ToString(), "/person.aspx?id" + p.id.ToString(), p.name);
    foreach (Person p1 in p.GetFirstLevelChildren())
    {
        SiteMapNode childNode = GetPersonAsNode(p1, thisNode);
        AddNode(childNode, thisNode);
    }
    return thisNode;
}

The GetPersonAsNode first add the given person object to the node and if the person object has any children directly under it, then build the node tree for child persons by calling the same method recursively, and then return the node representation of the person.

That’s it. Now you have created a sitemap provider for the navigation of your person objects. Now you need to use this sitemap provider in your aspx pages. To use your custom sitemap provider, first you need to add the provider in the web.config.

See the below code that adds PersonSiteMapProvider in web.config

<siteMap>
    <providers>
        <add name="PersonSiteMapProvider" type="cTest.PersonSiteMapProvider"/>
    </providers>
</siteMap>

** In the type name, you need to use the fully qualified name for the SiteMapProvider class. In this case, the namespace I have used is cTest. Now you can use the PersonSiteMapProvider in the page. Create a new aspx page and then drag and drop an aspmenu and a sitemapprovider to the page and configure them to use the PersonSiteMapProvider.

See the markup for the sitemap provider in my aspx page.

<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1"></asp:Menu>

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" SiteMapProvider="PersonSiteMapProvider" />

Now run the page, the aspmenu control will display the sitemap contained in your objects. See the sample output

clip_image001

By using the sitemap provider, the application will improve its performance. The sitemap provider caches the data in memory. So instead of hitting your back end data store each time to retrieve the sitemap data, your application will only hit the backend data store for the first time and for subsequent requests, it will reuse the SiteMap node from from memory.

References

http://msdn.microsoft.com/en-us/library/ms178431%28v=VS.80%29.aspx

No Comments