Introduction

In the context of .NET applications, settings are data that are not the main input or output of a program, but are nonetheless necessary for the program to function. Like business data, settings can change. An example of a setting is a folder path or a Web Service URI. Settings are often stored in application configuration files (app.config or web.config) to allow an administrator to easily change their values without refactoring and recompiling. This solution mostly works well for a single application. However, on occasions, the need arises to reuse settings across multiple applications. How to accomplish this with minimal code is the subject of this article.

Background

In an earlier article, I demonstrated how settings stored in a machine.config file can be exposed to multiple applications via strongly typed properties of a settings wrapper class. While simple and effective, this approach has two limitations:

  1. it only works for applications on the same machine, and
  2. settings cannot be updated at runtime.

To overcome these limitations, we need a new game plan.

Game Plan

Our approach will involve creating a custom settings provider that will read, and if needed, write to our custom data store. The data store can be an XML file, a relational database, or just about anything else. For the demo, I chose what's likely to be the most common approach - store the settings in a database. So, first we will create a database table and seed it with some data. Then, we will use Visual Studio to quickly generate a class with properties to match our settings. To allow the settings class to communicate with the data store, we will then create a new provider and instruct the settings class to use it. In this demo, the provider class will use LINQ to SQL to perform database reads and updates, but you could as well use any data access technology: Entity Framework, raw ADO.NET, a third party ORM, or whatever else suits your purpose. To emphasize the point, this approach hinges not on specific data access technology or a specific backend choice, but rather on wiring the settings class to use a custom settings provider, which we will do in steps 5-7.

Step-by-Step Guide

1. Create the Data store

As mentioned above, my data store will be a database table. In designing the schema for storing settings, we are faced with several decisions. Do we store all data in a single column as varchar, or do we store data in different fields or even different tables depending on type? I like several things about the schema that I've chosen for this demo. Take a look at the Settings table below:

schema.jpg

For simplicity, I am storing the name value pairs in one table. If you are bent on normalization, you may just as easily split them into separate tables. The interesting part here is the data type of the Value field. As you see, I am using sql_variant. I chose sql_variant because, unlike varchar, it preserves information about the underlying type (bit, int, date, etc.) without the need to create a field or table for every data type you might end up using. This is one of the niceties of SQL Server. If you like it, use it. Otherwise, or if you are using an RDBMS that does doesn't have the sql_variant data type, choose an alternative that works.

I've also added a Description field and an Enabled field to turn a setting on or off as needed.

After you have your table, populate it with some settings. Here are mine:

table.jpg

2. Create a New Class Library

If you already have a library that serves as a core reference for other projects in your solution, you can use it and skip this step. If not, add a new Class Library project to your solution. In my sample, this project is named NetRudder, which also serves as the root namespace for other projects.

3. Add References

Add the following assembly references to the library created in the previous step:

  • System.Configuration
  • System.Data.Linq

4. Add a Data Access Layer

If your solution already contains a Data Access Layer, you can use it instead. For the demo, I simply added a new LINQ to SQL data context to my NetRudder project. I then created a Setting class mapped to the Settings table that we created earlier. With LINQ to SQL, this is as easy as dragging out the table from Server Explorer to the designer surface. The new class looks like this:

Setting_entity.jpg

Notice an interesting detail: the type of the Value property is Object - this is the default LINQ to SQL mapping for sql_variant, and it works just fine for our purposes.

L2S_props.jpg

5. Create a Settings class

To take advantage of the .NET Application Settings architecture, we need to create a class that inherits from ApplicationSettingsBase. If you followed my previous post, you already have a settings class. If not, you can use Visual Studio Settings Designer to quickly create one:

  • Add a new Settings template to your project. I named mine NetrudderSettings.
  • Open the new .settings file. The Settings Designer comes up.
  • settings1.jpg

  • Enter the same settings you entered in the table in the first step. Make sure to match the names exactly. For each setting, choose the correct type, and change Scope from User to Application. The values are not critical now - these serve as defaults if the setting is not found in the database.
  • Change Access Modifier to Public, so we can reference the generated class outside the assembly.
  • Save the project, but do not close the Designer yet.

At this point, Visual Studio has generated a wrapper class for us with properties named identically to our settings.

6. Wire the Settings Class to Use a Custom Settings Provider

Let's recap for a moment. We now have the following pieces in place:

  • A Settings table seeded with some data
  • A LINQ to SQL Setting entity mapped to the Settings table
  • A settings class (NetrudderSettings) that exposes individual settings as properties

What's missing is a class that will mediate between the Data Access Layer and the settings class. So, let's create it.

