In my last blog, I talked about the two modes of WCF service hosting environment. Basically there are two modes: HTTP-specific ASP.NET Compatibility mode and Mixed Transports mode for all protocols. I also showed a sample of using ConfigurationManager to get AppSettings data from web.config in the ASP.NET Compatibility mode. However, it is a little bit trickier to read configuration data in the Mixed Transports mode than that in the other mode. This is because ConfigurationManager internally depends on HttpContext.Current which is nulled out in the Mixed Transports mode.
Because of this, we have to take a step back to look at how the ASP.NET configuration API reads configuration data. Let’s first take a quick look at the deployment model for IIS/WAS and how ASP.NET works for this model.
In Microsoft Internet Information Service (IIS) or Windows Activation Service (WAS) and ASP.NET, there are two models to control different levels of code isolation:
· Process Management model
· Application Management model
These different isolation models provide different levels of control for the health, performance, and security for web applications.
IIS 6.0 or WAS relies on the Process Management model to achieve code isolation at process-level. They host web applications in auto-launched worker processes named w3wp.exe. Runtime behaviors such as security identity and recycling of w3wp.exe instances are controlled by AppPool settings. You can find more information at:
In IIS 6.0 and WAS, web applications are arranged as virtual Applications. Each virtual application contains one or more virtual directories.
A virtual application is a self-contained code unit that runs similarly as a normal standalone application. In IIS 6.0, the concept of “virtual directory” is interchangeably with “virtual application”. From IIS manager, when you create a “virtual directory”, you are actually creating a virtual application.
A virtual application runs in an instance of the worker process. You can assign an AppPool to a virtual application. You can use the default AppPool named “DefaultAppPool” or create your own AppPools. Virtual applications assigned to the same AppPool will share the worker process instances with other virtual applications.
When a virtual application is created, the application-level settings are stored in IIS Metabase. For WAS, this is stored in the machine-level configuration file %windir%\system32\inetsrv\applicationhost.config. But you can still use the traditional Metabase API to query most of the metadata.
With ASP.NET, a virtual application has its real meaning. Each virtual application maps to an AppDomain in ASP.NET. This enables managed code isolation for web applications. You can apply different managed security settings to different virtual applications even though they use the same AppPool. ASP.NET stores its own settings in its configuration files: web.config. I will expand on this in next section.
Virtual applications can be nested as a hierarchy. The root virtual application of IIS/WAS has the virtual path “/”. All other virtual applications are offsprings of this root. A virtual application is uniquely identified by its virtual path. Thus you can use the virtual path to name a virtual application, for example, “/foo/bar”.
I did not mention IIS 5.1 which is available on Windows XP SP2. This is because IIS 5.1 has a different process management model. It does not have the AppPool concept and most of the content is delivered through the IIS service itself. With ASP.NET, however, there is a worker process named “aspnet_wp.exe” as a single worker process instance for IIS. All virtual applications are hosted in this instance.
A virtual directory is any physical directory that provides code and/or content to a virtual application. When you create a virtual application from IIS manager, you need to assign the default virtual directory first.
Both virtual applications and virtual directories can be nested in a mixed way. For example, you can have a virtual directory named “/app1/vdir1/app2/vdir2”. It is a virtual directory of application “app2” which nested under virtual directory “vdir1” of application “/app1”.
In IIS 6.0, you can actually create a virtual directory to point to a directory outside of the default directory of a virtual application. For example, you can create virtual application “vdir1” as a sub-application of “/app1”. Then from the property UI, you can click on “Remove” button to remove the virtual application and then it becomes a virtual directory of “/app1”.
Virtual directories can have sub-directories. You can actually have web.config files in any such directories.
With the above IIS/WAS concepts in mind, let’s take a look at how configuration data is managed hierarchically in ASP.NET.
ASP.NET introduced web.config files to store extra settings that are not stored in Metabase for virtual applications, virtual directories, and files. In WAS, this concept is extended. WAS is also config-based and it also uses web.config to store application settings.
· Machine.config (under %windir%\Microsoft.Net\Framework\[clrversion]\config)
· Root web.config (under the same location as machine.config)
The root web.config itself inherits from machine.config.
Virtual applications and directories are nested, so for the web.config files. For a virtual path such as “/app1/vdir1/app2/vdir2”, you can have web.config files at each level. If web.config is not provided at a level, it is inherited from those at higher levels.
You can use System.Configuration API to access the configuration data. This API provides a merged view of the web.config hierarchy. In this hierarchy, lower-level web.config always overwrites those from higher ones. This means that:
- For configuration elements of the same key at different levels, the lowest one always wins in the merged view.
For example, if you have the following configuration data at “/app1”:
<add key="Fruit" value="Pear"/>
<add key="Cookie" value="Saltine Cracker"/>
And you have the following data at “/app1/app2”:
<add key="Fruit" value="Apple"/>
With the System.Configuration API, you will see the following merged data for “/app1/app2”:
<add key="Fruit" value="Apple"/>
<add key="Cookie" value="Saltine Cracker"/>
Note that the first entry is from the lower-level web.config.
As mentioned above, it is non-trivial to access configuration data from hosted WCF services in the Mixed Transports mode. A .svc file that defines a hosted WCF service can be deployed to any hierarchy inside a virtual application. The System.Configuration API has no idea of where the WCF service is deployed. So from the code, you cannot simply use the API to get the right merged view. Actually you will always get the merged view up to the root of the virtual application from the API. This is normally not what people want.
Fortuanately, each hosted WCF service exposes a property VirtualPathExtention to indicate the virtual path of the current service.
The VirtualPathExtension is a special WCF service extension that has the property “VirtualPath” to indicate the virtual path of the WCF service. It is actually the virtual path of the .svc file that defines the service. It is the virtual path that is relative to the application root. For example, it is “~/Foo/Foo.svc” for the Foo.svc file under the subdirectory “Foo” of the current virtual application.
For hosted WCF service, you can get this extension from the extension collection. For example, from ServiceHostBase.InitializeRuntime, you can get this extension as following:
VirtualPathExtension extension = this.Extensions.Find<VirtualPathExtension>();
From a service operation, you can get it similarly as following:
VirtualPathExtension extension = OperationContext.Current.Host.Extensions.Find<VirtualPathExtension>();
With the VirtualPathExtension, you can get AppSettings from web.config as following:
Configuration config = WebConfigurationManager.OpenWebConfiguration(extension.VirtualPath);
string data = config.AppSettings.Settings["Fruit"].Value;
The above “config” actually contains the merged configuration view up to the current location of the .svc file.
With the above merged configuration view, we can easily get any configuration data. For example, you can get all bindings defined in the merged view:
BindingsSectionGroup bsg = (BindingsSectionGroup)config.GetSection("system.serviceModel/bindings");
foreach (BasicHttpBindingElement be in bsg.BasicHttpBinding.Bindings)
// Check binding information
You can also define your custom section handler and use this approach to read the configuration data when the .svc file and web.config are deployed to some nested locations.