Wed, 08 Dec 2010

How to Resolve “SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used” After Moving to Windows Azure SDK 1.3

If you’re receiving this exception after migrating a web role from Windows Azure SDK 1.2 to SDK 1.3, this may have to do with changes associated with the new full IIS model introduced in SDK 1.3. This post will help you to understand the change and how to use the FromConfigurationSetting method correctly under the full IIS model. If you want to simply go back to using the Hosted Web Core model (as in previous SDK releases), you can do that too.

The Full IIS Model

Full IIS in Windows Azure introduces new capabilities, such as hosting multiple web sites within a single web role and advanced IIS configuration. For a discussion of the new full IIS hosting model in Windows Azure SDK 1.3 and how it differs from the Hosted Web Core model used previously, see “New Full IIS Capabilities: Differences from Hosted Web Core” on the Windows Azure blog.

The following diagram from that post illustrates one difference that impacts how FromConfigurationSetting is used:

full iis app domain model

With the Hosted Web Core model, RoleEntryPoint code (e.g., WebRole.cs) and web application code (e.g., Default.aspx.cs for ASP.NET) were executed in the same app domain. Under full IIS, these are two distinct app domains. This means that static objects are no longer shared between these two parts of your application, and that’s the key to understanding how to use FromConfigurationSetting in the new full IIS hosting model.

Before: FromConfigurationSetting with Hosted Web Core

The Hosted Web Core model, where everything ran in the same app domain, lead to a common pattern for initializing CloudStorageAccount objects. This is how SDK 1.2 code was commonly written:

In the OnStart method of WebRole.cs:


CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSettingPublisher) =>
    {
        var connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
        configSettingPublisher(connectionString);
    }
);

(Your function may look more complicated than this, but the information in this post still applies.) Then, in Default.aspx.cs (or an ASP.NET MVC controller):

var account = CloudStorageAccount.FromConfigurationSetting("MyConnectionString");

This code worked, because the call to SetConfigurationSettingPublisher was made in the same app domain as the call to FromConfigurationSetting. However, under the full IIS hosting model, these are two different app domains.

After: FromConfigurationSetting with full IIS

A convenient place to put the call to SetConfigurationSettingPublisher is in the Application_Start method, which runs as part of the app domain where all your web application code runs. This method is located in Global.asax.cs. (If you don’t have a Global.asax.cs already, you can add one by right-clicking on the web application project, choosing “Add,” “New Item,” and then “Global Application Class.”)

void Application_Start(object sender, EventArgs e)
{
    CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSettingPublisher) =>
        {
            var connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
            configSettingPublisher(connectionString);
        }
    );
}

This is exactly the same code that would have been in WebRole.cs, just moved to a location that’s part of the web application’s app domain.

An Alternative

The combination of SetConfigurationSettingPublisher and FromConfigurationSetting abstracts the location of the configuration setting. Some people use this to write code that works both inside and outside of Windows Azure, reading configuration settings from Windows Azure when available and falling back to web.config or some other location otherwise.

If you don’t need this abstraction layer, you can use another method of initializing CloudStorageAccount objects from configuration settings:

var account = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("MyConnectionString"));