• Home
  • Tutorials
  • Development Tools
  • Contact Us

Developing Software

Mastering Software Craftsmanship

How to Store Application Settings in ASP.NET MVC Using Entity Framework

25th June 2014 by @developingsoft

In this tutorial, we will create a strongly typed Settings API that uses Lazy loading, Cache, Reflection and Entity Framework to manage application settings in ASP.NET MVC. I will explain why its better to store settings in a database, how using reflection can make it easier to add new settings, and how to group the settings into a wrapper that uses Lazy loading and cache.

The Settings API in Action

Before we start building the Settings API, lets take a look of what it looks like in action:

public SettingsController(ISettings settings)
{
    // example of saving 
    _settings.General.SiteName = "Talk Sharp";
    _settings.Seo.HomeMetaTitle = "Talk Sharp";
    _settings.Seo.HomeMetaKeywords = "ASP.NET MVC";
    _settings.Seo.HomeMetaDescription = "Welcome to Talk Sharp";
    _settings.Save();
}

Notice there are two properties in the code above named General and Seo. These are read only properties that contain custom settings which are derived from a SettingsBase type. There are two reasons for doing this which are:

  • So that settings are grouped together and easier to find when accessing them.
  • So that only the settings in the group being accessed are loaded from the database (this is done by using lazy loaded properties).

Why Store Settings in a Database?

You might be thinking. Why store the settings in a database when you can use the Web.config file and the .NET ConfigurationManager. The reason is because changes to the Web.config file will restart the application. There’s also the added issue of making sure Web.config files are kept in-sync when your application runs on multiple servers.

To avoid these issues, it’s best to store settings that need to be modified at runtime inside a database. In this article we will use the Entity Framework ORM to handle the storage and retrieval of the settings.

How to Create the Settings API?

There are quite a few steps involved so I have put together an example project you can download.
Here is an overview of what’s to come:

  • Create the ASP.NET Web Application
  • Create the Setting model and the Entity Framework DbContext
  • Create the SettingsBase class for loading and saving settings with reflection
  • Create the GeneralSettings and SeoSettings classes and make them inherit SettingsBase
  • Create the Settings class (A simple entry point to manage all types)

1. Create Web Application

To get started we need to create a new ASP.NET Web Application and then add Entity Framework to the project using NuGet. This example is using Visual Studio 2013 with .NET Framework 4.5.1 and no authentication. Once you have added Entity Framework you will need to add a connection string to the Web.config file.

<connectionStrings>
    <add name="MvcSettings" connectionString="Server=(localdb)\v11.0;Initial Catalog=MvcSettings;AttachDbFileName=|DataDirectory|\MvcSettings.mdf;Trusted_Connection=True" providerName="System.Data.SqlClient" />
</connectionStrings>

2. Entity Framework Model

The first thing we need to do is create a model that Entity Framework can use to store each setting. Create a new class called Setting and add it to the Models folder.

public class Setting
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Value { get; set; }
}

The Setting model is very simple, it contains three public properties that will be used to store each settings name, value and type it belongs to. The type is the name of each class which inherits the SettingsBase class. We will see how this works in step 5. The following screenshot shows how the settings for this example will look.

MVC App Settings Entity Framework Model
Figure 1: The generated settings table with multiple saved settings.

3. Create UnitOfWork (DbContext)

Create a new folder called Services at the root of the project and add a new class called UnitOfWork. Here’s what the UnitOfWork class should look like.

public interface IUnitOfWork
{
    DbSet<Setting> Settings { get; set; }
    int SaveChanges();
}

public class UnitOfWork : DbContext, IUnitOfWork
{
    public UnitOfWork() : base("MvcSettings") { }
    public DbSet<Setting> Settings { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Setting>()
                    .HasKey(x => new { x.Name, x.Type });

        modelBuilder.Entity<Setting>()
                    .Property(x => x.Value)
                    .IsOptional();

        base.OnModelCreating(modelBuilder);
    }
}

There’s not much going on here, we are overriding the OnModelCreating method so that we can specify a composite key of Name and Type. We are also making the Value field optional, and we are implementing an IUnitOfWork interface that contains a call to SaveChanges. Notice the string passed into the base constructor matches the name of the connection string inside the Web.config file.

