views:

3874

answers:

5

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?

A: 

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

Atømix
+2  A: 

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.

I'd love to know more information on this. It sounds interesting.
Atømix
+4  A: 

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.

Cool! Thanks. I'm going to post a solution that I came up with too.
Atømix
+1  A: 

Here's the code I used. IN Global.asax:

SiteMap.Provider.SiteMapResolve += new SiteMapResolveEventHandler(PathExpansionHandler.ExpandPath);

The Class File:
//----------------------------------------------------------------------- // This file is part of the Microsoft .NET SDK Code Samples. // // Copyright (C) Microsoft Corporation. All rights reserved. // //This source code is intended only as a supplement to Microsoft //Development Tools and/or on-line documentation. See these other //materials for detailed information regarding Microsoft code samples. // //THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY //KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE //IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A //PARTICULAR PURPOSE. //----------------------------------------------------------------------- using System; using System.Web;

/// <summary>
/// Summary description for SiteMapResolveEventHandler
/// </summary>
public class PathExpansionHandler {

    private static SiteMapNode findParentNode(string path) {
        SiteMapNodeCollection nodes = SiteMap.RootNode.GetAllNodes();
        while (path.Contains("/")) {
            path = path.Substring(0, path.LastIndexOf('/'));
            foreach (SiteMapNode node in nodes) {
                if(path.Equals(node.Url))
                    return node;
                // do something
            }
        }

        return null;
    }

    public static SiteMapNode ExpandPath(Object sender, SiteMapResolveEventArgs e) {

        if (SiteMap.CurrentNode == null)
            return findParentNode(e.Context.Request.Path);

        //Obtain a reference to the current node and its ancestors
        SiteMapNode nodeCopy = SiteMap.CurrentNode.Clone(true);

        //The temporary node is used for walking up the chain of nodes, fixing up Urls and
        //other properties along the way.
        SiteMapNode tempNode = nodeCopy;

        //Check if there is a newsgroup type in the query string
        string typeID = null;
        string typeIDUrlEncoded = null;
        if (!String.IsNullOrEmpty(e.Context.Request.QueryString["type"])) {
            typeID = e.Context.Server.HtmlEncode(e.Context.Request.QueryString["type"]);
            typeIDUrlEncoded = e.Context.Server.UrlEncode(e.Context.Request.QueryString["type"]);
        }

        //First perform Url fixups for the postings page
        //If there is a posting ID in the query string, then we know the current node
        //is the postings page.
        if (!String.IsNullOrEmpty(e.Context.Request.QueryString["postingID"])) {
            string postingID =
                e.Context.Server.HtmlEncode(e.Context.Request.QueryString["postingID"]);
            string postingIDUrlEncoded =
                e.Context.Server.UrlEncode(e.Context.Request.QueryString["postingID"]);

            string newUrl = tempNode.Url + "?type=" + typeIDUrlEncoded + "&postingID=" + postingIDUrlEncoded;
            string newTitle = tempNode.Title + ": " + postingID;

            tempNode.Url = newUrl;
            tempNode.Title = newTitle;

            //Walkup one level to the parent
            tempNode = tempNode.ParentNode;
        }

        //Next, perform fixups for the newsgroup page
        //By this point the nodeCopy variable is pointing at the newsgroup node
        if (!String.IsNullOrEmpty(e.Context.Request.QueryString["type"])) {
            string newUrl = tempNode.Url + "?type=" + typeIDUrlEncoded;
            string newTitle = tempNode.Title + ":  " + typeID;

            tempNode.Url = newUrl;
            tempNode.Title = newTitle;
        }

        //Lastly, return the current node
        return nodeCopy;
    }
}
Atømix
A: 

Please explain what is "mycontrols" in above code.

That's the Tag Prefix. When you have a control, you have to declare it in the head of your page. You can choose anything. Think of it like a namespace. For ASP.Net Controls, they always start with "asp". Usercontrols default to "uc" I think.
Atømix