A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Ravindra S. Rathore
Sep 11, 2019
  4499
(9 votes)

An easy way to manage static text in Episerver CMS

Hi All,

Today, I am writing a blog post to create a dictionary block to manage static text like - "Read More, Learn more, View More, etc...". This is very helpful when you have multiple CTA blocks or pages those have this kind of static text and you want to give the control to the content author to update this text as well as to localize it.

Also, it is very easy to manage all the content inside Episerver CMS and you don't need to create additional fields on each and every block again and again.

Step 1 - Create a block class and name it "DictionaryEntryBlock"

To do this you just need to create a block class and name it "DictionaryEntryBlock".

using System.ComponentModel.DataAnnotations;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;

namespace Dictionary.Models.Blocks
{
    [ContentType(DisplayName = "DictionaryEntry", GUID = "5199528c-6220-4b39-83b5-f1816df85ac8", Description = "Add dictionary value entry for static text")]
    public class DictionaryEntryBlock : BlockData
    {
        [Display(
            Name = "Key",
            Description = "Dictionary key name",
            GroupName = SystemTabNames.Content,
            Order = 1)]
        public virtual string Key { get; set; }

        [CultureSpecific]
        [Display(
            Name = "Phrase",
            Description = "Dictionary key pharse value",
            GroupName = SystemTabNames.Content,
            Order = 2)]
        public virtual string Phrase { get; set; }
    }
}

Step 2 - Create a new interface called "IDictionaryRepository" and a class which implements this interface

Then create a new interface called "IDictionaryRepository". Please create a folder inside your "Business" folder and named it "Repository" and then create below repository interface and class inside this folder.

using System.Collections.Generic;
using Dictionary.Models.Blocks;

namespace Dictionary.Business.Repositories
{
    public interface IDictionaryRepository
    {
        string GetPhrase(string key);

        IEnumerable<DictionaryEntryBlock> GetAll();
    }
}

Now you have to create a new class(DictionaryRepository) to implement the methods of the above interface. I am using the Episerver Find. You can also use some other search provider if have configured any other search provider.

using System;
using System.Collections.Generic;
using System.Linq;
using Dictionary.Models.Blocks;
using EPiServer.Find;
using EPiServer.Find.Cms;
using EPiServer.Globalization;

namespace Dictionary.Business.Repositories
{
    public class DictionaryRepository : IDictionaryRepository
    {
        private readonly IClient _findClient;

        public DictionaryRepository(IClient findClient)
        {
            _findClient = findClient ?? throw new ArgumentNullException(nameof(findClient));
        }

        public string GetPhrase(string key)
        {
            var queryBuilder = this._findClient
                .Search<DictionaryEntryBlock>()
                .ExcludeDeleted().InLanguageBranch(ContentLanguage.PreferredCulture.Name)
                .Filter(x => x.Key.MatchCaseInsensitive(key));

            var result = queryBuilder.GetContentResult().Items.FirstOrDefault();

            return result?.Phrase ?? "";
        }

        public IEnumerable<DictionaryEntryBlock> GetAll()
        {
            var queryBuilder = this._findClient
                .Search<DictionaryEntryBlock>()
                .ExcludeDeleted();

            var result = queryBuilder.GetContentResult().Items;

            return result;
        }
    }
}

Step 3 - Registering services

Now just register this repository service to your DependencyResolverInitialization class.

  context.Services.AddHttpContextOrThreadScoped<IDictionaryRepository>(x => x.GetInstance<DictionaryRepository>());

Step 4 - And a useful extension method to make our life easier

I have also created an Html helper method to retrieve the dictionary block.

using System;
using System.Web.Mvc;
using Dictionary.Business.Repositories;
using EPiServer.ServiceLocation;

namespace Dictionary.Helpers
{
    public static class DictionaryHelper
    {
        public static string DictionaryEntry(this HtmlHelper html, string key, string fallback = "")
        {
            if (string.IsNullOrEmpty(key))
                return string.Empty;

            var _dictionaryService = ServiceLocator.Current.GetInstance<IDictionaryRepository>();

            var phrase = _dictionaryService?.GetPhrase(key)?.Trim();

            return !string.IsNullOrEmpty(phrase) ? phrase : fallback;
        }
    }
}

FYI- I am using the ServiceLocator to get the instance of IDictionaryRepository for this blog post you should use constructor initialization on your BasePage or BaseBlock