In the Settings Designer, click "View Code". As you can see, NetrudderSettings is a partial class. If you want to add properties manually instead of using the Settings Designer, you could do so here. This is particularly useful if you need your properties to be writeable (see note below). For now, we just need to decorate our class with the following attribute:

<SettingsProvider(GetType(LinqSettingsProvider))> _
Partial Public NotInheritable Class NetrudderSettings

What we just did by adding the attribute is instruct the settings class to use a custom provider that we are going to write. Without this attribute, NetrudderSettings would use LocalFileSettingsProvider, which is the default provider class that knows how to "talk to".NET configuration files (machine.config, app.config, web.config) but not to databases or anything else.

We have not yet created a LinqSettingsProvider class, so you will see a squiggly underline under the class name. Putting the cursor over the name to bring up the error correction menu, choose to have Visual Studio create the class for you. (If you don't get this option because you are using VS Express, just create the class manually.)

Making Settings Writeable

By default, when you use the Settings Designer to add settings, the properties that Visual Studio creates to expose these settings are marked ReadOnly. If some of your settings need to be writable, you will have to create these properties manually. No big deal. Using the Settings Designer, remove any settings that you want to be writeable. Open the partial class that you've modified in this step, and add the properties that you want to be writeable following the pattern below:

<Global.System.Configuration.ApplicationScopedSettingAttribute()> _
Public Property Year() As Integer
    Get
        Return CType(Me("Year"), Integer)
    End Get
    Set(ByVal value As Integer)
        Me("Year") = value
    End Set
End Property

7. Make the Custom Settings Provider Do Something

The settings provider is the only part of our solution that actually communicates directly with the Data Access Layer. It's up to us to instruct it on how to do it. First, make sure that your provider class inherits from SettingsProvider. At this point, you should be looking at a stub that looks similar to this:

Public Class LinqSettingsProvider
    Inherits SettingsProvider

    Public Overrides Property ApplicationName() As String
        Get

        End Get
        Set(ByVal value As String)

        End Set
    End Property

    Public Overrides Function GetPropertyValues(ByVal context As _
           System.Configuration.SettingsContext, ByVal collection _
           As System.Configuration.SettingsPropertyCollection) _
           As System.Configuration.SettingsPropertyValueCollection

    End Function

    Public Overrides Sub SetPropertyValues(ByVal context As _
           System.Configuration.SettingsContext, ByVal collection _
           As System.Configuration.SettingsPropertyValueCollection)

    End Sub
End Class

Add the following line to the Initialize method:

MyBase.Initialize(Me.ApplicationName, col)

Add the following code in the Getter of ApplicationName property (leave the Setter empty):

ApplicationName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name

Add code to the GetPropertyValues method that reads values from the data store and into a SettingsPropertyValueCollection. To conserve space, I won't paste the code here, but you can see it all in the included demo. Of course, if you didn't use LINQ to SQL, the part of the code that interacts with the Data Access Layer will look somewhat different.

Add code to the SetPropertyValues method that takes the updated settings and persists them to the database (see the attached project).

8. Add the "Shortcut" Module

We are almost done. This last step creates a nice shortcut to a synchronized instance of our settings class, making it easier to work with. Paste the following code into a new module, substituting your root namespace for Netrudder:

<Global.Microsoft.VisualBasic.HideModuleNameAttribute()> _
Public Module PublicSettingsProperty
    Public ReadOnly Property Settings() As Netrudder.My.MySettings
        Get
            Return Netrudder.My.MySettings.Default
        End Get
    End Property
End Module

Compile the project. Done!

Accessing Settings

To test the settings, I created a new console project and referenced the NetRudder assembly. You can do the same basic test with a different application type. The following code reads my settings from the database and outputs them to the console. It also updates the Year property, which I made writeable earlier. Notice that to persist the update to the data store, you need to call the Save method of the settings class, which it inherits from ApplicationSettingsBase.

With Netrudder.Settings
    Console.WriteLine("Copyright {0} {1}", .Year, .Copyright)
    Console.WriteLine("Lat/Lng: {0},{1}", .Lattitude, .Longitude)

    .Year = 2009
    'Persist the settings change
    .Save()
End With

Combining Approaches

Just because you have a custom settings provider doesn't mean you can't also store some settings in a .config file as before. Remember how in step 6 we instructed our settings class to use our custom settings provider? Well, you can override this behavior for individual properties. So, if you have a setting whose value may be unique to each application, you can set the provider for that property to LocalFileSettingsProvider and store the value in app.config/web.config. Check out the SettingsProvider link to MSDN below to read more about mixing and matching providers.

Further Reading

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"