Keep your .config clean with external config files

June 16, 2008 at 1:25 PMAndre Loker

The web.config (or app.config for non-web applications) file is the central place to configure your web application, starting from connection strings, over application settings to ASP.NET specific topics like caching, authentication & authorization, sessions as well as HTTP handlers and modules. Normally the web.config starts as a neat little pet you can easily manage. But as soon as your project grows mature, web.config turns out to be a huge beast. The size itself is often not that much of a problem. An issue I personally find more important is the fact that I need my web.config files to be different for my developer's machine and the deployment scenarios: on my dev machine I certainly have different connection strings than on the production server, the same counts for app settings, logging, compilation settings etc. pp.

Maintaining multiple configuration files is a PITA, especially when it comes to the configuration parts that have to be similar on all config files (e.g. http handlers and modules will most likely be the same).

Luckily the .NET configuration system has a feature that drastically lightens the burden for us. Each configuration section may define an attribute named "configSource" to define an alternate location from where the configuration has to be loaded. Let's have a look at an example.

In web.config locate or create the connectionStrings section. Remove all child elements (<add>, <remove> or <clear>) and add a new attribute named configSource. Give it the name of a file called "ConnectionStrings.config" as its value:

   1: <connectionStrings configSource="ConnectionStrings.config"/>

Add a new file called "ConnectionStrings.config" to the same directory where web.config lives and define its content as follows:

   1: <?xml version="1.0"?>
   2: <connectionStrings>
   3:   <add name="MyDB" connectionString="Data Source=myServerAddress;Initial Catalog=MyDataBase;Integrated Security=SSPI;"/>
   4: </connectionStrings>

That's it. Now the content of ConnectionStrings.config will be used for the connectionStrings section.

Some things to note:

  • The external configuration file must contain the section's name as the root element, i.e. an external config file for the <smtp> element must have the <smtp> node as it's root even though the element is nested within system.net/mailSettings in the original web.config.
  • If you use configSource you must not define any other attribute or child element for the respective section in web.config. This means that configSource can never be used to add additional content to a section. It will always replace the section.
  • It might be useful to move configuration files to a subfolder to keep the root directory clean. I personally use a subfolder named Config. The configSource attributes have to include the folder in that case, like configSource="Config\connectionStrings.config"
  • The actual file extension of the external configuration files does not matter (as long as you point to the correct file in the configSource attribute). Keep in mind, though, that depending on the extension the web server might or might not serve the literal content of the config files when requested. If the config files contain sensitive information (DB passwords etc.) this can be a massive security risk. .config is one of the extensions IIS/ASP.NET will refuse serve to the client. Therefore I recommend to always give configuration files the extension ".config".

Multiple deployment scenarios

Now that we are able to move configuration settings to an external source file the web.config becomes easier to manage: if for example we need to have two different sets of connection strings (or app settings etc.) for different deployment scenarios we can define multiple external configuration files, each one for a different scenario, like: connectionStrings-dev.config for my dev machine and connectionStrings-deploy.config for the production server. All I have to do to switch between these two is to modify the configSource in the web.config. (BTW: the web deployment projects can do this automatically for you, just check the "Enable Web.config file section replacement" checkbox under the "Deployment" tab and define the new file names; see Extreme ASP.NET: Web Deployment Projects under "Pluggable Configuration Files").

Hiding sensitive information

If you're working on an open source project that uses a shared code repository you may not want specific information to appear in the repository, like database connection strings, smtp credentials. If you move those settings to external configuration files it is easy to exclude them from your repository.

Non-standard config sections

ConfigSource is a feature of the .NET configuration system and not specific to the ASP.NET configuration sections. Therefore it should work with other config sections as well.

Recommendations

Personally I recommend to move the following sections to external configuration files:

  • appSettings - likely to change for different deployment scenarios
  • connectionStrings - likely to change for different deployment scenarios
  • smtp in system.net/mailSettings - for security reasons and because it's likely to change for different deployment scenarios
  • machineKey in system.web - primarily for security reasons
  • External libraries like log4net, Castle Windsor, NHibernate etc

Posted in: ASP.NET

Tags: , ,

New blogroll member: Stefan Lieser

June 12, 2008 at 12:39 PMAndre Loker

Have a look at Stefan's blog. He's into TDD, DDD, ASP.NET MVC, improving code quality and other Good Things™, so it's definitively worth reading. Most posts are in German, though. He's developing a ReSharper plugin for NHibernate, be sure to have a look at it!

