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:
- it only works for applications on the same machine, and
- 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:
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:
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:
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.
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.
- 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
.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