Using Web.SiteMap with Dynamic URLS (URL Routing)
Asked Answered
F

3

10

I would like to match "approximate" matches in Web.SiteMap

The Web.Sitemap static sitemap provider works well, except for one thing. IT'S STATIC!

So, if I would have to have a sitemapnode for each of the 10,000 articles on my page like so :

  • site.com/articles/1/article-title
  • site.com/articles/2/another-article-title
  • site.com/articles/3/another-article-again
  • ...
  • site.com/articles/9999/the-last-article

Is there some kind of Wildcard mapping I can do with the SiteMap to match Anything under Articles?

Or perhaps in my Webforms Page, is there a way to Manually set the current node?

I've found a "bit" of help on this page when doing this with the ASP.Net MVC Framework, but still looking for a good solution for Webforms.

I think what I'm going to have to do is create a custom SiteMap Provider

Finch answered 15/11, 2008 at 0:48 Comment(0)
A
7

This is in response to the comment above. I can't post the full code, but this is basically how my provider works.

Suppose you have a page article.aspx, and it uses query string parameter "id" to retrieve and display an article title and body. Then this is in Web.sitemap:

<siteMapNode url="/article.aspx" title="(this will be replaced)" param="id" />

Then, you create this class:

public class DynamicSiteMapPath : SiteMapPath
{
  protected override void InitializeItem(SiteMapNodeItem item)
  {
    if (item.ItemType != SiteMapNodeItemType.PathSeparator)
    {
      string url = item.SiteMapNode.Url;
      string param = item.SiteMapNode["param"];

      // get parameter value
      int id = System.Web.HttpContext.Current.Request.QueryString[param];

      // retrieve article from database using id
      <write your own code>

      // override node link
      HyperLink link = new HyperLink();
      link.NavigateUrl = url + "?" + param + "=" + id.ToString();
      link.Text = <the article title from the database>;
      link.ToolTip = <the article title from the database>;
      item.Controls.Add(link);
    }
    else
    {
      // if current node is a separator, initialize as usual
      base.InitializeItem(item);
    }
  }
}

Finally, you use this provider in your code just like you would use the static provider.

<mycontrols:DynamicSiteMapPath ID="dsmpMain" runat="server" />

My class is more complicated than this, but these are the basics. Instead of using a querystring parameter, you could just analyze the friendly url you're using, and use that instead to retrieve the correct content. To minimize the additional db lookups with every request, you can add a caching mechanism to the provider (article title usually won't change often).

Hope this helps.

Authorized answered 20/11, 2008 at 9:9 Comment(2)
Cool! Thanks. I'm going to post a solution that I came up with too.Finch
How can I use this class in my aspx page? any help pleaseSlaby
A
2

This is not entirely an answer to your question I think, but maybe it gives you an idea. I once wrote a DynamicSiteMapPath class inheriting SiteMapPath. I use a custom attribute in each <siteMapNode> tag in Web.sitemap, like this:

<siteMapNode url="dynamicpage.aspx" title="blah" params="id" />

Then the DynamicSiteMapPath class gets the "id" parameter value, retrieves the content from the database, and overrides the currently rendered sitemap item node with the correct title and link. It's gonna take a little work, but when done correctly this is a very neat way of providing dynamic page support.

Authorized answered 19/11, 2008 at 12:26 Comment(1)
I'd love to know more information on this. It sounds interesting.Finch
A
2

I have been running into this problem and, frankly, not finding any solutions that made me happy... so I borrow ideas from here and there. My solution is multi-part: a) have the SiteMapProvider find the actual page handling the request and use it's node and b) use standard techniques for updating the sitemapnode from there.

A) The problem I kept running into is that if I didn't have the correct virtual path, the SiteMap.CurrentNode would be null and the fire the SiteMapResolve function. To solve this I subclassed XmlSiteMapProvider and overrode CurrentNode:

 namespace WebFormTools
{
    class RouteBaseSitemapProvider : XmlSiteMapProvider
    {
        public override SiteMapNode CurrentNode
        {
            get
            {
                var node = base.CurrentNode;


                if (node == null) 
                {
                    // we don't have a node, see if this is from a route
                    var page = HttpContext.Current.CurrentHandler as System.Web.UI.Page;

                    if (page != null && page.RouteData != null)
                    {
                        // try and get the Virtual path associated with this route
                        var handler = page.RouteData.RouteHandler as PageRouteHandler;

                        if (handler != null) {
                            // try and find that path instead.
                            node = FindSiteMapNode(handler.VirtualPath);
                        }
                    }

                }

                return node;
            }
        }
    }
}

Basically if the default implementation doesn't find anything, look up the route (if any) and try to find the node using the handler's virtual path.

For reference here is part of my Web.Config, Global.asax and SiteMap files:

Adding the provider

    <siteMap defaultProvider="RouteBaseSitemapProvider">
  <providers>
    <add name="RouteBaseSitemapProvider" type="WebFormTools.RouteBaseSitemapProvider" siteMapFile="Web.sitemap" />
  </providers>
</siteMap>

The route:

            routes.MapPageRoute("EvalRoutes",
            "Evals/{type}/New.aspx",
            "~/Evals/New.aspx");

And the SiteMap:

        <siteMapNode url="~/Evals/New.aspx" title="New Eval - {type}"  description="" />

B) I subclass System.Web.UI.Page, aptly named BaseClass, which adds a method to register handlers for the SiteMapResolve event:

public System.Web.SiteMapNode Process(System.Web.SiteMapNode currentNode)
    {
        if (currentNode == null) return currentNode;

        var page = HttpContext.Current.CurrentHandler as System.Web.UI.Page;

        if (page != null && page.RouteData != null)
        {

            Dictionary<Regex, string> replacements = new Dictionary<Regex, string>();

            // build a list of RegEx to aid in converstion, using RegEx so I can ignore class.  Technically I could also
            foreach (var key in page.RouteData.Values.Keys)
            {
                replacements.Add(new Regex(string.Format("\\{{{0}\\}}", key), RegexOptions.IgnoreCase), page.RouteData.Values[key].ToString());              
            }


            // navigate up the nodes
            var activeNode = currentNode;
            while (activeNode != null)
            {
                // to the replacements
                foreach(var replacement in replacements)
                {
                    activeNode.Title =  replacement.Key.Replace(activeNode.Title, replacement.Value);
                }

                activeNode = activeNode.ParentNode;
            }

        }

        return currentNode;
    }

I still need to have the URLs map appropriately (the would use the URL of the page receiving the route), which is sans routing information. I will probably use a custom Attribute in the sitemap to tell the node how to render the URL.

Anchoress answered 17/12, 2010 at 23:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.