Shortcut/FavIcon with SaaS Headless CMS and ASP.NET Core Razor Pages

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.

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: