20 Ekim 2009 Salı

Enhanced XMLSiteMapProvider with support for dynamic nodes.

Hello All,

This might be a long one so bear with me here this might bore you :).
I have have had the need to be able to add dynamic nodes to my sitemap, I am using a sitemap xml file. My first solution, was to create a custom sitemap provider, which would parse the XML and if it detected the node I had to insert my dynamic nodes into, it would do this and then continue.

Fine I thought but not good enough. I wanted a generic solution that could be reused and that would be easy to use. I came up with the idea of extending the sitemap XML file to contain some new nodes. Then create a custom parser that would make use of this. The idea in the end was to add two new elements to the sitemap, the elements being <dynamicNode> and <dynamicNodes>, the idea was that they would call a static method on a class that would either return 1 node or a collection. These would then be injected into the sitemap when it is created.

The beauty here was that you could add dynamic nodes to our sitemap, one other side effect was that you could now nest dynamic nodes in static nodes, and also nest static nodes in either a dynamic node, or a dynamic nodes collection. If you look at the sample project supplied you will see this works quite well. One thing to note is that any static nodes added to either a dynamic node or dynamic nodes collection. It will be added at the end. Which I think is expected behaviour, well for me anyway.

- The new sitemap file -
Here is an example of what the sitemap file will now look like. Say we have 3 static nodes, and within the static products node, we need to get products from our database, and add them as children. You could create an easy site map file like so:

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode url="Default.aspx" title="Home">
    <siteMapNode url="About.aspx" title="About Us" />
    <siteMapNode url="FAQ.aspx" title="FAQ" />
    <siteMapNode url="Product.aspx" title="Products">
      <dynamicNodes type="Sample.MenuHelper, Sample" method="GetProducts" />
    </siteMapNode>
  </siteMapNode>
</siteMap>

Here you can see that we have added some static nodes, and within our products node, we have added a dynamicnodes, node. You can see that this calls the static method GetProducts on the MenuHelper class. This method will return a collection of nodes which will be inserted into our static nodes children. And dynamic insertion of child nodes is now possible :)... You are also able to have a dynamic node, which is a single root node, plus you can nest until your heart is content, which you will see in the sample.
-- MenuHelper --
In this class we would have a static GetProducts method, this would return a list of XX objects. These objects are helpers, they allow us to create a collection of sitemap nodes, so that they can be used in the provider to build up the sitemap. The node has the default properties (key,url,title,description) and you can add custom attributes too. The method would look like so, you could imagine this would use the database to get products and build up the list.
public static List<EnhancedXMLSiteMapProvider.EnhancedSiteMapNode> GetProducts() {
            return new List<EnhancedXMLSiteMapProvider.EnhancedSiteMapNode>() {
                  new EnhancedXMLSiteMapProvider.EnhancedSiteMapNode() {
                       Key="Product1.aspx",
                       Url="Product1.aspx",
                       Title="Product 1"
                   }
             };
}

-- Web.Config Configuration --
You need to configure the provider in the web.config. In the sample project this is done as so:
<siteMap defaultProvider="EnhancedXMLSiteMapProvider">
            <providers>
                <add name="EnhancedXMLSiteMapProvider" type="EnhancedXMLSiteMapProviderSample.EnhancedXMLSiteMapProvider, EnhancedXMLSiteMapProviderSample"
                siteMapFile="~/Web.sitemap"
                cacheDuration="15"/>
            </providers>
</siteMap>

I am basically setting it as the default provider, I am pointing it to the xml sitemap file, and setting the cache duration (this defaults to 5 if not set). Once this is done you are ready to run. One thing to note is that the provider also puts a dependancy on the xml file, so that once changed it invalidates the sitemap. And also after the time duration.
One issue is that any dynamic data does not invalidate the cache, you are able to set a cacheKey on the provider which is used for caching. Then you can use code to remove the item. So you backend could remove the cache item when it needs the menu to change.

-- The Provider --
I am not going to go into full detail on the provider and show all the code, it is all included in the sample project so just have a look. Basically the main workings, just parses the XML file, depending on the node type will do different things. If it encounters a dynamicNode or dynamicNodes element it will grab the type and method attributes, and use reflection to call these methods and continue parsing the data.
I have tested this with many levels/combinations of nodes and I have not had any issues yet, mind you I know this needs neatening up and it is only the efforts of a days work. I will post updates as I go and any bug fixes to this. But at the moment I am happy with where it is.

-- Final Word --

The sample project is supplied below, have a play and let me know what you think. Also this is the first time I have ever used the site map provider, so if I have just reinvented the wheel please give me a gentle slap and let me know :).

 

reference :http://weblogs.asp.net/stefansedich/archive/2008/08/14/enhanced-xmlsitemapprovider-with-support-for-dynamic-nodes.aspx