Introduction
In a web application, the most common requirement for storing configuration is
like this:
-
You have a static singleton class (e.g.
MyConfiguration
) which stored
the configuration data. It has properties like "DatabaseConnectionString
" to
store configuration settings
-
You need to store it in a XML file.
-
You do not want to store it in web.config because whenever you change the
values, your web application restarts
-
You want to programmatically modify the configuration from some administrative
page and make it effective throughout the application immediately
If you have this requirement, Microsoft Configuration Management Application
Block (MCMAB) provides the solution according
to Microsoft (pbui)
in "Patterns & Practices" section. MCMAB has a XMLFileStorage
class which allows you to do this. However, you will be surprised because:
It does not work at all!
Microsoft Configuration Management Application Block's XMLFileStorage
class has
some problem with external XML file's path handling. It cannot read nor write
into external XML files when you use it from a web application. However, for
desktop application, it works smoothly. This article shows you a work-around
how to solve this problem and use external XML files to store configurations in
web applications.
Problem 1. Does not load XML file
You will be surprised to know, without modifying the code of MCMAB, you cannot
use its XMLFileStorage to read/write configuration in external XML file.
According to the documentation, the configuration should be like this in web.config
<configuration>
<configsections>
<section type="Microsoft.ApplicationBlocks.ConfigurationManagement.
ConfigurationManagerSectionHandler,Microsoft.ApplicationBlocks.
ConfigurationManagement, Version=1.0.0.0,Culture=neutral,
PublicKeyToken=null" name="applicationConfigurationManagement" />
</CONFIGSECTIONS>
<applicationconfigurationmanagement defaultSection="MyConfigSection">
<configsection name="MyConfigSection">
<configprovider type="Microsoft.ApplicationBlocks.
ConfigurationManagement.Storage.XmlFileStorage" refreshOnChange="true"
encrypted="false" signed="false" path="MyConfig.xml"
assembly="Microsoft.ApplicationBlocks.ConfigurationManagement,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" />
</CONFIGSECTION>
</APPLICATIONCONFIGURATIONMANAGEMENT>
The above configuration file configures the following:
-
An external XML file for storing configuration
-
XML File name is "MyConfig.xml" which should be in the virtual directory folder
where the web.config file is.
-
The configuration should be reloaded as the file is changed without restarting
the web application
This is extremely helpful as you change the configuration file, your web
application is not restarted. However it does not work
Why?
In the XMLFileStorage
class, you will find the following code
in the Init
method:
if( _applicationDocumentPath == null )
_applicationDocumentPath =
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
else
_applicationDocumentPath = Path.GetFullPath(
_applicationDocumentPath );
Q. In a webapp what do you think "Path.GetFullPath
" is going to return for
relative paths?
A. Definitely not the location "c:\inetpub\wwwroot\mysweetapp" but like
"C:\windows\system32\..."
Solution
Solution is to modify those lines like this:
if( _applicationDocumentPath == null )
{
_applicationDocumentPath =
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
}
else if ( _applicationDocumentPath.IndexOf( "{base.dir}" ) > -1 )
{
_applicationDocumentPath = _applicationDocumentPath.Replace(
"{base.dir}", AppDomain.CurrentDomain.BaseDirectory );
}
else
{
_applicationDocumentPath = Path.GetFullPath(
_applicationDocumentPath );
}
Source:
http://www.gotdotnet.com/community/workspaces/BugDetails.aspx?bugid=9524
Now it works! However, you have to modify the section in web.config
as follows:
<applicationconfigurationmanagement defaultSection="MyConfigSection">
<configsection name="MyConfigSection">
<configprovider
type="Microsoft.ApplicationBlocks.ConfigurationManagement.
Storage.XmlFileStorage" refreshOnChange="true" encrypted="false"
signed="false" path="<B>{base.dir}</b>MyConfig.xml"
assembly="Microsoft.ApplicationBlocks.ConfigurationManagement,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" />
</CONFIGSECTION>
</APPLICATIONCONFIGURATIONMANAGEMENT>
Now it really works!
Problem 2: Cannot write configuration
But you cannot invoke "ConfigurationManager.Write
", it does not work.
Why?
The default behavior of the section handler implementation is, it tries to look
for a "<section>" node having "name" equal to the name of the section you
are trying to save.
For example, for the following line:
ConfigurationManager.Write( "MyConfigSection",
Configurations.Instance );
It will look for a section declaration under the "configSections" in web.config.
Solution
You have to declare a section like this in web.config:
<section type="Microsoft.ApplicationBlocks.ConfigurationManagement.
XmlSerializerSectionHandler,Microsoft.ApplicationBlocks.
ConfigurationManagement,Version=1.0.0.0,
Culture=neutral,PublicKeyToken=null" name="MyConfigSection" />
Now it works!
Problem 3: Not MCMAB, my problem, cannot use singleton
I cannot use singleton for storing the configuration because in order to
serialize/deserialize any class, there must be a public default constructor.
How do we write singleton classes? Isn't it like this:
public class Configurations
{
private static raedonly Configurations _Instance =
new Configurations();
private Configurations()
{
}
public static Configurations Instance
{
get
{
return _Instance;
}
}
There goes out Design Patterns. I had to do this, don't blame me:
public class Configurations
{
private static Configurations _Instance = new Configurations();
public Configurations()
{
}
public static Configurations Instance
{
get
{
if( null == _Instance )
{
_Instance = new Configurations();
}
return _Instance;
}
set
{
lock( _Instance )
{
_Instance = value;
}
}
}
public int CurrentSemesterID;
public string CurrentSemesterTitle;
Oh by the way, in Application_Start
I'm doing this:
Configurations.Instance = (Configurations)
ConfigurationManager.Items["MyConfigSection"];
An idiot's guide to MCMAB
I admit, I was an idiot. I had to spend an hour fixing things up in order to
make MCMAB work for my web app.
Here's a step-by-step guide that you can follow to create a configuration class
which is persisted in an external XML file.
Step 1
Change the code for Init
method in XmlFileStorage
class as explained above. Build the binaries. Copy the latest binaries to the
"bin" folder of the web app.
Step 2
Create a web.config like this:
="1.0" ="utf-8"
<configuration>
<configsections>
<section type="Microsoft.ApplicationBlocks.
ConfigurationManagement.ConfigurationManagerSectionHandler,
Microsoft.ApplicationBlocks.ConfigurationManagement,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"
name="applicationConfigurationManagement" />
<section type="Microsoft.ApplicationBlocks.
ConfigurationManagement.XmlSerializerSectionHandler,
Microsoft.ApplicationBlocks.ConfigurationManagement,Version=1.0.0.0,
Culture=neutral,PublicKeyToken=null" name="MyConfigSection" />
</configsections>
<applicationconfigurationmanagement defaultSection="MyConfigSection">
<configsection name="MyConfigSection">
<configprovider type="Microsoft.ApplicationBlocks.
ConfigurationManagement.Storage.XmlFileStorage" refreshOnChange="true"
encrypted="false" signed="false" path="{base.dir}Config.xml"
assembly="Microsoft.ApplicationBlocks.ConfigurationManagement,
Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" />
</configsection>
</applicationconfigurationmanagement>
<system.web>
...
</system.web>
</configuration>
Step 3
Create a blank XML file named "MyConfig.xml" in the folder where web.config is:
<configuration>
<myconfigsection>
</myconfigsection>
</configuration>
That's all.
Please use MCMAB. Besides this tiny problem, it's extremely powerful. Do not
come up with your own implementation. Whatever you will do in your lifetime,
all of them are already done in MCMAB. Thanks.