CHAPTER 9

image

Configuration

The first service that the ASP.NET platform provides to applications is configuration. This may not seem like the most exciting topic, but the configuration feature is rich and flexible and helps avoid one of the pitfalls of complex software development: hard-coding behaviors into software components. As you will learn, the ASP.NET support for configuration can be adapted to suit all kinds of web applications and is worth taking the time to understand. Table 9-1 summarizes this chapter.

Table 9-1. Chapter Summary

Problem

Solution

Listing

Define simple configuration values.

Use application settings.

13

Read application settings.

Use the WebConfigurationManager.AppSettings property.

46

Define settings for connecting to remote services, such as databases.

Use connection strings.

7

Read connection strings.

Use the WebConfigurationManager.ConnectionStrings property.

8

Group related settings.

Define a configuration section.

912

Read configuration sections.

Use the GetWebApplicationSection method defined by the WebConfigurationManager class.

13, 19

Group a collection of similar settings together.

Define a collection configuration section.

1418

Group sections together.

Define a collection section group.

2021

Read section groups.

Use the OpenWebConfiguration method defined by the WebConfigurationManager class.

22

Override configuration settings.

Use the location element or create a folder-level configuration file.

2327

Read the ASP.NET settings.

Use the handler classes in the System.Configuration namespace.

28

Preparing the Example Project

For this chapter I created a new project called ConfigFiles, following the same approach I used for earlier example applications. I used the Visual Studio ASP.NET Web Application template, selected the Empty option, and added the core MVC references. I’ll be using Bootstrap again in this chapter, so enter the following command into the Package Manager Console:

Install-Package -version 3.0.3 bootstrap

I added a Home controller to the Controllers folder, the definition of which you can see in Listing 9-1. Throughout this chapter, I’ll be displaying lists of configuration properties and their values, so the data that the Index action passes to the View method is a dictionary. So that I can test the application, I have defined some placeholder data in the controller.

Listing 9-1.  The Contents of the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            configData.Add("Placeholder Property", "Placeholder Value");
            return View(configData);
        }
    }
}

I created a view by right-clicking the Index action method in the code editor and selecting Add View from the pop-up menu. I called the view Index.cshtml, selected the Empty (without model) template, and unchecked all of the view option boxes. You can see the content I defined in the view in Listing 9-2.

Listing 9-2.  The Contents of the Index.cshtml File

@model Dictionary<string, string>
@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="∼/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="∼/Content/bootstrap-theme.min.css" rel="stylesheet" />
    <style> body { padding-top: 10px; } </style>
    <title>Configuration</title>
</head>
<body class="container">
    <div class="panel panel-primary">
        <div class="panel-heading">Configuration Data</div>
        <table class="table table-striped">
            <thead>
                <tr><th>Property</th><th>Value</th></tr>
            </thead>
            <tbody>
                @foreach (string key in Model.Keys) {
                    <tr><td>@key</td><td>@Model[key]</td></tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>

Start the application and navigate to the root URL or /Home/Index to see how the view is rendered, as shown in Figure 9-1.

9781430265412_Fig09-01.jpg

Figure 9-1. Testing the example application

ASP.NET Configuration

Aside from a few standard additions to configure a database or enable debugging, most MVC framework developers don’t give any thought to the Web.config file, which is a shame because it provides a useful set of features that configure ASP.NET and that can be consumed within application code. In the sections that follow, I describe how the Web.config file forms part of a larger configuration system and explain how you can use this system to control the behavior of your applications. Table 9-2 puts the ASP.NET configuration system into context.

Table 9-2. Putting the ASP.NET Configuration System in Context

Question

Answer

What is it?

It’s a flexible set of classes and files that control the behavior of the ASP.NET foundation and services, the MVC framework, and your web application code.

Why should I care?

Using the built-in configuration support is much easier than writing your own settings code, and there are some excellent features that ease application development.

How is it used by the MVC framework?

The MVC framework uses the configuration system extensively. The most visible use to the programmer is the web.config file in the Views folder, which is used to configure the Razor view engine.

Understanding the Configuration Hierarchy

Most MVC framework developers only need to edit the Web.config file in the top-level of the Visual Studio project, but this is just one of a hierarchy of configuration files that ASP.NET uses. When an application is started, ASP.NET starts at the top of the hierarchy and works its way down. Each level in the hierarchy has a slightly narrower scope, and the overall effect is to allow lower-level configuration files to override more general settings that have been previously defined. The application-level Web.config file—the one in the root folder of Visual Studio—is close to the bottom of the hierarchy and relies on dozens of settings that have been defined in higher-level files. Table 9-3 summarizes the configuration files and explains how they relate to one another.

Table 9-3. The Hierarchy of Configuration Files

Scope

Name

Description

Global

Machine.config

This is the top-level file in the hierarchy. Changes to this file affect every ASP.NET application running on the server. See the following text for the location of this file.

Global

ApplicationHost.config

This file defines the configuration sections and default values for IIS or IIS Express. It is at the second level of the hierarchy and is used to define settings specific to the app server. See the following text for the location of this file.

Global

Web.config

This is the global version of the Web.config file and is located in the same directory as the Machine.config file. It provides the server-wide default values for ASP.NET and is at the second level of the hierarchy. Changes to this file override the settings in Machine.config.

Site

Web.config

An ASP.NET site is an IIS folder hierarchy that can contain multiple applications. The Web.config file in the site’s root folder sets the default configuration for all the applications in that site. This file is at level 3 in the hierarchy and is used to override settings in the global Web.config file.

App

Web.config

This is the application-level Web.config file found in the root folder of the application and is the one that developers most often use for configuration. It overrides the values specified in the site-level Web.config.

Folder

location elements

A location element in the app-level Web.config file specifies configuration settings for a URL specified by the path attribute. See the “Overriding Configuration Settings” section for details.

Folder

Web.config

This is a Web.config file added to a folder within the application and has the same effect as a location attribute in the app-level Web.config file. The MVC framework uses a web.config file in the Views folder to configure the view engine. See the “Overriding Configuration Settings” section for details.

image Tip  The MVC framework includes a file called web.config (with a lowercase w) in the Views folder. The file names are not case sensitive, and this is an example of a folder-level Web.config file as described in the table.

ASP.NET starts with the Machine.config file, which is at the top of the hierarchy to get the starting point for the configuration and then moves to the second-level of the hierarchy and processes the ApplicationHost.config and global Web.config files. New settings are added to form a merged configuration, and values for existing settings are replaced. This process continues down the hierarchy until the app-level Web.config file is reached, and elements are used to expand the merged configuration or replace existing configuration values. Finally, the location elements and the folder-level Web.config files are processed to create specific configurations for parts of the application. Figure 9-2 shows the hierarchy of files as they are processed.

9781430265412_Fig09-02.jpg

Figure 9-2. The hierarchy of ASP.NET configuration files

image Tip  I have included the site-level Web.config file for completeness, but it is specific to IIS. IIS is still widely used, but there is a substantial shift toward cloud deployment and the use of other servers using the OWIN API. I recommend that you don’t use IIS sites and that you install each application in isolation because it makes it easier to move between deployment models. Instead, use application-level Web.config files.

Although the configuration is spread throughout multiple files, the overall effect is a consistent configuration where settings are defined for every application at the global level and then progressively refined in the site, application, and folder-level files. This is why something as complex as an MVC framework application needs only a few lines of settings in the Web.config file: Hundreds of other settings are defined further up the hierarchy.

image Tip  If any of the files are missing, ASP.NET skips to the next level in the hierarchy. But, as you’ll learn, the global files define the structure that later files use to define configuration settings as well as their default values.

One of the reasons that developers work with the application-level Web.config file is that the files higher up in the hierarchy cannot be edited, something that is almost always the case for hosting and cloud platforms and frequently true for IIS servers running in private data centers as well.

image Caution  The ASP.NET platform caches the configuration data after it processes the hierarchy of files to improve performance. However, the configuration files are monitored, and any changes cause the application to be restarted, which can cause any state data to be lost and interrupt service to users. For this reason, it is important you don’t modify the configuration files for a production system while the application is running.

During development you will sometimes need to change the global configuration files to re-create the settings that you will encounter in production. You can find the Machine.config and global Web.config files in the following folder:

C:WindowsMicrosoft.NETFrameworkv4.0.30319Config

You may have a slightly different path if you are using a later version of .NET or have installed Windows into a nonstandard location. You can find the ApplicationHost.config file used by IIS Express in the following folder, where <user> is replaced by the name of the current user:

C:Users<user>DocumentsIISExpressconfig

Working with Basic Configuration Data

The configuration file system allows you to define settings for your application in different ways ranging from simple and generic key-value pairs to complex custom settings that are completely tailored to the needs of one application. In this section, I’ll show you how to define different kinds of basic setting and explain how you access them programmatically. After all, there is no point in being able to define a configuration if you can’t read it at runtime. Table 9-4 puts basic configuration data into context.

Table 9-4. Putting the Basic ASP.NET Configuration Data in Context

Question

Answer

What is it?

The basic configuration data consists of application settings and connection strings. Application settings are a set of key-value pairs, and connection strings contain information required to establish connections to external systems, such as databases.

Why should I care?

Application settings are the simplest way to define custom configuration information for an application. Connection strings are the standard mechanism for defining database connections and are widely used with ASP.NET.

How is it used by the MVC framework?

The MVC framework uses application settings to some of its own configuration data. Connection strings are not used directly but are relied on for persistence for application models.

Access to configuration data is provided through the WebConfigurationManager class, defined in the System.Web.Configuration namespace. The WebConfigurationManager class makes it easy to work with the configuration system—but there are some oddities, as I’ll explain. There are several useful static members of the WebConfigurationManager class, as described in Table 9-5.

Table 9-5. The Most Useful Members Defined by the WebConfigurationManager Class

Name

Description

AppSettings

Returns a collection of key-value pairs used to define simple application-specific settings. See the “Using Application Settings” section for details.

ConnectionStrings

Returns a collection of ConnectionStringSettings objects that describe the connection strings. See the “Using Connection Strings” section for details.

GetWebApplicationSection(section)

Returns an object that can be used to get information about the specified configuration section at the application level. This method will ignore any folder-level configuration even if the current request targets such a folder. See the “Grouping Settings Together” section for details.

OpenWebConfiguration(path)

Returns a System.Configuration.Configuration object that reflects the complete configuration at the specified level. See the “Overriding Configuration Settings” section for details.

image Note  The classes that ASP.NET provides for obtaining configuration information also allow changes to be made to the configuration. I think this is a terrible idea; in fact, I think it is such a bad idea that I am not going to demonstrate how it is done. I sometimes come across projects that try to modify the configuration as the application is running and it always ends badly.

Using Application Settings

Application settings are simple key-value pairs and are the easiest way to extend the configuration to define values that are specific to an application. Listing 9-3 shows the application settings that I added to the application-level Web.config file (the one that is at the root level in the Visual Studio project).

Listing 9-3.  Defining Application Settings in the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="defaultCity" value="New York"/>
    <add key="defaultCountry" value="USA"/>
    <add key="defaultLanguage" value="English"/>
  </appSettings>
  
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
</configuration>

image Tip  The configuration examples in this chapter all follow a related theme, which is to express the default values that a new user account might require. This is one of the most common uses for configuration data in the projects that I see in development—and is often hard-coded into the application components.

The Web.config file that Visual Studio created for the project already has an appSettings element, which is defined within the top-level configuration element. There can be only one appSettings element in the configuration file (unless you are using a location element, which I describe later in this chapter), so I have added to the existing element to define some new settings.

image Tip  Don’t confuse application settings with application state, which I describe in Chapter 10. Application settings are read-only values that are used to define custom configuration values, while application state is used for data values that can change as the application is running.

ASP.NET configuration files have a well-defined XML schema, which allows for some useful features. One such feature is that some configuration elements—including the appSettings element—are actually collections of properties and values. To control the content of the collection, you can use three different child elements within appSettings, as described in Table 9-6.

Table 9-6. The Elements Used to Control the Contents of a Configuration Collection

Name

Description

add

Defines a new application setting. The attributes supported by this element are key and value, which define the name of the setting and its value.

clear

Removes all of the application settings. No attributes are supported.

remove

Removes a single application setting, specified by the key attribute.

In Listing 9-3, I used the add element to define three new settings: defaultCity, defaultCountry, and defaultLanguage.

image Tip  You can get complete details of the XML schema used for configuration files from MSDN. See http://msdn.microsoft.com/en-us/library/zeshe0eb(v=vs.100).aspx.

Reading Application Settings

Reading application settings is done through the WebConfigurationManager.AppSettings property, which returns an instance of the NameValueCollection class, defined in the System.Collections.Specialized namespace. There are four useful properties for reading application settings, as described in Table 9-7.

Table 9-7. The NameValueCollection Properties Useful for Reading Application Settings

Name

Description

AllKeys

Returns a string array containing the application settings’ names

Count

Returns the number of application settings

Item[index]

Returns the value of the application setting at the specified index

Item[key]

Returns the value of the application setting with the specified key

In Listing 9-4, you can see how I have updated the Index action in the Home controller to read application settings.

Listing 9-4.  Reading Application Settings in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (string key in WebConfigurationManager.AppSettings) {
                configData.Add(key, WebConfigurationManager.AppSettings[key]);
            }
            return View(configData);
        }
    }
}

I have used a standard foreach loop to iterate through the set of application settings’ names and add them—and the corresponding values—to the view model Dictionary. You can see the effect by starting the application and requesting the /Home/Index URL, as shown in Figure 9-3.

9781430265412_Fig09-03.jpg

Figure 9-3. Reading the application settings

I enumerated all of the application settings, which is helpful to demonstrate how the configuration system works but isn’t a realistic demonstration of how settings are usually consumed. In Listing 9-5, you can see that I have added a DisplaySingle action method to the Home controller, which uses an application setting to vary the model data sent to the view.

Listing 9-5.  Adding an Action Method in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (string key in WebConfigurationManager.AppSettings) {
                configData.Add(key, WebConfigurationManager.AppSettings[key]);
            }
            return View(configData);
        }
 
        public ActionResult DisplaySingle() {
            return View((object)WebConfigurationManager.AppSettings["defaultLanguage"]);
        }
    }
}

In the new action method, I get the value of the defaultLanguage application setting and use it as the view model data. Listing 9-6 shows the view that I created for the action method.

Listing 9-6.  The Contents of the DisplaySingle.cshtml File

@model string
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Single Config Value</title>
    <link href="∼/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="∼/Content/bootstrap-theme.min.css" rel="stylesheet" />
    <style>body { padding-top: 10px; }</style>
</head>
<body class="container">
    <div class="alert alert-success">
        Value: @Model
    </div>
</body>
</html>

Defining simple settings in the configuration file has a number of benefits. First, settings are not hard-coded in the components of the application, such as models and views, and this means that making changes is simple and easy. Second, Visual Studio includes a useful feature that will automatically transform a configuration file for testing or deployment, which makes it easy to consistently change settings as an application moves out of development and toward production. Finally, using application settings makes it easy to ensure that all of the components that rely on a setting have the same value—something that is hard to do with hard-coded values. Start the application and request the /Home/DisplaySingle URL to see the effect of the new action method and its reliance on an application setting, as illustrated by Figure 9-4.

9781430265412_Fig09-04.jpg

Figure 9-4. Obtaining a single configuration value

Using Connection Strings

The second type of setting that can be defined in a configuration file is a connection string. You have almost certainly defined connection strings in your MVC framework applications because they are most often used to define the connection details required for databases.

Although most connection strings are used to configure database connections, they actually represent a more flexible mechanism that can be used to describe any kind of connection to an external resource. There are two parts to a connection string: the name of the provider class that will be instantiated to establish the connection and the string that the provider will use to do its work. The format of the string isn’t standardized because it has to make sense only to the provider, but in Listing 9-7 I have added a connection string to the application-level Web.config file that uses a format you may well recognize.

image Caution  The value for the connectionString attribute of the add element is too long to fit on the printed page, so it is shown wrapped on two lines. The configuration system can’t cope with values that span two lines, so you must ensure that the complete string is on a single line in your project.

Listing 9-7.  Adding a Connection String to the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="defaultCity" value="New York"/>
    <add key="defaultCountry" value="USA"/>
    <add key="defaultLanguage" value="English"/>
  </appSettings>
 
  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)v11.0;Initial
          Catalog=SportsStore;Integrated Security=True"
        providerName="System.Data.SqlClient"/>
  </connectionStrings>
  
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
</configuration>

I don’t want to get into setting up a database in this chapter, so I took the connection string in the listing from the SportsStore example in my Pro ASP.NET MVC 5 book, where I used it to connect the web application to a local database containing product data.

Connection strings are defined within the configuration/connectionStrings element, which, like appSettings, is expressed as a collection. That means new connection strings are defined using the add element and that connection strings can be changed using the clear and remove elements. When using the add element to define a connection string, there are three attributes that you may use, as described in Table 9-8.

Table 9-8. The add Element Attributes for Defining Connection Strings

Name

Description

name

The name with which the connection string will be referred to

connectionString

The string that tells the provider class how to connect to the external server or resource

providerName

The name of the provider class that will create and manage the connection

In the listing, my connection string uses the providerName attribute to specify that the System.Data.SqlClient class will be used as the provider, and the connectionString attribute specifies a string in a format that the SqlClient class can understand.

Reading Connection Strings

You won’t usually need to read connection strings from the configuration unless you are writing a provider class or code that manages them, but in Listing 9-8 you can see how I have used the WebConfigurationManager.ConnectionStrings property to get the connection string details.

Listing 9-8.  Reading a Connection String in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
using System.Configuration;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (ConnectionStringSettings cs in
                    WebConfigurationManager.ConnectionStrings) {
                configData.Add(cs.Name, cs.ProviderName + " " + cs.ConnectionString);
            }
            return View(configData);
        }
 
        public ActionResult DisplaySingle() {
            return View((object)WebConfigurationManager
                .ConnectionStrings["EFDbContext"].ConnectionString);
        }
    }
}

The ConnectionStrings property returns a collection of ConnectionStringSettings objects (defined in the System.Configuration namespace) that represent the configuration settings through the Name, ProviderName, and ConnectionString properties. In the listing, I enumerate all of the connection strings in the Index action and get the details of a connection string by name in the DisplaySingle action. Figure 9-5 shows the result of starting the application and requesting the /Home/Index URL.

9781430265412_Fig09-05.jpg

Figure 9-5. Enumerating the connection strings

There are two connection strings shown, even though I defined only one in Listing 9-7. When working with configuration data, it is important to remember that the data you are presented with is merged from the set of files I described in Table 9-3. In this example, the LocalSqlServer connection string is defined in the Machine.config file as a default connection string.

Grouping Settings Together

Application settings are useful for simple or self-contained settings, but for more complex configurations they quickly get out of hand because every setting is expressed as part of a flat list of key-value pairs.

ASP.NET uses odd terminology to describe the features that can be used to add structure to a configuration file by grouping related settings. Configuration sections are XML elements that have one or more attributes, and configuration groups are elements that contain one or more configuration sections. The application-level Web.config file contains a good example of both, as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="defaultCity" value="New York"/>
    <add key="defaultCountry" value="USA"/>
    <add key="defaultLanguage" value="English"/>
  </appSettings>
 
  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)v11.0;
            Initial Catalog=SportsStore;Integrated Security=True"
        providerName="System.Data.SqlClient"/>
  </connectionStrings>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
</configuration>

The system.web element is a configuration group, and the compilation and httpRuntime elements are configuration sections. The configuration group brings together settings that define how the ASP.NET platform and the application behave. The configuration sections define related settings. For example, the compilation section has attributes that control whether debug mode is enabled and what version of .NET will be used. In the sections that follow, I’ll show you how to read configuration sections in an application and explain how to create your own. Table 9-9 puts configuration sections into context.

Table 9-9. Putting the Basic ASP.NET Configuration Sections in Context

Question

Answer

What is it?

Configuration sections group related settings as attributes on a custom XML element.

Why should I care?

Configuration sections require more work than application settings, but they are less prone to typos and provide support for validating the values in configuration files.

How is it used by the MVC framework?

The MVC framework defines configuration sections in the Views/web.config file to configure view rendering.

Creating a Simple Configuration Section