Posted in: Meta | Off topic

Tags:

Getting rid of strings (2): use lambda expressions

June 12, 2008 at 12:08 PMAndre Loker

Intro

In the first article of this series I talked about the problems with strings in code. This article will show you how you can use lambda expressions and expression trees as another tool to avoid strings.

About Lambda Expressions

C# 3.0 brought a cool new feature call lambda expressions. On the one hand they are a nice abbreviation for anonymous delegates:

   1: Button b = /*..*/
   2: b.Click += (sender, e) => MessageBox(String.Format("{0} clicked", sender);

But there's an additional feature that might not be obvious to everyone. .NET 3.5 introduced the System.Linq.Expressions namespace which allows us to inspect a code expression tree. One special expression type is Expression<TDelegate> which derives from LambdaExpression. This expression type handles the expression tree represented by a lambda expression. Let's look at an example:

   1: public class Program {
   2:  
   3:   public static void ExpressionTest(Expression<Func<DateTime>> expression) {
   4:     Console.WriteLine("Expression body is '{0}'", expression.Body);
   5:     Console.WriteLine("Node type is {0}", expression.Body.NodeType);
   6:     Console.WriteLine("Expression body type is {0}", expression.Body.GetType());
   7:   }
   8:  
   9:   public static void Main(string[] args) {
  10:     ExpressionTest(() => DateTime.Now);
  11:   }
  12: }

ExpressionTest expects an expression representing a Func<DateTime>, that is a function that returns a DateTime. In the Main method ExpressionTest is invoked - not with an Expression<Func<DateTime>> but simply with a lambda expression with the Func<DateTime> signature. The C# 3.0 compiler will convert the lambda expression that is passed as argument to a expression tree with a top node of type Expression<Func<DateTime>>. The Body property of that expression contains the right hand side of the lambda expression.

Running this code prints:

   1: Expression body is 'DateTime.Now'
   2: Node type is MemberAccess
   3: Expression body type is System.Linq.Expressions.MemberExpression

The runtime type of the expression body is MemberExpression, which makes sense, because DateTime.Now represents access to a member (Now) of DateTime. Run the code in the debugger to see how the expression is represented in the expression tree.

I won't go into too much detail on expressions here. Browse through the documentation of the Expressions namespace to see what kind of expressions you can expect (and inspect for that matter).

How can lambda expressions help to avoid strings?

Expressions can be useful in situations where you need to provide the name of a member or a MethodInfo/PropertyInfo/FIeldInfo for that member. Ever needed to pass a MethodInfo somewhere? You'll most likely ended up with something like

   1: var info = typeof (DateTime).GetMethod("ToShortDateSting");
   2: Console.WriteLine(info.Name);

This compiles fine, of course, but when you run the code you'll get a null pointer exception at info.Name. Why? Because there's a typo in "ToShortDateSting". The method I was looking for is ToShortDateString (realize the 'r' in String). Did you see the typo at a glance? The situation gets worse if the name of the method changes, because now the code would break at runtime without being changed (see the first article to learn about problems with strings and refactoring).

Captain Lambda to the rescue

Here's an approach that is much more solid:

   1: var info = Reflect.GetMethod<DateTime>(dt => dt.ToShortDateString());
   2: Console.WriteLine(info.Name);

No strings attached so to speak. If you had a typo in ToShortDateString the code would not even compile. Additionally, the code is much more open to refactoring. But wait, how does it work? Here's the simple answer:

   1: public static class Reflect {
   2:   /// <summary>
   3:   /// Gets the MethodInfo for the method that is called in the provided <paramref name="expression"/>
   4:   /// </summary>
   5:   /// <typeparam name="TClass">The type of the class.</typeparam>
   6:   /// <param name="expression">The expression.</param>
   7:   /// <returns>Method info</returns>
   8:   /// <exception cref="ArgumentException">The provided expression is not a method call</exception>
   9:   public static MethodInfo GetMethod<TClass>(Expression<Action<TClass>> expression) {
  10:     var methodCall = expression.Body as MethodCallExpression;
  11:     if(methodCall == null) {
  12:       throw new ArgumentException("Expected method call");
  13:     }
  14:     return methodCall.Method;
  15:   }
  16: }

GetMethod expects an expression with a delegate of type Action<TClass>, that is a void method having a TClass as it's only argument. GetMethod checks that the expression passed in is a method call by casting the body to a MethodCallExpression. If the cast succeeds, the Method property already contains the MethodInfo that we were looking for. Thank you C# 3.0 compiler for doing the work for us :-)

A more practical example

In MonoRail the Controller class has a method called RedirectAction which - well - redirects the response to a new action. It expects the name of an action as its argument. So you might see code like this:

   1: public class HomeController : Controller {
   2:   
   3:   public void Index() {
   4:     if(!UserIsLoggedIn){
   5:         RedirectToAction("Login");
   6:     }
   7:   }
   8:  
   9:   public void Login() {
  10:   }
  11: }

This works fine but of course it suffers from all the string related problems I have been talking about so far. Let's see if we can use our new friend (Expression<TDelegate>) to improve the situation:

   1: public static class ControllerExtensions {
   2:   public static void RedirectToAction<TController>(this TController controller, Expression<Action<TController>> expression) where TController : Controller {
   3:     var methodCall = expression.Body as MethodCallExpression;
   4:     if (methodCall == null) {
   5:       throw new ArgumentException("Expected method call");
   6:     }
   7:     controller.RedirectToAction(methodCall.Method.Name);
   8:   }
   9: }

Now we have an extension method that we can use instead of the original RedirectToAction:

   1: public class HomeController : SmartDispatcherController {
   2:  
   3:   public void Index() {
   4:     // RedirectToAction("Login");
   5:     this.RedirectToAction(c => c.Login());
   6:   }
   7:  
   8:   public void Login(){
   9:   }
  10: }

Is this cool or what? Once again we got rid of a string. You can redirect to a method with parameters as well:

   1: public void Index() {
   2:   this.RedirectToAction(c => c.ShowItem(0));
   3: }
   4:  
   5: public void ShowItem(int id){
   6: }

You can pass any value to the "call" to ShowItem that you like. Remember: the expression is only examined but not executed. If you want to pass an actual value to the redirected action, create extension methods for the RedirectToAction overloads that accept parameters. I won't show this here because it is not too hard to implement (and I'm only showing some examples here anyway).

