Kentico 12: Design Patterns Part 6 - Rendering Meta Tags in Kentico 12 MVC
Tags: kentico, mvc, csharp, aspnet
Published: 2019-06-24 13:13:17

---
title: Kentico 12: Design Patterns Part 6 - Rendering Meta Tags in Kentico 12 MVC
description: Discover a great way to handle setting and displaying meta tags in your Kentico 12 MVC application
published: true
tags: kentico, mvc, csharp, aspnet
series: Kentico 12 - Design Patterns
---

![](https://thepracticaldev.s3.amazonaws.com/i/ea4ihs7myvmhiahzfii3.jpg)
<figcaption>Photo by <a href="https://unsplash.com/@_imkiran">Sai Kiran Anagani</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

## The Importance of Meta Tags

[HTML Meta tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) don't display information to our visitors - they instead display information to other systems or web crawlers - helpful hints to indicate information about the site and the content on a page.

If we want to curate the content that shows up in search engine results for our site, then we need to use meta tags to provide the best description possible.

If we want our site to be easily and accurately shared through social media, we need to add the correct meta tags to the page to tell those social platforms (ex: [Facebook](https://developers.facebook.com/docs/sharing/webmasters), [Pinterest](https://developers.pinterest.com/docs/rich-pins/overview/), and [Twitter](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started)) what is being shared.

Ok, so how do we ensure these meta tags are correctly applied to the pages rendered by our Kentico 12 MVC applications? 🤔

## The Simple Approach

Our `<meta>` tags need to be added in the `<head>` of our HTML document. In ASP.NET MVC the `<head>` is usually located in the `Views\_Layout.cshtml` file.

The [DancingGoat](https://docs.kentico.com/k12/installation/installing-kentico-mvc-projects) sample site presents us with something like this:

```html
<!DOCTYPE html>

<html>
<head id="head">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta charset="UTF-8" />
    <title>@ViewBag.Title - Dancing Goat</title>
    <link rel="icon" href="@Url.Content("~/content/images/favicon.png")" type="image/png" />
    <link href="@Url.Content("~/Content/Styles/Site.css")" rel="stylesheet" type="text/css" />
    @RenderSection("styles", required: false)
    
    <!-- We can add some meta tags -->
    <meta name="description" content="A description of the page!" />
</head>
```

This file is effectively the "master page" of the site with all page specific content being rendered in a section within it.

We want to add `<meta>` tags to the `<head>` above, but we have a problem.

The information we want to put into the `<meta>` tags comes from each specific page, but this layout is used by all pages. 🤨

We need to set this information dynamically, per request.

MVC presents us with a solution in the form of a [ViewBag](https://www.tutorialsteacher.com/mvc/viewbag-in-asp.net-mvc).

The `ViewBag` is a dynamic (read: un-typed) collection of data that we can access in our Views and which can be assigned values anywhere in the MVC pipeline.

It's globally available in all of our Views, including the `_Layout.cshtml`.

So, the standard approach would be to assign some values to the `ViewBag` in a `Controller` class and then access the data in the layout:

```csharp
// ProductsController.cs

public class ProductController
{
    public ActionResult Index()
    {
         ViewBag.OGTitle = "Acme Products 🎂";
         ViewBag.OGImage = "https://acme-products.com/logo.png";
         ViewBag.Description = "A description of the page!";

         return View();
    }
}
```

```html
<!-- _Layout.cshtml -->

<head>
   <!-- ... -->
   <meta name="og:title" content="@ViewBag.OGTitle" />
   <meta name="og:image" content="@ViewBag.OGImage" />
   <meta name="description" = content="@ViewBag.Description" />
</head>
```

This works, but there's a few issues...

- ❌ We end up performing the same kind of assignments, throughout the code, that we have in our example controller - except there's no validation on the data or requirements that all the required `ViewBag` keys are assigned
- ❌ The `ViewBag` is dynamic, which means you can typo a property name and not have any of the compiler support we normally get from C#
- ❌ `ViewBag` use is usually discouraged in applications that scale beyond a demo, and having this `ViewBag` use here might encourage more of it, with the caveats above multiplied.
- ❌ `ViewBag` isn't really unit testable... soo... 😣

We have the power of C# and MVC at our fingertips, let's use them to solve this dilemma!

## Design Patterns To The Rescue

We can use several common software design patterns to help us come to a more elegant and robust solution. 😎

### Domain Driven Design (DDD) - Protecting Invariants

First, let's define a type to represent the "meta" information we want in our page.

```csharp
public class SiteMeta
{
    public const string OGImage = "og:image";
    public const string OGSiteName = "og:site_name";

    private readonly Dictionary<string, string> metas;

    public string Title { get; } = "";

    public IReadOnlyDictionary<string, string> Metas => metas;

    public SiteMeta(
        string title,
        Dictionary<string, string> metas = null)
    {
        Guard.Against.NullOrWhiteSpace(title, nameof(title));

        Title = title;

        this.metas = metas is null
            ? new Dictionary<string, string>()
            : metas;
    }

    public void SetOGImage(string value)
    {
        Guard.Against.NullOrWhiteSpace(value, nameof(value)); 

        metas[OGImage] = value;       
    }

    public void AddMeta(string key, string value)
    {
        Guard.Against.NullOrWhiteSpace(key, nameof(key));
        Guard.Against.NullOrWhiteSpace(value, nameof(value));

        metas[key] = value;
    }
}
```

We have defined a class that [protects its invariants](https://softwareengineering.stackexchange.com/a/32755/87445) by using [Guard Clauses](https://deviq.com/guard-clause/) for both the constructor and method calls. 👍

We also add some strong typing with `SetOGImage()` that solves the issue we had with the dynamic `ViewBag`. 😄

This is a good first step towards ensuring our application works how we expect (and it's also very unit testable code!)

### Dependency Injection (DI) - Composing Layers

Now let's create a class which acts as a store for the `SiteMeta` type, allowing one part of the application to set the value and another to retrieve it.

```csharp
public interface ISiteMetaService
{
    SiteMeta Get();
    void Set(SiteMeta siteMeta);
}

public class SiteMetaService : ISiteMetaService
{
    private SiteMeta currentSiteMeta;

    public SiteMeta Get() => currentSiteMeta ?? new SiteMeta("Default");

    public void Set(SiteMeta siteMeta)
    {
        Guard.Against.Null(siteMeta, nameof(siteMeta));

        currentSiteMeta = siteMeta;
    }
}

```

We can register this type with our Inversion of Control (IoC) container and then supply it as a constructor parameter where we need to use it - like in a `Controller` ([injecting our dependency](https://en.wikipedia.org/wiki/Dependency_injection)).

> We want our `ISiteMetaService` to have a lifetime of "per-request" in our IoC container. This helps ensure that `SiteMeta` from a previous request isn't accidentally used on a later request.

```csharp
public class ProductController
{
    private readonly ISiteMetaService service;

    public ProductController(ISiteMetaService service)
    {
         Guard.Against.Null(service, nameof(service));

         this.service = service;
    }

    public ActionResult Index()
    {
         // These values could also come from a custom Kentico PageType

         var meta = new SiteMeta("Acme Products 🎂");

         meta.SetOGImage("https://acme-products.com/logo.png");

         service.Set(meta);

         return View();
    }
}
```

Now we can access this service through DI - property injection instead of constructor injection - in an `ActionFilterAttribute`:

```csharp
public class SiteMetaActionFilterAttribute : ActionFilterAttribute
{
    public ISiteMetaService SiteMetaService { get; set; }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.Controller.ViewBag.SiteMeta = SiteMetaService.Get();

        base.OnActionExecuted(filterContext);
    }
}
```

In our `_Layout.cshtml` we are guaranteed to have a `SiteMeta` property on the `ViewBag` of type `SiteMeta`:

```html
<head>
   @{
       var siteMeta = ViewBag.SiteMeta as SiteMeta;
   }
   <title>@siteMeta.Title</title>
   
   @foreach(var meta in siteMeta.Metas)
   {
       <meta name="@meta.Key" content="@meta.Value" />
   }
</head>
```

Well isn't that nice! 👏👏

Believe it or not, even with all this sweet C# awesomeness, we still have use cases we don't handle. 😮

What if we want to have something like the `og:site_name` set for the whole site but also want to pull this value from configuration?

Or, maybe we want to provide a fallback image in case one isn't set in the `SiteMeta`?

We could even encounter a scenario where a `SiteMeta` was never passed to the `SiteMetaService.Set()` call, which means we'd get a `NullReferenceException` at runtime when we try to access its properties! 😨

### Single Responsibility Principle (SRP)

We could put the validation and configuration for generating `SiteMeta` in the `SiteMetaService`, but that increased complexity will make it harder to test.

Instead, let's keep the roles of each of our classes focused, making sure to abide by the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). 🤗

This class below helps us standardize the values of our `SiteMeta` based on the requirements above:

```csharp
public interface ISiteMetaStandardizer
{
    SiteMeta Standardize(SiteMeta siteMeta);
}

public class SiteMetaStandardizer : ISiteMetaStandardizer
{
    private readonly Dictionary<string, string> defaultMeta;

    public SiteMetaStandardizer(string defaultSiteName, string defaultImage)
    {
        Guard.Against.NullOrWhiteSpace(defaultSiteName, nameof(defaultSiteName));
        Guard.Against.NullOrWhiteSpace(defaultImage, nameof(defaultImage));

        defaultMeta = new Dictionary<string, string>
        {
            { SiteMeta.OGImage, defaultImage },
            { SiteMeta.OGSiteName, defaultSiteName }
        };
    }

    public SiteMeta Standardize(SiteMeta siteMeta)
    {
        if (siteMeta is null)
        {
            return new SiteMeta(defaultMeta[SiteMeta.OGSiteName], defaultMeta);
        }

        foreach (var meta in defaultMeta)
        {
            if (!siteMeta.Metas.ContainsKey(meta.Key))
            {
                siteMeta.AddMeta(meta.Key, meta.Value);
            }
        }

        return siteMeta;
    }
}
```

We can register an instance of this type in our IoC container, as a Singleton, constructing it with values from Kentico's `SettingsKeyInfoProvider` or from `AppSettings`.

Now we just need to update our `SiteMetaService` to use the `ISiteMetaStandardizer`:

```csharp
public class SiteMetaService : ISiteMetaService
{
    private SiteMeta currentSiteMeta;
    private readonly standardizer;

    public SiteMetaService(ISiteMetaStandardizer standardizer)
    {
        Guard.Against.Null(standardizer, nameof(standardizer));

        this.standardizer = standardizer;
    }

    public SiteMeta Get() => standardizer.Standardize(currentSiteMeta);

    public void Set(SiteMeta siteMeta)
    {
        Guard.Against.Null(siteMeta, nameof(siteMeta));

        currentSiteMeta = siteMeta;
    }
}
```

If you've been coding along, you should now be able to use the `ISiteMetaService` in some of your MVC routes, pulling data from the Kentico pages you fetch from the database. 😉

## Wrap Up

That was a lot of coding, and hopefully you found it helpful and enlightening.

What did we accomplish?

- ✅ We now have a type-safe and validated way of setting and accessing our `SiteMeta` values from anywhere in the application.
- ✅ We leveraged ASP.NET MVC's pipeline to apply our `SiteMeta` values to the `ViewBag` for every response.
- ✅ We created focused and testable classes that compose together as a flexible and power system.

Want to enhance what we built here?

You can try adding additional methods to the `SiteMeta` class that set specific `<meta>` tags, like `og:image`.

You could add more logic to the `SiteMetaStandardizer`, setting defaults based on things like the current url, controller, or anything else Kentico, ASP.NET, and MVC make available to us.

I was also thinking about adding extension methods to Kentico's `*Info` or `PageType` classes to turn them into `SiteMeta` (ex: `skuInfo.ToSiteMeta();`)

If you have any suggestions, I'd love to hear them. Maybe we can turn this into an open source project for other Kentico developers to use?! 💪

---

If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

{% tag kentico %}

or my [Kentico 12: Design Patterns](https://dev.to/search?q=Kentico%2012%20-%20Design%20Patterns) series.