To demonstrate how to create a configuration section, it is easier to show the process in reverse, starting with the configuration values and working back through the process of creating the section that defines them. In Listing 9-9, I have added a new configuration section to the Web.config file.

Listing 9-9.  Adding a New Configuration Section to the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="defaultCity" value="New York"/>
    <add key="defaultCountry" value="USA"/>
    <add key="defaultLanguage" value="English"/>
  </appSettings>
 
  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)v11.0;
            Initial Catalog=SportsStore;Integrated Security=True"
        providerName="System.Data.SqlClient"/>
  </connectionStrings>
 
  <newUserDefaults city="Chicago" country="USA" language="English" regionCode="1"/>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
</configuration>

The configuration section is represented by the newUserDefaults element, which defines city, country, language, and regionCode attributes. The first three attributes correspond to the application settings I defined earlier, and I added the regionCode attribute to demonstrate how configuration settings can be used to automatically parse numeric values. This may not seem like a significant improvement over using application settings, but you’ll see that there are some advantages of taking the time to create custom configuration sections for your applications.

image Tip  You will see an error if you start the application at this point because all of the elements in a configuration file need to be supported by a section definition and handler class, both of which I create in the following sections.

Creating the Configuration Section Handler Class

Configuration sections are processed by a handler class, which is responsible for processing the information from the configuration file and presenting it to the application. Handler classes are derived from the System.Configuration.ConfigurationSection class and define properties that correspond to the attributes of the XML element they support. I created an Infrastructure folder in the example application and added to it a class file called NewUserDefaultsSection.cs, the contents of which can be seen in Listing 9-10.

Listing 9-10.  The Contents of the NewUserDefaultsSection.cs File

using System.Configuration;
 
namespace ConfigFiles.Infrastructure {
    public class NewUserDefaultsSection : ConfigurationSection {
 
        [ConfigurationProperty("city", IsRequired = true)]
        public string City {
            get { return (string)this["city"]; }
            set { this["city"] = value; }
        }
 
        [ConfigurationProperty("country", DefaultValue = "USA")]
        public string Country {
            get { return (string)this["country"]; }
            set { this["country"] = value; }
        }
 
        [ConfigurationProperty("language")]
        public string Language {
            get { return (string)this["language"]; }
            set { this["language"] = value; }
        }
 
        [ConfigurationProperty("regionCode")]
        [IntegerValidator(MaxValue = 5, MinValue = 0)]
        public int Region {
            get { return (int)this["regionCode"]; }
            set { this["regionCode"] = value; }
        }
    }
}

The names of the properties usually match the attribute names with the first letter capitalized, although this is just a convention and property names should make it obvious which attributes they relate to. You can use any name for the properties; for example, the property that represents the regionCode attribute is called Region in the handler class.

The base for configuration sections is the ConfigurationSection class, which defines a protected collection used to store the configuration values. This collection is available through the this indexer, and I implement each property so that the set and get blocks assign and retrieve values from the this collection, like this:

...
public string City {
    get { return (string)this["city"]; }
    set { this["city"] = value; }
}
...

The next step is to apply the ConfigurationProperty attribute to each property. The first parameter is the name of the attribute in the configuration file that the property corresponds. There are some optional parameters that refine the property behavior, as shown in Table 9-10.

Table 9-10. The Parameters Used with the ConfigurationProperty Attribute

Name

Description

DefaultValue

This specifies the default value for the property if one is not set in the configuration file.

IsDefaultCollection

This is used when a configuration section manages a collection of elements.

IsRequired

When set to true, an exception will be thrown if a value is not defined in the hierarchy for this property.

The use of the attribute parameters is optional, but they help prevent configuration mistakes from causing unexpected behaviors when the application is running. The DefaultValue parameter ensures that there is a sensible fallback value when one is not supplied in the configuration files, and the IsRequired parameter allows ASP.NET to report a configuration exception rather than letting code fail when it tries to read the configuration property values. In the listing, I have used IsRequired on the City property and DefaultValue on the Country property, as follows:

...
[ConfigurationProperty("city", IsRequired = true)]
public string City {
    get { return (string)this["city"]; }
    set { this["city"] = value; }
}
 
[ConfigurationProperty("country", DefaultValue = "USA")]
public string Country {
    get { return (string)this["country"]; }
    set { this["country"] = value; }
}
...

When ASP.NET processes the configuration section, it instantiates the handler class and sets the properties using the values in the file. ASP.NET will report an error if it can’t convert a value from the configuration file into the type of the handler class property, but it is possible to be more specific about the range of allowed values by applying a set of validation attributes, such as the IntegerValidator attribute that I applied to the Region property:

...
[ConfigurationProperty("regionCode")]
[IntegerValidator(MaxValue = 5, MinValue = 0)]
public int Region {
    get { return (int)this["regionCode"]; }
    set { this["regionCode"] = value; }
}
...

The MinValue and MaxValue parameters specify the range of acceptable values for this property, and the ASP.NET framework will report an error if the value specified in the configuration file is outside this range or cannot be parsed to an int value. Table 9-11 describes the full set of validation attributes, all of which are defined in the System.Configuration namespace.

Table 9-11. The Configuration Validation Attribtutes

Name

Description

CallbackValidator

Used to perform custom validation; see the text following the table.

IntegerValidator

Used to validate int values. By default, this attribute accepts values within the range defined by the MinValue and MaxValue parameters, but you can use the ExcludeRange parameter to true to exclude values in that range instead.

LongValidator

Used to validate long values. This defines the same parameters as the IntegerValidator.

RegexStringValidator

Used to ensure that a string value matches a regular expression. The expression is specified using the Regex parameter.

StringValidator

Used to perform simple validations on string values. The MinLength and MaxLength parameters constrain the length of the value, and the InvalidCharacters parameter is used to exclude characters.

TimeSpanValidator

Used to validate time spans. The MinValueString and MaxValueString parameters restrict the range of values, expressed in the form 0:30:00. The MinValue and MaxValue parameters do the same thing but require TimeSpan values. When set to true, the ExcludeRange parameter excludes values that fall between the minimum and maximum values.

The validation attributes are easy to use, but the CallbackValidator allows you to specify a method that contains validation logic for custom configuration properties. Listing 9-11 shows the use of the CallbackValidator attribute applied to the NewUserDefaultsSection class.

Listing 9-11.  Performing Custom Validation in the NewUserDefaultsSection.cs File

using System.Configuration;
using System;
 
namespace ConfigFiles.Infrastructure {
    public class NewUserDefaultsSection : ConfigurationSection {
 
        [ConfigurationProperty("city", IsRequired = true)]
        [CallbackValidator(CallbackMethodName = "ValidateCity",
             Type = typeof(NewUserDefaultsSection))]
        public string City {
            get { return (string)this["city"]; }
            set { this["city"] = value; }
        }
 
        //...other properties omitted for brevity...
 
