|
|
| White Papers Home | White Papers | Message Board | Search | Products | Purchase | News | Web Log | |
Building a better .NET
|
|
ASP.Net Security Note Note that if you’re using ASP.Net, writing to the web.config file requires that you give Write access rights to the ASPNET/NETWORK SERVICE account. If writing is not enabled the default values are not written and you have to manually enter the keys or use a link that allows impersonation to save and update settings. There’s more info on this topic in my WebLog at http://west-wind.com/weblog/posts/295.aspx . |
The example above uses a new class instance to load the WebStore Config class. While this works it’s also very inefficient and takes extra work on your part. You really don’t want to have to create an instance of this class each time you need to access one or more values. Nor do we want the class to have to retrieve and convert each of the values each time when we instantiate it.
Instead we can use a static member to store this Configuration object. By using a static we can create the Configuration object once and leave it on the static property which is always accessible to the application. Using a static constructor it’s possible to have the Configuration object load up automatically the first time that it is needed.
To do this we need to hook up our configuration object somewhere as a static member. I like to use a class called App in all of my applications. I use App rather than any of the preexisting framework classes (such as the Global class in ASP.Net applications) so that I can reuse the object in multiple applications regardless whether it is a ASP.Net or WinForm application. I usually store this class in my business object project. A simple example of the App class with the Configuration object is shown in Listing 4. Normally this class would have other ‘global’ static properties that are used in the applications, but for demonstration I only show the Configuration member.
Listing 4 – Hooking up the Configuration object to a static property with a static constructor
public class App
{
public static WebStoreConfig Configuration;
static App()
{
Configuration = new WebStoreConfig();
}
}
Notice the use of a static constructor (static App()). This constructor fires the first time you access the Configuration object. Static constructors fire exactly once for the lifetime of the application and on the first access to any object of that particular class instance which makes them very useful as loaders for properties just like our configuration object that require only a one time initialization. With this constructor in place you can now use the following syntax from anywhere in your code:
decimal TaxRate = App.configuration.TaxRate;
No object instantiation, no fuss and a cached Configuration object.
You can also set properties of the configuration object by simply assigning a value:
App.Configuration.TaxRate = 0.05M;
Keep in mind that when you’re setting a value it is only changed in memory and not automatically persisted back into the configuration file. If you want to write all changes to the configuration to disk you can call the WriteKeysToConfig() method.
App.Configuration.WriteKeysToConfig()
This method is static so you also don’t need an instance to call it. If you are running ASP.Net, calling this method will update Web.config, which has the same effect as manually editing Web.config: ASP.Net shuts down and restarts the currently executing Application (unloads and reloads the AppDomain) to reflect the changes that were made to Web.config.
This behavior of ASP.Net is very useful. If you manually edit the web.config file the same thing occurs. The app shuts down and restarts and your static constructor gets fired again when you first access the App.configuration object. This means loose nothing by using this class – we still get notified of changes in web.config even when the file is manually edited or edited by another program. Cool!
Remember that in a multi-threaded environment a static instance is shared between all threads. This means the App.configuration object is seen by all ASP.Net threads for example. You need to be careful when writing data to properties/fields. Configuration information is not likely to be in contention so you probably don’t have to loose any sleep in this case, but keep this in mind as you’re dealing with static properties in general.
You can also write the configuration information out to an alternate store instead of to a config file and its AppSettings section. By calling an alternate constructor you can prevent the class from reading values on load and instead use serialization to read and write the contents of your object. I find for WinForms applications it’s actually easier to use this mechanism because of the way app.config works (or doesn’t work <g>) in Visual Studio.
All you need to do is to create the class like this:
[Serializable]
public class WebStoreConfig : wwAppConfiguration
{
// *** Override the constructor to do nothing!
public WebStoreConfig(bool NoLoad)
{
}
… property interface here
}
Add the Serializable attribute and add a new constructor that doesn’t call back into the base class to prevent the default loading from the configuration file. To write out the configuration data you can use a method in the wwUtils class in your source code that allows XML (or binary) serialization:
wwUtils.SerializeObject(App.WebStoreConfig,FileName,false);
and deserialization like this:
App.configuration = wwUtils.DeSerializeObject(FileName,typeof(WebStoreConfig),false);
if (App.configuration == null)
App.configuration = new WebStoreConfig();
If deserialization returns null the file didn’t exist or the XML format was incorrect. In that case you need to create a new instance and use the default values.
Let’s take a look behind the covers and see how this class works. The class’ functionality is straightforward. It implements two main worker methods: ReadPropertiesFromConfiguration() and WriteKeysToConfig(). Their function isn’t hard to guess. They shuttle data between the class interface and the config file.
The Read version uses Reflection to loop through all the properties of the class and reads the values out of the ConfigurationSettings.AppSettings object. It performs conversions and checks to make sure that all keys exist in the configuration file. If they don’t a flag is set which causes the settings to be written out at the end. Listing 5 shows the code for the ReadPropertiesFromConfiguration.
Listing 5 – Reading properties from the .config file in the wwAppConfiguration class
public abstract class wwAppConfiguration
{
public wwAppConfiguration()
{
this.ReadKeysFromConfig();
}
public void ReadKeysFromConfig()
{
Type typeWebConfig = this.GetType();
MemberInfo[] Fields = typeWebConfig.GetMembers(
BindingFlags.Public | BindingFlags.Instance );
// *** If we have missing fields write them out at end
bool MissingFields = false;
foreach(MemberInfo Member in Fields)
{
string TypeName = null;
FieldInfo Field = null;
PropertyInfo Property = null;
if (Member.MemberType == MemberTypes.Field)
Field = (FieldInfo) Member;
else if (Member.MemberType == MemberTypes.Property)
Property = (PropertyInfo) Member;
else
continue; // Skip methods, events etc.
if (Field != null)
TypeName = Field.FieldType.Name.ToLower();
else
TypeName = Property.PropertyType.Name.ToLower();
string Fieldname = Member.Name.ToLower();
string Value = ConfigurationSettings.AppSettings[Fieldname];
if (Value == null)
{
MissingFields = true;
continue;
}
if (TypeName == "string")
wwUtils.SetPropertyEx(this,Fieldname,Value);
else if (TypeName.StartsWith("int") )
wwUtils.SetPropertyEx(this,Fieldname,Convert.ToInt32(Value));
else if (TypeName == "boolean")
if (Value.ToLower() == "true")
wwUtils.SetPropertyEx(this,Fieldname,true);
else
wwUtils.SetPropertyEx(this,Fieldname,false);
else if (TypeName == "datetime")
wwUtils.SetPropertyEx(this,Fieldname,Convert.ToDateTime(Value));
else if (TypeName == "decimal")
wwUtils.SetPropertyEx(this,Fieldname,Convert.ToDecimal(Value));
… More types omitted here
else if(Field != null && Field.FieldType.IsEnum)
wwUtils.SetPropertyEx(this,Fieldname,
Enum.Parse(Field.FieldType,Value) );
else if (Property != null && Property.PropertyType.IsEnum)
wwUtils.SetPropertyEx(this,Fieldname,
Enum.Parse( Property.PropertyType,Value) );
}
// *** We have to write any missing keys
if (MissingFields)
this.WriteKeysToConfig();
}
}
The constructor of the class automatically calls the ReadKeysFromConfig() method that causes the properties of the class to be loaded with the values from the configuration file. The ReadKeysFromConfig method then proceeds to use Reflection using the MemberInfo collection and the .NET ConfigurationSettings.AppSettings class to loop through each of the fields of the object and retrieve a matching value from the AppSettings NameValueCollection. Because we need to check both properties and fields we need to use both FieldInfo and PropertyInfo collections which makes the code a little more messy than it should be as there are conditional uses of either of these classes to set values.
I use the wwUtils.SetPropertyEx method to simplify the property of field setting code – this method automatically checks to see whether the property is a property or field and then uses the appropriate FieldInfo or PropertyInfo class with its appropriate Set method. The method also has logic to drill down multiple object levels using ‘.’ object syntax. You can check out this useful method and several other Reflection helpers in the supplied code in the wwUtils class.
If a value is not found in the AppSettings (null return) a flag is set that notifies our code that keys are missing and need to be written out when we’re done. If a value is found that value is read and then converted into the proper type for the field. Conversion usually involves using the Convert object except for a few types like string and bool which can be converted more directly. The last type conversion is for Enums. It checks to see if the field or property is an Enum and if it is uses the extremely handy Enum.Parse() method to turn the string value into an Enum value.
And so we loop through each field and assign its value with the matching entries from the configuration file. If there are extra keys in the configuration file those are ignored. If there are missing keys in the config file MissingFields will be set and we call WriteKeysToConfig() to actually write the current configuration out to the config file.
The WriteKeysToConfig() is used to write out values back into the configuration file. It’s used internally, but you can also call it externally to cause all of your current configuration settings to be.
The logistics of this method are a little different than the read method, primarily because .NET doesn’t provide an interface to write value back to the Config file. ConfigurationSettings.AppSettings is a read only interface. So in order to write out data we’ll have to do this the hard way by using XML directly. In the WriteKeysToConfig method shown in Listing 6 I use the XmlDocument class to read the Config file and update the structure.
Listing 6 – Writing the current configuration into the Configuration File
public void WriteKeysToConfig(string Filename)
{
// *** Load the config file into DOM parser
XmlDocument Dom = new XmlDocument();
Dom.Load(Filename);
// *** Parse through each of hte properties of the properties
Type typeWebConfig = this.GetType();
MemberInfo[] Fields = typeWebConfig.GetMembers();
foreach(MemberInfo Field in Fields)
{
// *** If we can't find the key - write it out to the document
string Value = null;
if (Field.MemberType == MemberTypes.Field)
Value = ((FieldInfo) Field).GetValue(this).ToString();
else if (Field.MemberType == MemberTypes.Property)
Value = ((PropertyInfo) Field).GetValue(this,null).ToString();
else
continue; // not a property or field
XmlNode Node = Dom.SelectSingleNode(
@"/configuration/appSettings/add[@key='" + Field.Name + "']");
if (Node == null)
{
// *** Create the node and attributes and write it
Node = Dom.CreateNode(XmlNodeType.Element,"add",null);
XmlAttribute Attr2 =Dom.CreateAttribute("key");
Attr2.Value = Field.Name;
XmlAttribute Attr = Dom.CreateAttribute("value");
Attr.Value = Value;
Node.Attributes.Append(Attr2);
Node.Attributes.Append(Attr);
XmlNode Parent = Dom.SelectSingleNode(@"/configuration/appSettings");
if (Parent == null)
{
XmlNode AppSettingsNode =
Dom.CreateNode(XmlNodeType.Element,"appSettings",null);
Parent = Dom.DocumentElement.AppendChild(AppSettingsNode);
}
Parent.AppendChild(Node);
}
else
{
// *** just write the value into the attribute
Node.Attributes.GetNamedItem("value").Value = Value;
}
string XML = Node.OuterXml;
} // for each
Dom.Save(Filename);
}
Like the read code we use the MemberInfo collection to loop through each of the fields in the object. But here we check against nodes that exist in the XML document using XPATH expressions. If a node is not found we create it including possibly the top level AppSettings node. When the update is complete the whole .config file is written back out to disk.
In addition to the internal requirement to write out the keys when they don’t exist it’s also useful to be able to do this programmatically. For example, Figure 1 shows a Web interface for some of the keys we were looking at earlier, that are in this case databound directly against the App.configuration object.

