This blog post explains one way to let CMS users control shortcut/favorite icons with SaaS headless content management systems and provides an ASP.NET Core razor pages view component implementation.
Update 15.Dec.2025: After years of frustration with WordPress, I am finally abandoning this blog. The content will likely stay here for some time, but new content will appear here:
This prototype is almost exactly like the prototype for google analytics that I described in a previous blog post, so I will not repeat that content.
To summarize, any entry should be able to specify a favicon. If an entry does not specify a favicon, then the view renders the favicon associated with the nearest ancestor that specifies a favicon.
The implementation depends on how you let the CMS user specify the favicon. If they need to type or paste in a file system path, then maybe a single line text field will suffice. If they need to be able to select from a media library, then a more complex field type may be required, as well as logic to map that structure back to a flat list. In my case, I store page metadata like this in a nested structure, which also affects value retrieval. Optimally, views are completely unaware of these implementation details.
This prototype solution uses the following.
- A field for the favicon in the collection of metadata fields available in all page entries in the CMS.
- Depends on CMS and implementation.
- A property in the class that models that structure to expose this new value.
- The FavIcon property I had already added to the class in that previous post.
- A view component class that determines the favicon.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Deliverystack.DeliveryApi.Models;
using Deliverystack.Models;
public class FavIcon : ViewComponent
{
public class FavIconModel
{
public string Path { get; }
public FavIconModel(PathApiResultModel data)
{
foreach (var entry in data.Entries.Reverse())
{
if (!string.IsNullOrEmpty(entry.PageData?.FavIcon))
{
Path = entry.PageData.FavIcon;
break;
}
}
}
}
private PathApiClient _client;
public FavIcon(PathApiClient client)
{
_client = client;
}
// May 18 2021 - logic is synchronous and lightweight; ignore warnings about async
#pragma warning disable CS1998
public async Task<IViewComponentResult> InvokeAsync()
{
var favIconModel = new FavIconModel(
_client.Get(new PathApiBindingModel()
{ Path = this.HttpContext.Request.Path, Ancestors = Int32.MaxValue }));
if (String.IsNullOrEmpty(favIconModel.Path))
{
return Content(String.Empty);
}
return View(favIconModel);
}
#pragma warning restore CS1998 // Rethrow to preserve stack details
}
- A view component template (razor .cshtml file) to render the favicon.
@model HeadlessArchitect.Website.Pages.Shared.Components.Breadcrumb.FavIcon.FavIconModel
@{
if (!string.IsNullOrEmpty(Model.Path))
{
<link rel="icon" href="@Model.Path" type="image/x-icon">
}
}
- Logic to invoke the view component.
- @await Component.InvokeAsync(“FavIcon”) in that previous post.