        public static void ValidateCity(object candidateValue) {
            if ((string)candidateValue == "Paris") {
                throw new Exception("Unsupported City Value");
            }
        }
    }
}

The parameters for the CallbackValidator attribute are CallbackMethodName and Type, which are used to specify the method that should be called when the configuration data is processed. The method must be public and static, take a single object argument, and not return a result—the method throws an exception if the value passed as the argument is not acceptable. In the listing, I throw an exception if the value is Paris. (I could have achieved the same effect using the RegexStringValidator attribute, but I wanted to demonstrate the CallbackValidator attribute, which can be a lot more flexible.)

Defining the Section

The next step is to associate the handler class with the configuration section, which requires an addition to the Web.config file. All configuration sections have to be defined somewhere, but the ones used by ASP.NET, such as compilation and httpRuntime, are defined in higher-level files, so you won’t have seen the definition of them in the application-level Web.config file. Listing 9-12 shows the additions I made to the application-level Web.config file to define my custom configuration section.

Listing 9-12.  Defining a Custom Configuration Section in the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <section name="newUserDefaults"
        type="ConfigFiles.Infrastructure.NewUserDefaultsSection"/>
  </configSections>
 
  <!--...application settings and connection strings omitted for brevity...-->
 
  <newUserDefaults city="Chicago" country="USA" language="English" regionCode="1"/>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
</configuration>

The configSections element is used to define new sections and section groups. I used the section element in the listing, which defines the attributes shown in Table 9-12.

Table 9-12. The Attributes Defined by the configSections/section Element

Name

Description

allowDefinition

Used to limit where the section can be defined. The values are Everywhere (the section can be defined anywhere in the configuration hierarchy), MachineToApplication (the section can be defined in the hierarchy from the Machine.config through to the app-level Web.config file), MachineToWebRoot (in the Machine.config or the global Web.config file), and MachineOnly (only in the Machine.config file). If the attribute is omitted, then the default value of Everywhere is used.

allowLocation

Specifies whether the section can be defined in location elements. The default is true. See the “Overriding Configuration Settings” section for details of location elements.

name

The name of the section.

Type

The name of the handler class.

Using the table, you can see I have defined my new section with the name newUserDefaults, specified the NewUserDefaultsSection handler class, and accepted the default values for the allowDefinition and allowLocation attributes.

Reading Configuration Sections

All that remains is to read the values from the custom configuration section, which is done through the WebConfigurationManager. Listing 9-13 shows the changes I made to the Home controller to read the settings in the newUserDefaults section.

Listing 9-13.  Reading Values from a Configuration Section

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
using System.Configuration;
using ConfigFiles.Infrastructure;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
 
            NewUserDefaultsSection nuDefaults = WebConfigurationManager
                .GetWebApplicationSection("newUserDefaults") as NewUserDefaultsSection;
            if (nuDefaults != null) {
                configData.Add("City", nuDefaults.City);
                configData.Add("Country", nuDefaults.Country);
                configData.Add("Language", nuDefaults.Language);
                configData.Add("Region", nuDefaults.Region.ToString());
            };
            return View(configData);
        }
 
        public ActionResult DisplaySingle() {
            return View((object)WebConfigurationManager
                .ConnectionStrings["EFDbContext"].ConnectionString);
        }
    }
}

The WebConfigurationManager.GetWebApplicationSection method takes the name of the section as its argument and returns an instance of the handler class, which is NewUserDefaultsSection in this case. I can then use the properties defined by the handler class to read the configuration values. To see the result, start the application and request the /Home/Index URL, as shown in Figure 9-6.

9781430265412_Fig09-06.jpg

Figure 9-6. Reading a configuration section

Creating a Collection Configuration Section

Simple configuration sections are useful when you want to define a set of related values, such as the defaults for users in the previous section. If you want to define a set of repeating values, then you can define a collection configuration section, of which the appSettings and connectionStrings sections are examples. Configuration values are added to the collection with the add element and deleted from the collection using the remove element, and the collection is reset entirely with the clear element.

Once again, I am going to start by adding a configuration section to Web.config and then work back to define the supporting features. Listing 9-14 shows the addition of a custom collection configuration section to the application-level Web.config file.

Listing 9-14.  Adding a Collection Configuration Section to the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <section name="newUserDefaults"
        type="ConfigFiles.Infrastructure.NewUserDefaultsSection"/>
  </configSections>
 
  <!--...application settings and connection strings omitted for brevity...-->
 
  <newUserDefaults city="Chicago" country="USA" language="English" regionCode="1"/>
 
  <places default="LON">
    <add code="NYC" city="New York" country="USA" />
    <add code="LON" city="London" country="UK" />
    <add code="PAR" city="Paris" country="France" />
  </places>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
</configuration>

Creating the Item Handler Class

The process for supporting this kind of configuration section is a little more complex than for a basic section. The first step is to create a class that will represent each of the items that the add element creates. To this end, I added a class file called Place.cs to the Infrastructure folder and used it to define the class shown in Listing 9-15.

Listing 9-15.  The Contents of the Place.cs File

using System;
using System.Configuration;
 
namespace ConfigFiles.Infrastructure {
 
    public class Place : ConfigurationElement {
 
        [ConfigurationProperty("code", IsRequired = true)]
        public string Code {
            get { return (string)this["code"]; }
            set { this["code"] = value; }
        }
 
        [ConfigurationProperty("city", IsRequired = true)]
        public string City {
            get { return (string)this["city"]; }
            set { this["city"] = value; }
        }
 
        [ConfigurationProperty("country", IsRequired = true)]
        public String Country {
            get { return (string)this["country"]; }
            set { this["country"] = value; }
        }
    }
}

The handler class for individual collection items is derived from the ConfigurationElement class and defines properties that correspond to the attributes on the add element. The add element can define any attributes you need to represent the data, which is why the add element for, say, application settings has different attributes from the one used for connection strings. The class in Listing 9-15 has properties for Code, City, and Country, which I will use to define a set of cities. The properties in the handler class are decorated with the ConfigurationProperty attribute, which is used in the same way as the previous example.

Creating the Collection Handler Class

I also need to create a handler class for the overall collection of configuration elements. The job of the handler class is to override a series of methods that the ASP.NET platform will use to populate the collection. I added a class file called PlaceCollection.cs to the Infrastructure folder and used it to define the class shown in Listing 9-16.

Listing 9-16.  The Contents of the PlaceCollection.cs File

using System.Configuration;
 
namespace ConfigFiles.Infrastructure {
 
    public class PlaceCollection : ConfigurationElementCollection {
 
        protected override ConfigurationElement CreateNewElement() {
            return new Place();
        }
 
        protected override object GetElementKey(ConfigurationElement element) {
            return ((Place)element).Code;
        }
 
        public new Place this[string key] {
            get { return (Place)BaseGet(key); }
        }
    }
}