Properties

You can also get a PropertyInfo (and similarly a FieldInfo) using Lambdas, here's an example:

   1: public static class Reflect {
   2:   public static PropertyInfo GetProperty<TClass, TValue>(Expression<Func<TClass, TValue>> expression) {
   3:     var memberExpression = expression.Body as MemberExpression;
   4:     if (memberExpression == null || !(memberExpression.Member is PropertyInfo)) {
   5:       throw new ArgumentException("Expected property expression");
   6:     }
   7:     return (PropertyInfo) memberExpression.Member;
   8:   }    
   9: }
  10:  
  11: // use:
  12: var dayProperty = Reflect.GetProperty<DateTime, int>(dt => dt.Day);
  13: Console.WriteLine(dayProperty.Name);

NB: the code shown above only works for properties that are not write-only (otherwise you will not be able to "return" the property value in the expression). I don't consider this a big limitations. How many write-only properties have you written in the past two months?

In the example above we have to provide both the type of the class as well as the type of the property. It makes the code slightly less elegant. We can improve it like this:

   1: public static PropertyInfo GetProperty<TClass>(Expression<Func<TClass, object>> expression) {
   2:     MemberExpression memberExpression;
   3:     // if the return value had to be cast to object, the body will be an UnaryExpression
   4:     var unary = expression.Body as UnaryExpression;
   5:     if (unary != null) {
   6:       // the operand is the "real" property access
   7:       memberExpression = unary.Operand as MemberExpression;
   8:     } else {
   9:       // in case the property is of type object the body itself is the correct expression
  10:       memberExpression = expression.Body as MemberExpression;
  11:     }
  12:     // as before:
  13:     if (memberExpression == null || !(memberExpression.Member is PropertyInfo)) {
  14:       throw new ArgumentException("Expected property expression");
  15:     }
  16:     return (PropertyInfo) memberExpression.Member;
  17: }
  18:  
  19: var dayProperty = Reflect.GetProperty<DateTime>(dt => dt.Day);
  20: Console.WriteLine(dayProperty.Name);
  21:  

It's a bit more complicated, but still understandable.

Update 07/22/2008: RednaxelaFX came up with a third alternative. GetProperty stays the same as in the first (simpler) version, but we call the method differently:

   1: // use:
   2: var dayProperty = Reflect.GetProperty( (DateTime dt) => dt.Day);
   3: Console.WriteLine(dayProperty.Name);

