ASP.NET

Breadcrumbs in ASP.NET with the SiteMapPath control

ASP.NET · · Published 2026-10-03

A breadcrumb shows where the current page sits in the site: Home › Products › Training. In Web Forms the SiteMapPath control builds it automatically from the same Web.sitemap file that can feed a Menu control.

Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
  <siteMapNode url="~/Default.aspx" title="Home">
    <siteMapNode url="~/Products/Default.aspx" title="Products">
      <siteMapNode url="~/Products/Training.aspx" title="Training" />
      <siteMapNode url="~/Products/Software.aspx" title="Software" />
    </siteMapNode>
    <siteMapNode url="~/Contact.aspx" title="Contact" />
  </siteMapNode>
</siteMap>

The file must be called Web.sitemap and sit at the application root for the default provider to find it. Each URL may appear only once.

The control in a master page

<nav aria-label="Breadcrumb" class="crumbs">
    <asp:SiteMapPath ID="smpTrail" runat="server" PathSeparator=" › "
        RenderCurrentNodeAsLink="false" ShowToolTips="false" />
</nav>

On ~/Products/Training.aspx it renders Home › Products › Training, with the first two as links. The control finds the current node by comparing the request URL with the site map; on a page that is not in the file, it renders nothing.

Templates for cleaner markup

By default the control renders spans. Templates let you output what you want, including aria-current on the last item:

<asp:SiteMapPath ID="smpTrail" runat="server" PathSeparator="">
    <NodeTemplate>
        <a href='<%# Eval("Url") %>'><%#: Eval("Title") %></a> ›
    </NodeTemplate>
    <CurrentNodeTemplate>
        <span aria-current="page"><%#: Eval("Title") %></span>
    </CurrentNodeTemplate>
</asp:SiteMapPath>

In the templates, the data item is the SiteMapNode, so Eval("Url") returns an already resolved URL and Eval("Title") the title. A RootNodeTemplate also exists if the home link needs its own look.

Pages with query strings

Product.aspx?id=42 is one URL in the site map but many products. Handle the provider's SiteMapResolve event to build a node for the current item on the fly:

// Global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
    SiteMap.SiteMapResolve += (s, args) =>
    {
        if (!args.Context.Request.Path.EndsWith("/Product.aspx", StringComparison.OrdinalIgnoreCase))
            return null; // fall back to normal behaviour

        SiteMapNode parent = SiteMap.Provider.FindSiteMapNode("~/Products/Default.aspx");
        if (parent == null) return null;

        int.TryParse(args.Context.Request.QueryString["id"], out int id);
        string name = ProductRepository.Find(id)?.Name ?? "Product";

        var current = new SiteMapNode(SiteMap.Provider, "product-" + id,
                                      args.Context.Request.RawUrl, name);
        current.ParentNode = parent.Clone(true);
        return current;
    };
}

The cloned parent keeps the original tree intact. Remember to encode the product name when it is displayed; the template above does that with <%#: %>.

Style

.crumbs { font-size: .9rem; margin: .5rem 0 1rem; }
.crumbs a { color: #1b6aa5; }
.crumbs [aria-current] { color: #555; }

Structured data

Search engines can show breadcrumbs instead of a raw URL in results if the page includes BreadcrumbList structured data (JSON-LD) that matches the visible trail. Generate it from the same nodes: walk from SiteMap.CurrentNode up through ParentNode, then output the list in order with absolute URLs.

Official documentation