The base class is ConfigurationElementCollection, which is integrated with the other classes in the System.Configuration namespace. My implementation has to override the CreateNewElement method to create instances of the item handler class (Place in this example) and has to override the GetElementKey method to return a key that will be used to store an item in the collection—I used the value of the Code property. I have also added an indexer so that I can request items directly by key; the base class already defines a protected indexer, so I had to apply the new keyword to hide the base implementation.

Creating the Configuration Section Handler Class

The last handler class is for the configuration section itself. I added a class file called PlaceSection.cs to the Infrastructure folder and used it to define the class shown in Listing 9-17.

Listing 9-17.  The Contents of the PlaceSection.cs File

using System.Configuration;
 
namespace ConfigFiles.Infrastructure {
 
    public class PlaceSection : ConfigurationSection {
        [ConfigurationProperty("", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(PlaceCollection))]
        public PlaceCollection Places {
            get { return (PlaceCollection)base[""]; }
        }
 
        [ConfigurationProperty("default")]
        public string Default {
            get { return (string)base["default"]; }
            set { base["default"] = value; }
        }
    }
}

The complexity in managing the configuration section is contained in the other handler classes. For the section handler, all that I need to do is create a property that returns an instance of the collection handler class, which I have called Places, and apply two attributes to it.

The ConfigurationProperty attribute is applied with an empty string for name and the IsDefaultCollection parameter set to true. This tells the ASP.NET framework that the add, remove, and clear elements in the configuration section will be applied to this collection. The empty string is also used in the property getter and is a special incantation that sets up the collection. The ConfigurationCollection attribute tells the ASP.NET framework what collection class should be instantiated to hold the configuration items—in this example, the PlaceCollection class.

When I defined the configuration data in Listing 9-14, I included an attribute on the section element, like this:

...
<places default="LON">
  <add code="NYC" city="New York" country="USA" />
  <add code="LON" city="London" country="UK" />
  <add code="PAR" city="Paris" country="France" />
</places>
...

This is a common technique for collections, which define the range of allowable values and use an element to specify the default value if one has not otherwise been selected. In Listing 9-17, I added the Default property to provide access to the value of the default attribute, which I am able to obtain using the indexer provided by the base handler class.

Defining the Configuration Section

Now that the (many) handler classes are in place, I can define the collection configuration section in the Web.config file, which has the effect of associating the handlers with the XML element. Listing 9-18 shows the addition I made to the configSections element in the application-level Web.config file.

Listing 9-18.  Adding a New Configuration Section to the Web.config File

...
<configSections>
  <section name="newUserDefaults"
            type="ConfigFiles.Infrastructure.NewUserDefaultsSection"/>
  <section name="places" type="ConfigFiles.Infrastructure.PlaceSection"/>
</configSections>
...

There are no special attributes required to define a collection section; the complexity of the collection is managed by the section handler class.

Reading Collection Configuration Sections

The process for reading collection configuration sections is similar to that for simple sections. Listing 9-19 shows the changes that I made to the Home controller to read the values for the places section.

Listing 9-19.  Reading a Collection Configuration Section in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
using System.Configuration;
using ConfigFiles.Infrastructure;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            PlaceSection section = WebConfigurationManager
                .GetWebApplicationSection("places") as PlaceSection;
            foreach (Place place in section.Places) {
                configData.Add(place.Code, string.Format("{0} ({1})",
                    place.City, place.Country));
            }
            return View(configData);
        }
 
        public ActionResult DisplaySingle() {
            PlaceSection section = WebConfigurationManager
                .GetWebApplicationSection("places") as PlaceSection;
            Place defaultPlace = section.Places[section.Default];
            return View((object)string.Format("The default place is: {0}",
                defaultPlace.City));
        }
    }
}

I have used the Index action method to enumerate all of the configuration items in the collection. I use the WebConfigurationManager.GetWebApplicationSection method to get an instance of the section handler class (PlaceSection) and read the value of the Places property to get an instance of the collection handler class (PlaceCollection). I use a foreach loop to obtain each item handler class (Place) and add the individual property values, which are obtained from the attributes of the add element, to the view data.

For the DisplaySingle action method, I read the value of the PlaceSection.Default property to get the value of the default attribute and use this as a key to retrieve the corresponding Place object. You can see the output from both action methods by starting the application and requesting the /Home/Index and /Home/DisplaySingle URLs, as illustrated by Figure 9-7.

9781430265412_Fig09-07.jpg

Figure 9-7. Reading a collection configuration section

Creating Section Groups

A section group—as the name suggests—allows configuration sections to be grouped together. The main reason for using section groups is to add further structure to the data in the configuration files in order to make it clear that section groups are related to one another and affect related parts of the application. Listing 9-20 shows the addition of a section group to contain the newUserDefaults and places sections in the Web.config file. I have also added the definition of the section group in the configSections element.

Listing 9-20.  Adding a Configuration Section Group to the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <sectionGroup name="customDefaults" type="ConfigFiles.Infrastructure.CustomDefaults">
      <section name="newUserDefaults"
          type="ConfigFiles.Infrastructure.NewUserDefaultsSection"/>
      <section name="places" type="ConfigFiles.Infrastructure.PlaceSection"/>
    </sectionGroup>
  </configSections>
 
  <!--...application settings and connection strings omitted for brevity...-->
 
  <customDefaults>
    <newUserDefaults city="Chicago" country="USA" language="English" regionCode="1"/>
    <places default="LON">
      <add code="NYC" city="New York" country="USA" />
      <add code="LON" city="London" country="UK" />
      <add code="PAR" city="Paris" country="France" />
    </places>
  </customDefaults>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
  
</configuration>

You can see from the listing that a section group is a custom element that wraps one or more custom sections. The sectionGroup element tells ASP.NET which handler class will represent the section group and defines the relationship between the group and the sections it contains. In the listing, my section group is called customDefaults, the handler class is called CustomDefaults, and the section group will contain the newUserDefaults and places sections that I created earlier.

Creating the Section Group Handler Class

Section groups rely on handler classes, similar to the ones used for configuration sections. For my customDefaults section group, I added a class file called CustomDefaults.cs to the Infrastructure folder and used it to define the class shown in Listing 9-21.

Listing 9-21.  The Contents of the CustomDefaults.cs File

using System.Configuration;
 
namespace ConfigFiles.Infrastructure {
    public class CustomDefaults : ConfigurationSectionGroup {
 
        public NewUserDefaultsSection NewUserDefaults {
            get { return (NewUserDefaultsSection)Sections["newUserDefaults"]; }
        }
 
        public PlaceSection Places {
            get { return (PlaceSection)Sections["places"]; }
        }
    }
}

The purpose of the section group handler class is to expose properties that access the configuration sections that the group element contains. Section group handler classes are derived from the ConfigurationSectionGroup class, which defines a Sections property through which instances of section handler classes can be obtained by name, like this:

...
get { return (PlaceSection)Sections["places"]; }
...

Reading Section Groups

