Let's PoC C#: Customize Web.config Settings in Web Forms Application

Marcelo F NunesMarcelo F Nunes
8 min read

Sometimes you'll need a more flexible custom settings structure than the common key/value pair in your XML configuration file.

I recently faced the challenge of implementing new features in a legacy Web Forms project. For my task, I needed to have some URLs organized in collections to be used within the code. After some research, I was able to implement the necessary flexible custom settings in Web.config file along with the needed collections. Then I decided to write this article for further reference. To demonstrate the subject discussed here, I'll be using Microsoft Visual Studio 2022, version 17.1.4 and the PoC (Proof of Concept) project for this review will be based on the ASP.NET Web Application (.NET Framework) template. You can find the source code here.

The necessary basic structure we must implement for this approach to work is composed of an interaction among six user-created classes:

  1. Base element class: the C# object type of a single settings item

  2. Configuration settings class: represents each settings item

  3. XML collection's element class: bound to collection's element node in Web.config file

  4. XML collection class: bound to proper custom settings collection node

  5. XML section class: bound to section node that is one level above collection's level

  6. Mapping properties class: injects the values from the elements of the collection node in Web.config file into the properties of the configuration settings class (item 2)

PoC'n Roll, baby!

Open the PoC project. You're gonna need it to follow this tutorial. Observe the custom settings in Web.config file. There is one collection named "alternativeSocialMedia" nested in another outer collection named "usefulUrls". We can find four elements within the inner collection. We could have more collections inside usefulUrls if we wanted to, but that is not the case here. The properties name, value and level contain the values we want to read along our project's code and again we can have as many properties as we want.

It's important to notice that for every set of custom settings in Web.config file that we create, it's mandatory to refer to them inside the configSections section. These tags are situated within the configuration section. This configSections node has two basic attributes which are name and type, the latter being the full C# class name that will be the projection of our custom section in C# code.

In this case, the full C# class name is:

CustomWebConfig.Models.CustomConfiguration.UsefulUrls

1. Base Element Class

Firstly, we'll create the C# class that will be a projection of the lowest level of our custom configuration settings node: the element. It'll contain the three basic properties that we created for each node: name, value and level. By the way, "level" means nothing and it's there only for demonstration purposes. You can insert as many attributes as you wish for each element.

Below is the code for the C# class that represents a single element in our set of custom configuration:

public class CustomConfigBasicNode
{
    public string Name  { get; private set; }
    public string Value { get; private set; }
    public int Level    { get; private set; }

    public CustomConfigBasicNode(string name, string value, int level)
    {
        Name = name;
        Value = value;
        Level = level;
    }
}

2. Configuration Settings Class

This class will be the one we'll use along the project to get the values of the custom settings. Because of that, every time you add or remove an element to/from the custom collection, you'll need to propagate this change into this class. Thus, as in our example there are four custom items in the collection, we'll have to create a class with four properties, so that the custom settings in Web.config file can be properly mapped to the properties of the C# class. So let's see what is the code for the class with the four properties to hold the four settings' items of the alternativeSocialMedia collection.

namespace CustomWebConfig.Models.CustomConfiguration
{
    public class AlternativeSocialMedia
    {
        public CustomConfigBasicNode IConnectFx { get; set; }
        public CustomConfigBasicNode DTube { get; set; }
        public CustomConfigBasicNode Gab { get; set; }
        public CustomConfigBasicNode Rumble { get; set; }
    }
}

3. XML Elements Class

For the next 3 classes, they will express the exact structure that is found in the configuration file. It'll be necessary to extend C# native classes that are abstractions to read the values in Web.config file and transport them into the C# user created classes. It envolves inheritance, specific syntaxes and an hierarchical relationship among the native C# classes. About this hierarchical structure we can say that sections contain collections which, in turn, contain elements. No worries. Sounds complicated but it's not. Keep reading, and you'll see.

The next class AlternativeSocialMediaElement will hold the properties and values for the set of elements. Pay attention to the attribute decoration statements over the class properties. They are important to map the properties from the Web.config file to the correct property within the C# class. Also notice that this class has to inherit from the built-in C# class ConfigurationElement.

using System.Configuration;

namespace CustomWebConfig.Models.CustomConfiguration
{
    public class AlternativeSocialMediaElement : ConfigurationElement
    {
        [ConfigurationProperty("name", DefaultValue = "Localhost")]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("value", DefaultValue = "http://127.0.0.1")]
        [RegexStringValidator(@"\w+:\/\/[\w.]+\S*")]
        public string Value
        {
            get { return (string)this["value"]; }
            set { this["value"] = value; }
        }

        [ConfigurationProperty("level", DefaultValue = 1)]
        [IntegerValidator(ExcludeRange = false, MinValue = 1, MaxValue = 10)]
        public int Level
        {
            get { return (int)this["level"]; }
            set { this["level"] = value; }
        }
    }
}

4. XML Collection Class

As we already have the XML element mapping classes implemented, let's go for the classes that will map the collection of these elements. Observe the AlternativeSocialMediaElementCollection class below, and notice how it inherits from ConfigurationElementCollection and is decorated with the AlternativeSocialMediaElement class that represents a single element in the collection. It is also mandatory to implement the two overridden methods:

using System.Configuration;