By providing the type of the expression's argument explicitly the compiler is now able to infer the return value of the expression. Thanks RednaxelaFX for your comment!

Summing it up

Lambda expression trees are a great to tool that allows us to point to members without using strings. There are some limitations, though:

  • It only works for "compile time reflection". The expression tree is created during compilation, so you cannot get the name of "some member" of "some type" at runtime.
  • It won't work with static members
  • It will only work for members that are visible in the context where the expression is built. Non-public fields, properties and methods can therefore be tricky using this technique.

Still there are plenty of situations where you need to provide the MemberInfo of a public instance method/property (or it's name) known at compile time.

Posted in: C# | Patterns

Tags: , , ,

Castle DictionaryAdapter

June 10, 2008 at 3:02 PMAndre Loker

DictionaryAdapter is a component of the Castle stack which can create an adapter between an interface and a dictionary at runtime. The DictionaryAdapter combines the flexibility of a dictionary with the type safety and convenience provided by a strong API. Here's an example:

   1: // the interface that will be used to access the content of the dictionary
   2: public interface ISimple {
   3:   string Name { get; set; }
   4:   int Age { get; set; }
   5: }
   6:  
   7: //...
   8:  
   9: // the backing store
  10: IDictionary dict = new Hashtable(); 
  11:  
  12: // dynamically create an adapter around dict 
  13: ISimple wrapper = new DictionaryAdapterFactory().GetAdapter<ISimple>(dict);
  14: wrapper.Name = "Andre"; // will be written to dict["Name"];
  15: wrapper.Age = 26;       // will be written to dict["Age"];
  16:  
  17: Assert.AreEqual("Andre", dict["Name"]);
  18: Assert.AreEqual("Andre", wrapper.Name);
  19:  
  20: Assert.AreEqual(26, dict["Age"]);
  21: Assert.AreEqual(26, wrapper.Age);

How to get the DictionaryAdapter

The DictionaryAdapter can be downloaded as part of the Castle project:

How does it work?

Calling DictionaryAdapterFactory.GetAdapter will create a new type on the fly. This type implements the interface that was provided as the type argument (here: ISimple). For each property on the interface a getter and/or setter will be implemented that reads a value from the provided dictionary or writes a value to the dictionary. The key that is used is based on the property name (the default setting is to use the property name as the key, later we will see how we can influence the key being used).

Creating an adapter is rather costly. It requires the creation of a new in-memory assembly containing the dynamic adapter type. However, this performance only exists the first time GetAdapter is called for a specific type. Subsequent requests for the same adapter type will reuse the generated assembly.

More examples and options

DictionaryAdapter has plenty of options to customize how the mapping between the adapter interface and the underlying dictionary happens. In this section I'll give some examples of what DictionaryAdapter is capable of without covering every detail (for a complete reference I'd recommend the Castle source code).

I will provide code examples in the form of an MbUnit test with the following skeleton:

   1: using System.Collections;
   2: using MbUnit.Framework;
   3: using Castle.Components.DictionaryAdapter;
   4:  
   5: [TestFixture]
   6: public class DictionaryAdapterTests {
   7:   private DictionaryAdapterFactory factory;
   8:   private IDictionary dict;
   9:  
  10:   [SetUp]
  11:   public void SetUp() {
  12:     factory = new DictionaryAdapterFactory();
  13:     dict = new Hashtable();
  14:   }
  15: }

The IDictionary instance is used as the backing store for which an adapter will created. The factory handles the dynamic creation of the adapters.

Download the source code of the examples (DictionaryAdapterTests.cs)

Basic example

Ones again, here's the most basic example:

   1: public interface ISimple {
   2:   string Name { get; set; }
   3:   int Age { get; set; }
   4: }
   5:  
   6: [Test]
   7: public void SimpleTest() {
   8:   var wrapper = factory.GetAdapter<ISimple>(dict);
   9:   wrapper.Name = "Andre";
  10:   wrapper.Age = 26;
  11:  
  12:   Assert.AreEqual("Andre", dict["Name"]);
  13:   Assert.AreEqual(26, dict["Age"]);
  14: }

Type prefix