4. Create SettingsBase

The SettingsBase class is what contains the reflection magic. Whenever we need to create a new group of settings, we do so by inheriting this class. Create a new class called SettingsBase and add it to the Services folder.

public abstract class SettingsBase
{
    // 1 name and properties cached in readonly fields
    private readonly string _name;
    private readonly PropertyInfo[] _properties;

    public SettingsBase()
    {
        var type = this.GetType();
        _name = type.Name;
        // 2
        _properties = type.GetProperties();
    }

    public virtual void Load(IUnitOfWork unitOfWork)
    {
        // ARGUMENT CHECKING SKIPPED FOR BREVITY
        // 3 get settings for this type name
        var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

        foreach (var propertyInfo in _properties)
        {
            // get the setting from the settings list
            var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
            if (setting != null)
            {
                // 4 assign the setting values to the properties in the type inheriting this class
                propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
            }
        }
    }

    public virtual void Save(IUnitOfWork unitOfWork)
    {
        // 5 load existing settings for this type
        var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

        foreach (var propertyInfo in _properties)
        {
            object propertyValue = propertyInfo.GetValue(this, null);
            string value = (propertyValue == null) ? null : propertyValue.ToString();

            var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
            if (setting != null)
            {
                // 6 update existing value
                setting.Value = value;
            }
            else
            {
                // 7 create new setting
                var newSetting = new Setting()
                {
                    Name = propertyInfo.Name,
                    Type = _name,
                    Value = value,
                };
                unitOfWork.Settings.Add(newSetting);
            }
        }
    }
}

The class name and array of property info is stored in readonly instance fields (1). The reason for this is because the call to GetProperties (2) is a slow operation so we don’t want to do it more than once. The Load method takes an IUnitOfWork instance as a parameter and is used to load the group of settings in the derived type from the database (3). Each property that exists in the derived type is assigned a value from the database and is converted to the appropriate type (4).

The Save method also takes an IUnitOfWork instance as a parameter and is used to save the group of settings in the derived type to the database. This is done by first loading the existing settings belonging to the derived type (5) then looping through the properties array and either updating the existing setting (6) or adding a new one (7).

The reason for passing the UnitOfWork into both methods rather than the constructor is because the UnitOfWork might need to be disposed between calls (cached objects).

5. GeneralSettings and SeoSettings

Create a new class called GeneralSettings and a new class called SeoSettings.

public class GeneralSettings : SettingsBase
{
    public string SiteName { get; set; }
    public string AdminEmail { get; set; }
}

public class SeoSettings : SettingsBase
{
    public string HomeMetaTitle { get; set; }
    public string HomeMetaDescription { get; set; }
}

The GeneralSettings and SeoSettings classes both inherit the SettingsBase class and therefore contain Load and Save methods. You can add as many properties to these classes as you like and they will automatically be populated from the database when the Load method is called.

6. Settings

The Settings class is used to group all the settings types together into a wrapper. It provides the entry point to the Settings API. Create a new class called Settings with the following code.

public interface ISettings
{
    GeneralSettings General { get; }
    SeoSettings Seo { get; }
    void Save();
}

public class Settings : ISettings
{
    // 1
    private readonly Lazy<GeneralSettings> _generalSettings;
    // 2
    public GeneralSettings General { get { return _generalSettings.Value; } }

    private readonly Lazy<SeoSettings> _seoSettings;
    public SeoSettings Seo { get { return _seoSettings.Value; } }

    private readonly IUnitOfWork _unitOfWork;
    public Settings(IUnitOfWork unitOfWork)
    {
        // ARGUMENT CHECKING SKIPPED FOR BREVITY
        _unitOfWork = unitOfWork;
        // 3
        _generalSettings = new Lazy<GeneralSettings>(CreateSettings<GeneralSettings>);
        _seoSettings = new Lazy<SeoSettings>(CreateSettings<SeoSettings>);
    }