Setting up things

Now login into your Episerver instance and create a new block of type "DictionaryEntry". Name it "ReadMore". Please fill both the "Key" and "Phrase" fields. Please see below screenshot.

Once everything is in place then you can use the above helper method to retrieve the dictionary value. The first parameter is the key and second is the fallback value nothing found in CMS.

@Html.DictionaryEntry("ReadMore", "Read More")

You can grab all the code on my Github here

I hope it helps.

Thanks and regards

Ravindra

Sep 11, 2019

Comments

Mattias Olsson
Mattias Olsson Sep 11, 2019 09:19 AM

Hi!

I can also recommend using DbLocalizationProvider which solves this problem.

[LocalizedResource]
public class DictionaryEntries
{
    [TranslationForCulture("sv", "Läs mer")]
    public static string ReadMore => "Read more";
}

And and example in Razor view:

@Html.Translate(() => DictionaryEntries.ReadMore)

If you install the DbLocalizationProvider.AdminUI.EPiServer package, content editors can edit these texts for all enabled languages.

Darren Stahlhut
Darren Stahlhut Sep 12, 2019 01:39 AM

@Ravindra it's always good to see some new approaches thank you for sharing.

As @Mattias mentioned, we to use the DbLocalizationProvider and typically add a KeyPrefix to help with "Namespacing" resource strings, this helps your users find them the UI (the expandable Tree view below)

[LocalizedResource(KeyPrefix = "SiteName.Components.LanguageSelector")]
public static class LanguageSelector
{
    public static string Text => "Please select your region and language";
}

And then in View

localizationProvider.GetString(() => Resources.LanguageSelector.Text)

Here's what the Localization tab looks like






Mattias Olsson
Mattias Olsson Sep 12, 2019 06:42 AM

Yes, thanks for sharing! :)

Ravindra S. Rathore
Ravindra S. Rathore Sep 12, 2019 03:27 PM

My pleasure Darren and Mattias :)

Praful Jangid
Praful Jangid Sep 15, 2019 06:25 PM

Hi Ravindra,

Thanks for sharing your experience. 

We recently faced issue with dictionary uses in Episerver with caching. The content author update the phrase and the changes don't reflect on the site event you clear all types of cache. So we finally decided to not use them is projects and instead of that we will use property on content type.

I am just sharing my experience. 

Thanks again.

Ravindra S. Rathore
Ravindra S. Rathore Sep 16, 2019 07:36 AM

Hey Praful,

I am using this approach in two-three projects and never had this kind of issue because of it just an Epi block that I am using to store the static text and then I just pulling this from Find indexes.

It might be some issue with your Find configuration or implementation approach of caching is not up to the mark.

Please login to comment.
Latest blogs
Looking back at Optimizely in 2025

Explore Optimizely's architectural shift in 2025, which removed coordination cost through a unified execution loop. Learn how agentic Opal AI and...

Andy Blyth | Dec 17, 2025 |

Cleaning Up Content Graph Webhooks in PaaS CMS: Scheduled Job

The Problem Bit of a niche issue, but we are building a headless solution where the presentation layer is hosted on Netlify, when in a regular...

Minesh Shah (Netcel) | Dec 17, 2025

A day in the life of an Optimizely OMVP - OptiGraphExtensions v2.0: Enhanced Search Control with Language Support and Synonym Slots

Supercharge your Optimizely Graph search experience with powerful new features for multilingual sites and fine-grained search tuning. As search...

Graham Carr | Dec 16, 2025

A day in the life of an Optimizely OMVP - Optimizely Opal: Specialized Agents, Workflows, and Tools Explained

The AI landscape in digital experience platforms has shifted dramatically. At Opticon 2025, Optimizely unveiled the next evolution of Optimizely Op...

Graham Carr | Dec 16, 2025

Optimizely CMS - Learning by Doing: EP09 - Create Hero, Breadcrumb's and Integrate SEO : Demo

  Episode 9  is Live!! The latest installment of my  Learning by Doing: Build Series  on  Optimizely Episode 9 CMS 12  is now available on YouTube!...

Ratish | Dec 15, 2025 |

Building simple Opal tools for product search and content creation

Optimizely Opal tools make it easy for AI agents to call your APIs – in this post we’ll build a small ASP.NET host that exposes two of them: one fo...

Pär Wissmark | Dec 13, 2025 |