Appearance
Are you an LLM? You can read better optimized documentation at /features/breadcrumbs.md for this page in Markdown format
Breadcrumbs navigation
This document provides a technical overview of the automatic breadcrumb generation feature in TheExampleApp. The system is designed to create a navigational trail based on the current request URL, with support for localization and dynamic label replacement for content-driven pages.
Breadcrumb implementation
The core of the breadcrumb generation is implemented as custom ASP.NET Core middleware. This middleware intercepts incoming requests, parses the URL path, and constructs a list of breadcrumb segments.
The Breadcrumb Data Model
Each segment in the navigation trail is represented by the Breadcrumb class. This simple data structure holds the display text and the URL for a single link.
csharp
// From: TheExampleApp/Configuration/Breadcrumbs.cs
/// <summary>
/// Encapsulates a single breadcrumb.
/// </summary>
public class Breadcrumb
{
/// <summary>
/// The human readable label of the breadcrumb.
/// </summary>
public string Label { get; set; }
/// <summary>
/// The path to navigate to if the breadcrumb is clicked.
/// </summary>
public string Path { get; set; }
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Path Parsing and Label Generation
The Breadcrumbs middleware class contains the primary logic in its Invoke method. For each request, it performs the following steps:
- Retrieves the Path: It gets the current request path from
HttpContext.Request.Path. - Splits the Path: The path string is split by
/to get individual segments (e.g.,/courses/my-coursebecomes["courses", "my-course"]). - Initializes the Trail: The breadcrumb list is always initialized with a root "Home" link. The label "Home" is retrieved using the application's localization service, ensuring it is displayed in the current language. For more details on localization, see the Localization documentation.
- Iterates and Builds: The middleware loops through the path segments. For each segment, it:
- Constructs a cumulative path (e.g.,
/courses, then/courses/my-course). - Generates a label for the segment. The label is determined using the following priority:
- Localized Key: It checks if a localization key exists for the segment (e.g.,
coursesLabelfor the "courses" segment). If found, the translated value is used. - Formatted Segment: If no localized key is found, it formats the raw segment by replacing hyphens with spaces (e.g.,
my-coursebecomesmy course).
- Localized Key: It checks if a localization key exists for the segment (e.g.,
- Constructs a cumulative path (e.g.,
- Stores the Result: The final
List<Breadcrumb>is stored in theHttpContext.Itemscollection with the key"breadcrumbs". This makes the data available to later stages of the request pipeline, such as MVC controllers or Razor views, without modifying method signatures.
csharp
// From: TheExampleApp/Configuration/Breadcrumbs.cs
public async Task Invoke(HttpContext context)
{
var path = context.Request.Path;
var parts = path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries);
var items = new List<Breadcrumb>();
// 1. Add localized "Home" link
items.Add(new Breadcrumb { Label = _localizer["homeLabel"].Value, Path = "/" });
var translations = _localizer.GetAllStrings(false);
foreach (var part in parts)
{
var label = part.Replace("-", " ");
// 2. Check for a matching localization key (e.g., "coursesLabel")
if(translations.Any(c => c.Name == $"{LowerCaseFirstChar(label)}Label"))
{
label = _localizer[$"{LowerCaseFirstChar(label)}Label"].Value;
}
// 3. Add the new breadcrumb to the list
items.Add(new Breadcrumb { Label = label, Path = $"/{string.Join('/', parts.Take(Array.IndexOf(parts, part) + 1))}" });
}
// 4. Store the list for downstream use
context.Items["breadcrumbs"] = items;
await _next(context);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
BreadcrumbsManager
While the middleware provides excellent baseline functionality, it cannot resolve dynamic, content-based labels. For example, a URL like /courses/agile-development will result in a breadcrumb labeled "agile development". The actual course title, "Agile Development with Contentful", is stored in the CMS and is not known to the middleware.
The BreadcrumbsManager solves this problem. It provides a mechanism for other parts of the application (typically controllers or Razor Page models) to modify the breadcrumb labels after they have been generated by the middleware.
The manager is registered via its interface, IBreadcrumbsManager, using the built-in dependency injection container. See the Dependency Injection documentation for more information.
csharp
// From: TheExampleApp/Startup.cs - Service Registration
services.AddTransient<IBreadcrumbsManager, BreadcrumbsManager>();1
2
2
The ReplaceCrumbForSlug method allows for targeted replacement of a label. It finds a breadcrumb whose label matches the URL slug and updates it with a more descriptive title, usually fetched from Contentful.
csharp
// From: TheExampleApp/Configuration/Breadcrumbs.cs
public class BreadcrumbsManager : IBreadcrumbsManager
{
private readonly IHttpContextAccessor _accessor;
private List<Breadcrumb> _crumbs;
public BreadcrumbsManager(IHttpContextAccessor accessor)
{
_accessor = accessor;
_crumbs = accessor.HttpContext.Items["breadcrumbs"] as List<Breadcrumb>;
}
/// <summary>
/// Replaces the label of a <see cref="Breadcrumb"/> for a specific slug with a new label.
/// </summary>
public void ReplaceCrumbForSlug(string slug, string label)
{
// The initial label is derived from the path segment (e.g., "course-x" becomes "course x")
// This logic finds that initial label and replaces it.
if(_crumbs.Any(c => c.Label == slug))
{
_crumbs.First(c => c.Label == slug).Label = label;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Usage Example (Conceptual):
In a Razor Page model for a course page:
csharp
public class CourseModel : PageModel
{
private readonly IBreadcrumbsManager _breadcrumbsManager;
private readonly IContentfulClient _client;
// ... constructor ...
public async Task OnGetAsync(string slug)
{
var course = await _client.GetEntryBySlug<Course>(slug);
// The middleware created a label from the slug, e.g., "agile-development"
// We replace it with the actual course title from Contentful.
_breadcrumbsManager.ReplaceCrumbForSlug(slug.Replace("-", " "), course.Title);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Middleware integration
The breadcrumb functionality is enabled by registering the custom middleware and its related services in the Startup.cs class. For more details on service setup, refer to the Service Configuration documentation.
Service Registration
The BreadcrumbsManager is registered as a transient service in the ConfigureServices method.
csharp
// From: TheExampleApp/Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// ... other services
services.AddTransient<IBreadcrumbsManager, BreadcrumbsManager>();
// ...
}1
2
3
4
5
6
7
2
3
4
5
6
7
Request Pipeline Configuration
A custom extension method, UseBreadcrumbs, provides a clean way to add the middleware to the application's request pipeline in the Configure method.
csharp
// From: TheExampleApp/Configuration/Breadcrumbs.cs
public static class BreadcrumbsMiddlewareExtensions
{
public static IApplicationBuilder UseBreadcrumbs(this IApplicationBuilder builder)
{
return builder.UseMiddleware<Breadcrumbs>();
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
The order of middleware is critical. UseBreadcrumbs() must be called before UseMvc() to ensure that the breadcrumb list is generated and available in HttpContext.Items before the MVC framework processes the request and executes the action method.
csharp
// From: TheExampleApp/Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseStaticFiles();
app.UseSession();
app.UseRequestLocalization(...);
app.UseStatusCodePagesWithReExecute("/Error");
app.UseBreadcrumbs(); // Placed before UseMvc
app.UseDeeplinks();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Rendering breadcrumbs
The final step is to render the generated breadcrumb trail in the UI. This is handled by a dedicated Razor partial view. For more on the application's view structure, see the Views documentation.
Breadcrumbs.cshtml Partial View
The TheExampleApp/Views/Shared/Breadcrumbs.cshtml partial view is responsible for rendering the HTML for the breadcrumb navigation.
- It retrieves the
List<Breadcrumb>fromContext.Items["breadcrumbs"]. - It iterates through the list, rendering each item as a link within a
<ul>structure. - The entire component is wrapped in a
<nav>element with thebreadcrumbclass for styling.
This partial view can be included in any page layout or view where breadcrumbs are desired using @await Html.PartialAsync("Breadcrumbs").
razor
// From: TheExampleApp/Views/Shared/Breadcrumbs.cshtml
@{
var crumbs = Context.Items["breadcrumbs"] as List<TheExampleApp.Configuration.Breadcrumb>;
}
@if (crumbs != null)
{
<nav class="breadcrumb">
<ul>
@foreach (var crumb in crumbs)
{
<li><a href="@crumb.Path">@crumb.Label</a></li>
}
</ul>
</nav>
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Styling
The visual appearance of the breadcrumbs is controlled by CSS rules targeting the .breadcrumb class and its child elements. These styles are defined in the main application stylesheet located at ~/stylesheets/style.css, which is linked in the main layout file _Layout.cshtml.