Especially if you want to use multiple adapters on the same dictionary naming collisions can occur if a property with the same name is present in more than one adapter interface. To avoid this problem you can specify key prefixes using attributes. One possible prefix is the (full) name of the interface type:

   1: [DictionaryTypeKeyPrefix]
   2: public interface IWithTypePrefix {
   3:   string Name { get; set; }
   4:   int Age { get; set; }
   5: }
   6:  
   7: [Test]
   8: public void WithTypePrefix() {
   9:   var wrapper = factory.GetAdapter<IWithTypePrefix>(dict);
  10:   wrapper.Name = "Andre";
  11:   wrapper.Age = 26;
  12:  
  13:   Assert.AreEqual("Andre", dict[typeof (IWithTypePrefix).FullName + "#Name"]);
  14:   Assert.AreEqual(26, dict[typeof (IWithTypePrefix).FullName + "#Age"]);
  15: }

Custom prefix

The type prefix can be quite lengthy, so you might prefer setting your own prefix:

   1: [DictionaryKeyPrefix("My")]
   2: public interface IWithCustomPrefix {
   3:   string Name { get; set; }
   4:   int Age { get; set; }
   5: }
   6:  
   7: [Test]
   8: public void WithCustomPrefix() {
   9:   var wrapper = factory.GetAdapter<IWithCustomPrefix>(dict);
  10:   wrapper.Name = "Andre";
  11:   wrapper.Age = 26;
  12:  
  13:   Assert.AreEqual("Andre", dict["MyName"]);
  14:   Assert.AreEqual(26, dict["MyAge"]);
  15: }

Custom keys

You can opt for not using a prefix altogether and define the keys manually:

   1: public interface ICustomKeys {
   2:   [DictionaryKey("Foo")]
   3:   string Name { get; set; }
   4:  
   5:   [DictionaryKey("Bar")]
   6:   int Age { get; set; }
   7: }
   8:  
   9: [Test]
  10: public void WithCustomKey() {
  11:   var wrapper = factory.GetAdapter<ICustomKeys>(dict);
  12:   wrapper.Name = "Andre";
  13:   wrapper.Age = 26;
  14:  
  15:   Assert.AreEqual("Andre", dict["Foo"]);
  16:   Assert.AreEqual(26, dict["Bar"]);
  17: }

Convert property to string

In some scenarios you might need all values in the dictionary to be strings. This can be enforced by using the DictionaryStringValuesAttribute:

   1: public interface IPropertyAsString {
   2:   string Name { get; set; }
   3:  
   4:   [DictionaryStringValues]
   5:   int Age { get; set; }
   6: }
   7:  
   8: [Test]
   9: public void ConvertPropertyToString() {
  10:   var wrapper = factory.GetAdapter<IPropertyAsString>(dict);
  11:   wrapper.Name = "Andre";
  12:   wrapper.Age = 26;
  13:  
  14:   Assert.AreEqual("Andre", dict["Name"]);
  15:   Assert.AreEqual("26", dict["Age"]);
  16: }

This attribute can be applied to the interface itself as well to force all properties to be converted to strings.

Collection as string list

If your property implements IEnumerable you can have it converted to a string containing a list of all values:

   1: public interface IAsStringList {
   2:   [DictionaryStringList]
   3:   int[] Values { get; set; }
   4: }
   5:  
   6: [Test]
   7: public void ConvertPropertyToStringList() {
   8:   var wrapper = factory.GetAdapter<IAsStringList>(dict);
   9:   wrapper.Values = new[] {1, 2, 3, 4, 5};
  10:  
  11:   Assert.AreEqual("1,2,3,4,5", dict["Values"]);
  12: }

Several aspects are configurable, like the separator character.

Adapting complex properties

You are not limited to using "flat" interfaces with only simple types. You can also create an adapter that automatically adapts complex properties:

   1: public interface IWithComponent {
   2:   [DictionaryComponent]
   3:   ISimple Simple { get; }
   4: }
   5:  
   6: [Test]
   7: public void CanUseComponent() {
   8:   var wrapper = factory.GetAdapter<IWithComponent>(dict);
   9:   wrapper.Simple.Name = "Andre";
  10:   wrapper.Simple.Age = 26;
  11:   Assert.AreEqual("Andre", dict["Simple_Name"]);
  12:   Assert.AreEqual(26, dict["Simple_Age"]);
  13: }

As you can see IWithComponent.Simple is automatically populated with an adapter itself.

Conclusion

