MediaWizards Grimoire

Sep 27, 2022 - Huw Reddick

Formatting for RichTextEditor CustomConfig sections in appsettings.json

As some of you are now aware, adding custom style formats to Umbraco 9+ is somewhat cumbersome as rather than accepting nicely formatted json it is expecting a string which requires the double quotes to be escaped and therfore makes it difficult to maintain.

I have come up with a solution to maintain it automatically. I keep a copy of the style_formats section in it's own .json file in the application root folder.

Nicely formatted json

In the startup.cs I check to see if this file exists and if it does, I load the content, minify the json and then write the minified string back to the appsetting.json. Writing the minified json using the serializer automatically escapes the double quotes in the value.

minified and escaped value

Startup.cs changes

        public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config)
        {
            _env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
            _config = config ?? throw new ArgumentNullException(nameof(config));
            UpdateStyleFormats();
        }

        private void UpdateStyleFormats()
        {
            //does the json file exist?
            //if yes, minify it, escape the " and set the value in appsettings.json
            if (File.Exists("RTE_style_formats.json"))
            {
                
                object obj = JsonConvert.DeserializeObject(File.ReadAllText("RTE_style_formats.json"));
                var jsonString = JsonConvert.SerializeObject(obj, Formatting.None);

                var settingsUpdater = new AppSettingsUpdater();
                settingsUpdater.UpdateAppSetting("Umbraco:CMS:RichTextEditor:CustomConfig:style_formats", jsonString);

            }
        }

AppSettingsUpdater Class

using System.Text;
using System.Text.Json;

namespace MediaWiz.Core.Extensions
{
    public class AppSettingsUpdater
    {
        private const string EmptyJson = "{}";
        /// <summary>
        /// This method will update the value for a given key in the json file
        /// </summary>
        /// <param name="file">Name of the json file to edit</param>
        /// <param name="key">Name of the json key</param>
        /// <param name="value">New value for the setting</param>
        /// <exception cref="ArgumentException"></exception>
        public void UpdateAppSetting(string key, object value)
        {
            if (key == null)
            {
                throw new ArgumentException("Json property key cannot be null", nameof(key));
            }

            string settinsgFileName = "appsettings.json";

            var config = File.ReadAllText(settinsgFileName);

            var updatedConfigDict = UpdateJson(key, value, config);
            // After receiving the dictionary with updated key value pair, we serialize it back into json.
            var updatedJson = JsonSerializer.Serialize(updatedConfigDict, new JsonSerializerOptions { WriteIndented = true });
            // serializing the json escaped the + signs!! so let's put them back
            File.WriteAllText(settinsgFileName, updatedJson.Replace(@"\u002B","+"),Encoding.UTF8);
        }


        /// <summary>
        /// This method will recursively read json segments separated by semicolon (firstObject:nestedObject:someProperty)
        /// until it reaches the desired property that needs to be updated,
        /// it will update the property and return json document represented by dictonary of dictionaries of dictionaries and so on.
        /// This dictionary structure can be easily serialized back into json
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="jsonSegment"></param>
        /// <returns></returns>
        private Dictionary<string, object> UpdateJson(string key, object value, string jsonSegment)
        {
            const char keySeparator = ':';

            var config = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonSegment);
            var keyParts = key.Split(keySeparator);
            var isKeyNested = keyParts.Length > 1;
            if (isKeyNested)
            {
                var firstKeyPart = keyParts[0];
                var remainingKey = string.Join(keySeparator, keyParts.Skip(1));

                // If the key does not exist already, we will create a new key and append it to the json
                var newJsonSegment = config.ContainsKey(firstKeyPart) && config[firstKeyPart] != null
                    ? config[firstKeyPart].ToString()
                    : EmptyJson;
                config[firstKeyPart] = UpdateJson(remainingKey, value, newJsonSegment);
            }
            else
            {
                config[key] = value;
            }
            return config;
        }
    }
}

In this blog post I explain how to implement an email validation flow for Member registration.

In part 2 of my Implementing a Forgot password for members I explain how to implement the IMemberMailService to send the reset password email.

How to implement a ForgotPassword process for Umbraco members in Umbraco 9+

Custom views give you complete control over how a Block is rendered in the backoffice and this enables you to give a better representation of the content. In this article I will explain how I created a custom Block view based on the fullcalendar.io javascript library to display events in the backoffice.

These are my experiences of creating an Umbraco package for the MediaWiz Forums, using package targets, razor class libraries, static web assets and template views.

Many thanks go to Kevin Jump and Luuk Peters without whose help I would probably have given up.