Figure 1 – Because configuration settings live in an object it’s easy to databind to the properties and bind to them in the user interface.
Any change made in the User Interface automatically updates the fields in the Configuration object and you can then simply write it out into the configuration. The btnSave code for this form is shown in Listing 7.
Listing 7 – Loading and Saving configuration settings from the front end
private void Page_Load(object sender, System.EventArgs e)
{
this.config = App.configuration;
if (!this.IsPostBack)
this.BindData(); // Controls bind to this.config
}
private void Save_Click(object sender, System.EventArgs e)
{
// *** Bind the data back to this.config
this.UnbindData();
if (this.bindingErrors != null)
{
this.lblErrorMessage.Text = this.bindingErrors.ToHtml();
return;
}
// *** Data is bound to this.config which maps to this.configuration
App.configuration.WriteKeysToConfig();
this.lblErrorMessage.Text = "Settings saved...";
}
Because we have strong types and an object instance, the databinding can occur directly against the properties of our Configuration object and data can directly be bound back. (Note: This example uses databinding classes described in a previous article: Implementing two-way Data Binding in ASP.Net). All you need to do is write out the data with WriteKeyToConfig() when you’re done. It’s that simple… the hardest part is creating the User interface.
In Windows Forms it’s even easier if you use Properties (not fields as I’ve done here in the examples so far) for your configuration settings. You can use the PropertyGrid to display your data simply by assigning your Config object to the SelectedObject of the PropertyGrid – you don’t even need to write any code. If you add Component attributes you can even automatically group and attach descriptions to your settings but remember in order for the property grid to see your properties you need to use properties:
[Description("The name for the Store displayed in headers, confirmations etc."),
Category("Company Information")]
public string StoreName
{
get { return this.cStoreName; }
set { this.cStoreName = value; }
}
string _StoreName = "West Wind Web Store";
Using a class has obvious advantages both from a usability point of view as well as from a UI point of view because you have something that you can bind to easily.
The final feature of the class that I want to demonstrate is encryption of selected keys. A lot of configuration information is esential, so you might want to protect the .config file by encrypting sensitive keys. The purpose is to prevent people from physically looking into the web.config file and gaining access information for, say, the database connection string. Instead, this information should only be available for editing through the application itself and, most likely, through password-protected access forms that require a valid log on.
To add encryption, I use a simple support class called wwEncrypt that is provided with the source for this article. This class uses two-way DES encryption to encrypt and also decrypt specific values.
In order to implement this functionality, I added two
private properties to the wwAppConfiguration base class:
private string EncryptFieldList = "";
private string EncryptKey = "";
EncryptFieldList is a comma-delimited list of fields that you want to have
encrypted. The EncryptKey is the Key used to encrypt and decrypt the fields as
they are read from the configuration and out to disk in the ReadKeysFromConfig/WriteKeysToConfig
methods.
There’s also a special Constructor and a SetEncryption()
method used to set these fields. This Constructor looks like this:
public wwAppConfiguration(string EncryptFields,
string EncryptKey)
{
this.SetEncryption(this.EncryptFieldList,
EncryptKey);
this.ReadKeysFromConfig();
}
If you want more control you can also create the object and call the SetEncryption() method directly
WebStoreConfig Config = new WebStoreConfig(true);
Config.SetEncryption("ConnectionString,MailPassword",
"SuperSecret");
Config.SetConfigurationSection("MyApplication");
this.ReadKeysFromConfig();
Note the constructor call with a Boolean value parameter which stops the default
loading of configuration settings until you explicitly call ReadKeysFromConfig().
Both Read- and WriteKeysToConfig methods then add a couple of lines of code to
handle the encryption and decryption as part of the Member loops that go through
each of the properties. Here’s the relevant Read code, which fires immediately
after you retrieve the value from the AppSettings:
if (Value != "" && this.EncryptFieldList.IndexOf("," +
Fieldname + ",") > -1 )
Value = wwEncrypt.DecryptString(Value,this.EncryptKey);
Here, you decrypt the string and store the decrypted value in the object. On the
Write end, the process is reversed. Immediately after reading the value from the
property and converting it to a string, it is encrypted:
if (this.EncryptFieldList.IndexOf("," +
Field.Name.ToLower() + ",") > -1)
Value = wwEncrypt.EncryptString(Value,this.EncryptKey);
With the right routines in place, this process is really easy. You can find the
EncryptString and DecryptString methods in the wwEncrypt class in the source.
In order for you to enable this encryption for your own
subclass, all you have to do is implement a custom Constructor that calls back
to the base class and passes the EncryptFields and the EncryptKey.
public WebStoreConfig() : base(false)
{
this.SetEncryption("ConnectionString,MailServerPassword","SuperSecret");
this.ReaadKeysFromConfig();
}
Of course, you can also just call the two-parameter Constructor directly, but
generally, I prefer to have this done all in one place and forget about it.
A lot of talk about something that’s pretty simple to use in the end, but I hope you find this configuration class useful – I know I do. As so many small things, a reusable tool like this makes life a lot easier especially when the next project rolls around and you get to reuse this functionality again.
As always if you have any questions or comments you can contact me on our message board at:
http://www.west-wind.com/wwThreads/Default.asp?Forum=White+Papers
Enhanced and updated version of this class as part f the West Wind Web Toolkit:
http://www.west-wind.com/WestwindWebWebToolkit/
By Rick Strahl
Rick Strahl is president
of West Wind Technologies on Maui, Hawaii. The company specializes in Web
and distributed application development and tools with focus on .NET, Visual Studio and Visual FoxPro. Rick is author of
West Wind Web Connection, a powerful and widely used Web application
framework and
West Wind HTML Help Builder and
West Wind Web Store. He's also a
C# MVP,
a frequent speaker at international developer conferences and a frequent contributor to magazines and books. He is co-publisher of Code
magazine. For more information
please visit:
http://www.west-wind.com/ or contact Rick at
rstrahl@west-wind.com.
Abstract Classes
Abstract classes are classes that cannot be instantiated – they are meant to always be inherited from. Instead they are usually used as base classes that provide core worker functionality, but require specialization at a higher level in the class hierarchy. In this article the specialization of the subclass is in the form of new class members that are to be persisted and retrieved.
Abstract classes can also include abstract members – these are properties, fields and methods that must be implemented at the subclass level. In this respect Abstract classes are similar to Interfaces, but Interfaces are not classes and their sole purpose is to enforce a contract that a class that implements the interface must implement the interface methods and properties. Abstract classes provide both a contract (via abstract members) as well as an implementation for any non abstract methods,fields and properties implemented.
Static Members and Static Constructors
Static properties and fields are extremely useful in providing a semblance of ‘global’ variables in an application. Although globals should be avoided as much as possible, for some operations that need to be cached or actively be shared across multiple threads static members are very useful. A mostly read access based configuration object as demonstrated in this article is a great candidate.
Static constructors are special constructors that fire the first time any static member of a specific class is accessed. This makes static constructors very useful because they fire exactly once during the lifetime of an application, including accesses from different threads. This makes them perfect for initializing the object or reading data for the first time. Be careful though – static constructors behave differently than non-static constructors as they are not inheritable and cannot be called from an inherited class. A static constructor is pinned to a specific class level and only applies against members of that particular class.
Because statics are global you have to be careful in their use in multi-threaded environments such as ASP.Net where multiple threads might simultaneously access data in static objects. Reading data is safe generally, but whenever you write data you should make sure that the data is protected in the proper synchronization code if the data is indeed accessed in such.
Reflection
Reflection is .NET’s type discovery and access mechanism and it’s the key to discovering the properties and fields of your subclassed configuration object in this article. Reflection allows you to ‘evaluate’ string based property, field and method names on an object. This means you can discover a type at runtime and extract or set a value or execute a method without requiring the compiler to have to know the signature of this class. It’s essentially dynamic code execution.
Reflection can be quite unwieldy though, especially if your objects accessed are complex and multiple levels deep. This article’s source code includes a helper class (wwUtils) that greatly simplifies common Reflection tasks such as retrieving and setting properties and fields and drilling down multiple levels in an object’s internal hierarchy.
Configuration Settings get a boost in Whidbey
Some of the features I’ve implemented in the wwAppConfiguration class will be provided in ASP.Net 2.0. Encryption and read/write access to configuration settings will be available through the ConfigurationSettings object. However, the interface still remains essentially non-typed which is probably its biggest shortcoming. With ASP.Net 2.0 the code to create this class would get significantly simpler since no XML access would be required. In addition, ASP.Net 2.0 also provides a much improved Configuration class that lets you more easily change settings inside of web.config programmatically without having to write out XML.
| White Papers Home | White Papers | Message Board | Search | Products | Purchase | News | |