namespace CustomWebConfig.Models.CustomConfiguration
{
    [ConfigurationCollection(typeof(AlternativeSocialMediaElement))]
    public class AlternativeSocialMediaElementCollection : ConfigurationElementCollection
    {
        public AlternativeSocialMediaElement this[int index]
        {
            get
            {
                return (AlternativeSocialMediaElement)BaseGet(index);
            }
            set
            {
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);

                BaseAdd(index, value);
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new AlternativeSocialMediaElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((AlternativeSocialMediaElement)element).Name;
        }
    }
}

5. XML Section Class

Let's move forward. We're almost there! This next class UsefulUrlsSection will represent the section level of our XML configuration file structure. Observe the importance of the attribute decoration over the single property of this class. It's used to inform C# which node should be linked to which class property:

using System.Configuration;

namespace CustomWebConfig.Models.CustomConfiguration
{
  // Extend the ConfigurationSection class.  Your class
  // name should match your section name and be postfixed with "Section"
  public class UsefullUrlsSection : ConfigurationSection
  {
    //Decorate the property with the XML tag for your collection
    [ConfigurationProperty("alternativeSocialMedia")]
    public AlternativeSocialMediaElementCollection AlternativeSocialMedia
    {
      get { return (AlternativeSocialMediaElementCollection)this["alternativeSocialMedia"]; }
    }
  }
}

6. Mapping Properties Class

Now it's time to implement the class that will contain the methods that will read the Web.config and map all the custom nodes to the properties of their respective C# classes that we've created so far. Let's create the class and give it the same name as our custom XML configuration section using Pascal Case string format, that is UsefulUrls:

using System.Configuration;
using System.Reflection;

namespace CustomWebConfig.Models.CustomConfiguration
{
    public static class UsefulUrls
    {
        public static AlternativeSocialMedia AlternativeSocialMediaConfigurationObject { get; set; }
        private static UsefulUrlsSection _usefullUrlsSection = ConfigurationManager.GetSection("usefulUrls") as UsefulUrlsSection;

        public static void SetAlternativeSocialMediaConfiguration()
        {
            var alternativeSocialMedia = _usefullUrlsSection.AlternativeSocialMedia;

            if (AlternativeSocialMediaConfigurationObject == null)
            {
                AlternativeSocialMediaConfigurationObject = new AlternativeSocialMedia();

                for (int i = 0; i < alternativeSocialMedia.Count; i++)
                {
                    PropertyInfo alternativeSocialMediaCurrentProperty =
                        AlternativeSocialMediaConfigurationObject
                            .GetType()
                            .GetProperty(alternativeSocialMedia[i].Name);

                    if (alternativeSocialMediaCurrentProperty != null)
                    {
                        var alternativeSocialMediaCurrentObject =
                            new CustomConfigBasicNode(
                                alternativeSocialMedia[i].Name,
                                alternativeSocialMedia[i].Value,
                                alternativeSocialMedia[i].Level);

                        alternativeSocialMediaCurrentProperty.SetValue(
                            AlternativeSocialMedia,
                            alternativeSocialMediaCurrentObject,
                            null);
                    }
                }
            }
        }
    }
}

Let's take a closer look at what's going on here. Firstly we have a static property named AlternativeSocialMediaConfigurationObject, which means it will be available to the whole project. Next, we can see the GetSection method from ConfigurationManager class being invoked to read the content of Web.config file and bind its data right into a variable called _usefullUrlsSection. Because of all attribute annotations that we've decorated the related classes before, C# is now able to map each of the custom section tags from Web.config to their respective properties within the related classes.

After that, there is a method called SetAlternativeSocialMediaConfiguration. It uses some reflection and reads the contents of the structured C# configuration classes and writes their contents to the AlternativeSocialMedia type object created in step 2. The reason why we are using reflection to transfer the contents of the already populated classes is that we want to use the configuration settings by their names and not by their indexed numbers.

As soon as you invoke SetAlternativeSocialMediaConfiguration method, the static property AlternativeSocialMediaConfigurationObject will have all the custom settings we wanted. In our PoC example, it is achieved through Global.asax.cs file in its Application_Start method.

Now that we have everything properly configured, we can use the custom settings values along with our application. Default.aspx.cs file has an example of how to access the properties of our custom configuration object.

Summary

Firstly we saw how Web.config file should be properly structured with the custom configuration section being referenced in configSections node. Then, we created the two initial classes that were responsible for representing each custom settings item in the config file. Next, we created the three classes that should receive the values from Web.config and keep them as indexed itens in a collection. Finally, we created the method that was responsible for extracting the indexed values from the collection and writing them into the initial two created classes that would allow us to access the custom values by their property names.


Bibliography

Sanner, Eric. “4 Easy Steps to Custom Sections in Web.Config” Perficient Blogs, 5 Jan. 2017, https://blogs.perficient.com/2017/01/05/4-easy-steps-to-custom-sections-in-web-config/

Microsoft Learn. “ConfigurationElement Class (System.Configuration)” ConfigurationElement Class. Microsoft Learn. https://learn.microsoft.com/en-us/dotnet/api/system.configuration.configurationelement?view=windowsdesktop-7.0.

0
Subscribe to my newsletter

Read articles from Marcelo F Nunes directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Marcelo F Nunes
Marcelo F Nunes

Software developer for .NET technologies. Passionate about programming since his teenage, learning the fundamentals through old things like BASIC, ASSEMBLY and VISUAL BASIC, evolving to the most recent technologies in the art of creating software solutions.