Reading section groups is a little more complex than reading individual sections because there is no direct convenience method in the WebConfigurationManager class. Instead, I have to obtain a System.Configuration.Configuration object from WebConfigurationManager and then request the section group, as shown in Listing 9-22.

Listing 9-22.  Reading a Section Group in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Configuration;
using System.Configuration;
using ConfigFiles.Infrastructure;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
 
            CustomDefaults cDefaults
                = (CustomDefaults)WebConfigurationManager.OpenWebConfiguration("/")
                    .GetSectionGroup("customDefaults");
 
            foreach (Place place in cDefaults.Places.Places) {
                configData.Add(place.Code, string.Format("{0} ({1})",
                    place.City, place.Country));
            }
            return View(configData);
        }
 
        public ActionResult DisplaySingle() {
            PlaceSection section = WebConfigurationManager
                .GetWebApplicationSection("customDefaults/places") as PlaceSection;
            Place defaultPlace = section.Places[section.Default];
            return View((object)string.Format("The default place is: {0}",
                defaultPlace.City));
        }
    }
}

I have used the WebConfigurationManager.OpenWebConfiguration method in the Index action method to get the Configuration object, which allows me to call the GetSectionGroup method to get an instance of my handler class. The argument I have passed to the OpenWebConfiguration method specifies where in the configuration hierarchy the data will be read from, which I return to in the next section. Once I have obtained an instance of the section group handler class, I can access the properties it defines to get instances of the section handlers and read the configuration data.

You can see an alternative approach to dealing with section groups in the DisplaySingle action method, where I used the WebConfigurationManager.GetWebApplicationSection method and included the name of the section group along with the section, like this:

...
WebConfigurationManager.GetWebApplicationSection("customDefaults/places")
...

The configuration system is smart enough to navigate the elements in the configuration file and locate the correct section.

Overriding Configuration Settings

The ASP.NET platform provides a means to override configuration values within an application, allowing you to change the settings for specific requests. These features were originally designed to work with Web Forms, where the requested URL corresponds directly with the file that will be used to generate the response. They don’t work quite as well with MVC framework applications, but I have included them in this chapter because I sometimes find them useful for applying quick patches to applications where an urgent fix is needed to buy time while a real solution is found and tested. Table 9-13 puts configuration overrides in context.

Table 9-13. Putting Configuration Overrides in Context

Question

Answer

What is it?

Overriding configuration settings allows you to create different configurations for different parts of the application.

Why should I care?

Overriding configuration settings allows you to specify only the changes you require, rather than duplicating the entire configuration.

How is it used by the MVC framework?

The MVC framework uses the Views/web.config file to configure the Razor view engine separately from the configuration for the rest of the framework.

Using the Location Element

The location element allows you to create sections in configuration files that are used for requests with specific URLs. In Listing 9-23, I have added a location element to the application-level Web.config file.

Listing 9-23.  Adding a location Element to the Web.config File

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <sectionGroup name="customDefaults" type="ConfigFiles.Infrastructure.CustomDefaults">
      <section name="newUserDefaults"
          type="ConfigFiles.Infrastructure.NewUserDefaultsSection"/>
      <section name="places" type="ConfigFiles.Infrastructure.PlaceSection"/>
    </sectionGroup>
  </configSections>
 
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
     <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="defaultCity" value="New York"/>
    <add key="defaultCountry" value="USA"/>
    <add key="defaultLanguage" value="English"/>
  </appSettings>
 
  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)v11.0;Initial
          Catalog=SportsStore;Integrated Security=True"
        providerName="System.Data.SqlClient"/>
  </connectionStrings>
  
  <customDefaults>
    <newUserDefaults city="Chicago" country="USA" language="English" regionCode="1"/>
    <places default="LON">
      <add code="NYC" city="New York" country="USA" />
      <add code="LON" city="London" country="UK" />
      <add code="PAR" city="Paris" country="France" />
    </places>
  </customDefaults>
 
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
 
  <location path="Home/Index">
    <appSettings>
      <add key="defaultCity" value="London"/>
      <add key="defaultCountry" value="UK"/>
    </appSettings>
  </location>
  
</configuration>

The location element defines the path attribute, which identifies the URL that the overridden configuration applies to. In this case, I have specified the Home/Index URL, which means that the location element will be applied to requests that target the Index action on the Home controller.

Within the location element, you can define the configuration settings that you want to override for requests that target the path URL. In the listing, I have used an appSettings element to change two of the application settings that I defined earlier. To demonstrate the effect, I have updated the Home controller to read the application settings, 3as shown in Listing 9-24.

Listing 9-24.  Reading Application Settings in the HomeController.cs File

using System.Collections.Generic;
using System.Web.Configuration;
using System.Web.Mvc;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (string key in WebConfigurationManager.AppSettings.AllKeys) {
                configData.Add(key, WebConfigurationManager.AppSettings[key]);
            }
            return View(configData);
        }
 
        public ActionResult OtherAction() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (string key in WebConfigurationManager.AppSettings.AllKeys) {
                configData.Add(key, WebConfigurationManager.AppSettings[key]);
            }
            return View("Index", configData);
        }
    }
}

As well as modifying the Index action method, I have replaced the DisplaySingle action with the OtherAction method that contains the same code as the Index action. Both the Index and OtherAction action methods read application settings, but the location element means that the DisplaySingle action method will be provided with a different set of values. To see the difference, start the application and request the /Home/Index and /Home/OtherAction URLs, as shown in Figure 9-8.

9781430265412_Fig09-08.jpg

Figure 9-8. The effect of using the location element

Only the application settings that I included in the location element have changed. The other values are inherited, which means that when the WebConfigurationManager class is used, it merges the data in the hierarchy of files with the contents of the location element to present a unique view of the data to a single action method.

image Caution  The problem with this approach is that the value of the path attribute doesn’t take into account the effect of the routing system, so changes to the routes in an application can render a location element ineffective. This isn’t a problem in classic Web Forms projects but has the effect of limiting the use of the location element when routing is used, either with Web Forms or in the MVC framework.

Using Folder-Level Files

You can define Web.config files in individual folders and use them to extend the hierarchy of configuration files. This feature is a little harder to work with because you have to explicitly request the configuration file you want (and work with some different classes), but it does have the benefit of not being tied to the requested URL so it can be used in multiple controllers and action methods. To get started, I added a Web.config file to the Views/Home folder and defined the configuration elements shown in Listing 9-25.

Listing 9-25.  The Contents of the Web.config File in the Views/Home Folder

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="defaultCity" value="Paris"/>
    <add key="defaultCountry" value="France"/>
  </appSettings>
</configuration>

I have used the appSettings element to define new values for the defaultCity and defaultCountry application settings. To read these values, I have updated the OtherAction method in the Home controller, as shown in Listing 9-26.

Listing 9-26.  Reading a Folder-Level Configuration in the HomeController.cs File