    public void Save()
    {
        // only save changes to settings that have been loaded
        if (_generalSettings.IsValueCreated)
            _generalSettings.Value.Save(_unitOfWork);

        if (_seoSettings.IsValueCreated)
            _seoSettings.Value.Save(_unitOfWork);

        _unitOfWork.SaveChanges();
    }
    // 4
    private T CreateSettings<T>() where T : SettingsBase, new()
    {
        var settings = new T();
        settings.Load(_unitOfWork);
        return settings;
    }
}

First of all the interface defines what Settings types will be available and a Save method to update the settings. Each Settings class that inherits SettingsBase is stored in a readonly field using Lazy<T> (1) so that the settings class is only constructed when the property is first accessed (2).

The readonly fields are assigned a new Lazy<T> instance in the constructor (3) and passed the CreateSettings<T> factory method. This factory method is called when the property is first accessed. The CreateSettings<T> method uses generics and type constraints to make sure only types that inherit SettingsBase can be constructed.

You can find a more detailed explanation of generics and type constraints in the book C# In Depth by Jon Skeet.

Where’s the Cache?

The Settings class is usable in it’s current state but you may wish to add caching so that the application does not slow down due to reflection. If you want to add cache to the Settings class you will need to add a new constructor and a new factory method that first checks the cache before creating the settings objects.

private readonly ICache _cache;
public Settings(IUnitOfWork unitOfWork, ICache cache)
{
    // ARGUMENT CHECKING SKIPPED FOR BREVITY
    _unitOfWork = unitOfWork;
    _cache = cache;
    _generalSettings = new Lazy<GeneralSettings>(CreateSettingsWithCache<GeneralSettings>);
    _seoSettings = new Lazy<SeoSettings>(CreateSettingsWithCache<SeoSettings>);
}

private T CreateSettingsWithCache<T>() where T : SettingsBase, new()
{
    // this is where you would implement loading from ICache
    throw new NotImplementedException();
}

This article is pretty long already so I’ve skipped out the impementation details of loading the settings types from cache. I will be writing an article on implementing ICache with an Autofac module in the future.

Example Usage

The following example shows how to use the Settings API by creating a new action in the HomeController:

public ActionResult Save()
{
    using (var uow = new UnitOfWork())
    {
        var settings = new Settings(uow);
        settings.General.SiteName = "Talk Sharp";
        settings.Seo.HomeMetaDescription = "Welcome to Talk Sharp";
        settings.Save();

            var settings2 = new Settings(uow, null); 
            string output = string.Format("SiteName: {0} HomeMetaDescription: {1}",
                                            settings2.General.SiteName,
                                            settings2.Seo.HomeMetaDescription
                                            );
        return Content(output);
    }
}

Now if you run the application and browse to /home/save you should see the following output:

SiteName: Talk Sharp HomeMetaDescription: Welcome to Talk Sharp

You should also see the settings saved in the Settings table as they appear in Figure 1.

Final Thoughts

It’s been a lengthy process creating this Settings API for managing settings. In my opinion its worth the initial effort because adding new settings is very easy. The settings classes can be expanded by adding new properties because reflection takes care of mapping them to the settings records in the database. I think this is better than the alternative of writing out the retrieval and conversion for each property manually.

Share this on:

Filed Under: Tutorials Tagged With: ASP.NET MVC, C#, Entity Framework

Search

Advertisement

Newsletter

Subscribe now to receive practical tips on how to become a better software developer.

Free - No Spam - 100% Email Privacy

Featured Posts

Abstract Factory Pattern: C# Example Using the Unity Game Engine

23 Software Design Patterns That Will Make You a More Effective Programmer

How to Deploy an ASP.NET Core Website to Ubuntu with Git

How to Run an ASP.NET Core Website in Production on Ubuntu Linux

How to Install the Edimax Wireless nano USB Adapter on Windows IoT Core for Raspberry Pi

How to Convert a Post Title into a Friendly URL (Slug) in C#

How to Convert Markdown to HTML in ASP.NET Core

How to Send an E-Mail with ASP.NET Core and Mailgun

How to Generate a Sitemap in ASP.NET MVC and ASP.NET Core

How to Create an MD5 Hash of a String in C# and Displaying a Gravatar Image

© 2014–2023 Developing SoftwareTerms • Privacy