DictionaryAdapter provides means to access the content of a dictionary in an elegant, type safe way. It is a great tool to get rid of strings and to to make your source code robust and refactorable.

Pro

  • Easy to use
  • Very flexible
  • Type safe access even to IDictionary
  • Great tool to improve robustness of code

Cons

  • One time overhead (creating adapters)
  • Memory overhead (in-memory assemblies created by the factory)

Download source code: DictionaryAdapterTests.cs (2.97 kb)

Posted in: Patterns | Snippets | Tools

Tags: , ,

Getting rid of strings (1): meet the villain

June 10, 2008 at 1:30 PMAndre Loker

Intro

This series covers the potential problems that arise from the use of string literals in source code. Avoiding string literals can make your source code more robust, more manageable and less fragile regarding refactoring. The articles in this series will show several solutions on how to replace string literals with smarter constructs.

The problem: meaningful strings

Strings in source code can be a tricky thing. How much depends on the content and the context. A "Hello, world!" that is spit out somewhere is not that much of a problem. The meaningful strings are the problematic ones. I consider a string meaningful if it contains a named entity in the source code (types, members etc.) or a named resource (configuration settings, embedded resources, file names).  Here are two examples of meaningful strings that can be tricky:

   1: string setting = ConfigurationManager.AppSettings["UserName"];
   2: MethodInfo info = typeof (Foo).GetMethod("Execute");

What's the deal with them? Generally, strings like these cause trouble if

  1. the string is used multiple times in the code
  2. the string literal has to be changed

In the first example the string "UserName" refers the key of an application setting, found in the <appSettings> element in the application/web configuration file. If you were to change the key for whatever reason you would have to hunt down all occurrences of "UserName" and replace them. While practically all modern IDE's and text editors provide a search and replace function over multiple files, blindly replacing all occurrences of "UserName" might also override instances of "UserName" that are in no way related to the configuration setting. For example "UserName" could be used as a filter criterion in an NHibernate query:

   1: public Account GetAccountByUserName(string userName){
   2:   ISession session = //.. get an NHibernate session
   3:   return session.CreateCriteria(typeof(Account))
   4:     .Add(Expression.Eq("UserName", userName))
   5:     .UniqueResult<Account>();
   6: }

Changing "UserName" to anything else will most likely cause an error at runtime.

In the second example "Execute" references the name of a method of some fictional type Foo. Refactoring tools are widely available these days, so renaming Execute to something different is done quickly. Changing the method name will brake the code, again at runtime. While decent refactoring tools will try to find the symbol being renamed in strings, relying on this feature suffers from the same problem as the one above.

How to handle strings: basic rules

The basic rules of using meaningful string literals in source code:

  1. Avoid them. If possible, simply don't use string literals which are meaningful and suffer from the problems described above.
  2. If the previous is not possible, at least remove all duplicate occurrences of the same string literal and replace them with a named symbol. Just like 'magic numbers', replace all string literals with the same meaning by a string constant with a descriptive name. For the first example above, one could introduce a static class containing the settings keys (see below)
  3. Hide the use of string literals and/or string constants behind an API. (see below)

For the first example (the app settings key), rule 2 could be applied like this:

   1: public class AppSettingsKeys {
   2:   public const string UserName = "UserName";
   3: }
   4: //...
   5: var userName = ConfigurationManager.AppSettings[AppSettingsKeys.UserName];

But maybe applying rule 2 and 3 is even better:

   1: public static class AppSettings {
   2:   const string UserNameKey = "UserName";
   3:  
   4:   public static string UserName {
   5:     get{ return Get(UserNameKey);}
   6:   }
   7:  
   8:   static string Get(string key) {
   9:     return ConfigurationManager.AppSettings[key];
  10:   }
  11:  
  12: }
  13: //...
  14: var userName = AppSettings.UserName;

The fact that we are dealing with strings to access the app settings is nicely hidden behind the API provided by AppSettings. (Note: a subsequent article of this series will show a much better way to access app settings).

In both examples if the key of the "UserName" app setting changed, only one string had to be updated (AppSettingsKeys.UserName or AppSettings.UserNameKey). The name of the string constant could be left alone or it could be easily renamed using refactoring tools.

Conclusion

This article gives a fairly basic overview of the problems that can occur when dealing with meaningful string literals in source code. The next parts of this series will cover more sophisticated techniques to get rid of strings.

Posted in: C# | Patterns

Tags: , ,