using System.Collections.Generic;
using System.Configuration;
using System.Web.Configuration;
using System.Web.Mvc;
 
namespace ConfigFiles.Controllers {
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            foreach (string key in WebConfigurationManager.AppSettings.AllKeys) {
                configData.Add(key, WebConfigurationManager.AppSettings[key]);
            }
            return View(configData);
        }
 
        public ActionResult OtherAction() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            AppSettingsSection appSettings =
                WebConfigurationManager.OpenWebConfiguration("∼/Views/Home").AppSettings;
            foreach (string key in appSettings.Settings.AllKeys) {
                configData.Add(key, appSettings.Settings[key].Value);
            }
            return View("Index", configData);
        }
    }
}

I have called the WebConfigurationManager.OpenWebConfiguration method, which takes a path as its arguments. Paths are a central part of the Web Forms programming model, but in an MVC framework application they can be used to specify the location of the folder-level configuration file. The path must be prefixed with a tilde character () followed by the name of the folder that contains the Web.config file; in this case, I have specified Views/Home, which is the location of the file I created in Listing 9-25.

The OpenWebConfiguration method returns an instance of the Configuration class, which is defined in the System.Configuration namespace. The Configuration class provides access to all the features I described in this chapter but does it using a different set of methods and properties from the WebConfigurationManager class. (In fact, the WebConfigurationManager class relies on the System.Configuration namespace for its functionality, but with an emphasis on ease of use in web applications.)

image Caution  Don’t create a folder hierarchy that matches the URLs that your application supports, such as a Home/Index folder. The routing configuration is set up to not route requests that correspond to folders on the disk, and you’ll see an error. Instead, either define a separate top-level folder to contain your folder-level configuration files or use the Views folder, as I have done in the example.

The Configuration.AppSettings property returns an AppSettingsSection object, which exposes a collection of the application settings through its Settings property. I use this collection to enumerate the application settings and generate the view model data.

When you request a Configuration object through the OpenWebConfiguration method, the contents of the Web.config file in the folder that you specify are merged with the contents of the files higher in the hierarchy. This includes any folder-level files that exist in folders that are closer to the root of the project. I defined the Web.config file in Listing 9-25 in the Views/Home folder, but Visual Studio created a web.config file in the Views folder when the project was started. To show that all of the files in the hierarchy are used to create the configuration data, I added an application setting to the Views/web.config file, as shown in Listing 9-27.

image Tip  The name of the Web.config file is not case-sensitive. Web.config and web.config are both acceptable names for folder-level configuration files.

Listing 9-27.  Adding an Application Setting to the web.config file in the Views Folder

<?xml version="1.0"?>
 
<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor"
        type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup,
        System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral,
        PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection,
        System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral,
        PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages"
        type="System.Web.WebPages.Razor.Configuration.RazorPagesSection,
        System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral,
        PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>
 
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc,
         Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="ConfigFiles" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>
 
  <appSettings>
    <add key="webpages:Enabled" value="false" />
    <add key="defaultLanguage" value="French"/>
  </appSettings>
 
  <system.webServer>
    <handlers>
      <remove name="BlockViewHandler"/>
      <add name="BlockViewHandler" path="*" verb="*"
          preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
    </handlers>
  </system.webServer>
</configuration>

The Views/web.config file is used by the MVC framework to configure the Razor view engine, and you wouldn’t usually add configuration elements for the application. But that’s just convention, and you can see the effect of my addition by starting the application and requesting the /Home/OtherAction URL, as shown in Figure 9-9.

9781430265412_Fig09-09.jpg

Figure 9-9. Using a folder-level configuration file

As the figure shows, the set of application settings consists of those defined by the application-level Web.config file, supplemented by those in the folder-level files in the Views and Views/Home folders.

Navigating the ASP.NET Configuration Elements

The examples so far in this chapter have been focused on defining configuration settings for an application, largely because that’s the most common reason for using the configuration system. However, the settings that control the behavior of the ASP.NET platform and the MVC framework are also defined in the configuration files and are available to be read within an application. This isn’t a common task—and I won’t spend much time on it—but I find it useful when I suspect that a problem I am tracking down is caused by a configuration problem.

The ASP.NET configuration elements are contained in the system.web section, and the handler classes are defined in the System.Web.Configuration namespace. When you request a configuration section or section group through the WebConfigurationManager class, it will be represented by one of the classes in that namespace. I have listed the most commonly used handler classes in Table 9-14. There are others, but some of them are just for backward compatibility or have no bearing in an MVC framework application.

Table 9-14. The Commonly Used Handler Classes to Represent ASP.NET Configuration Sections

Name

Description

CacheSection

Represents the cache section, which controls content caching. See Chapters 11 and 12.

CompilationSection

Represents the compilation section, which controls how the application code is compiled. See the following text for details.

CustomErrorsSection

Represents the customErrors section, which controls the error handling policy. See Pro ASP.NET MVC 5 for details.

GlobalizationSection

Represents the globalization section, which controls locale and culture settings for the application.

HttpRuntimeSection

Represents the httpRuntime section, which defines basic behavior for the application.

SessionStateSection

Represents the sessionState section, which I describe in Chapter 10.

SystemWebSectionGroup

Represents the system.Web section group, which contains the ASP.NET configuration sections.

TraceSection

Represents the trace section, which control request tracing. See Chapter 8.

To demonstrate reading the ASP.NET settings, I have modified the Home controller so that it displays details of the compilation section, as shown in Listing 9-28.

Listing 9-28.  Reading ASP.NET Configuration Elements in the HomeController.cs File

using System.Collections.Generic;
using System.Configuration;
using System.Web.Configuration;
using System.Web.Mvc;
 
namespace ConfigFiles.Controllers {
 
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, string> configData = new Dictionary<string, string>();
            SystemWebSectionGroup sysWeb =
                (SystemWebSectionGroup)WebConfigurationManager.OpenWebConfiguration("/")
                    .GetSectionGroup("system.web");
            configData.Add("debug", sysWeb.Compilation.Debug.ToString());
            configData.Add("targetFramework", sysWeb.Compilation.TargetFramework);
            return View(configData);
        }
    }
}

I use the technique I described at the start of the chapter to get the handler for the system.Web section group, which defines properties for each of the sections it defines. The Compilation property returns the handler for the compilation section, which defines properties for the debug and targetFramework settings. You can see the result from reading the compilation settings by starting the application, as shown in Figure 9-10.

9781430265412_Fig09-10.jpg

Figure 9-10. Reading an ASP.NET configuration section

Summary

In this chapter, I explained how the ASP.NET configuration system works. I introduced the hierarchy of configuration files and the way that the ASP.NET platform merges them to create a unified configuration. I explained how you can create simple custom configurations for your application using application settings and connection strings and, with a little more effort, create your own configuration sections and collections. In the next chapter, I introduce the ASP.NET platform services for state data: application state and session state.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.182.62