February 13, 2014

Role of application configuration file in Azure Web role

In the last post we saw how to access and modify web.config file associated with Azure Web Role and application configuration file associated with Azure Worker Role after deployment.

In this post we will see the role of application configuration file in a Web Role.

In any Windows Azure Web Role project there are two categories of code, which runs under different process once deployed to the Web Role virtual machine.

Category-1:
   The website related code runs under a normal IIS 'w3wp.exe' process.

Category-2:
    All 'RoleEntryPoint' related code (i.e. code in OnStart, OnRun, Stop methods) will be running under the process 'WaIISHost.exe'.

The Visual Studio Web Role project compiles into a single DLL (e.g. NorthWindWebRole.DLL), but parts of code runs under two different processes as described above.

Following screenshot shows these two processes in Web Role virtual machine, we can see both are loading the Web Role DLL.





When category-1 code [ running under IIS (w3wp.exe) ] try to read configuration values using 'ConfigurationManager', it loads web.config and looks for the given setting. For example Entity Framework context class uses ConfigurationManager to read the default DB connection string.

When category-2 code [ running under 'WaIISHost.exe'] try to read configuration values using 'ConfigurationManager', it will read from an application configuration file with name '<assembly-name>.dll.config. For example, if the name of Web Role DLL is NorthWindWebRole.DLL, then the file that 'ConfigurationManager' try to load and read from will be 'NorthWindWebRole.dll.config'.



When we create Web Role project using Visual Studio, a web.config file will be added to the project by VS , but not application configuration file.

If any code in your 'RoleEntryPoint' methods uses directly or indirectly 'ConfigurationManager' then you need to make sure you are explicitly adding an application configuration file with name same as the compiled DLL (with .config extension) and define required settings in this file. Don't forget to set 'Build Action' property for this file to 'Content' and 'Copy To Output Directory' property to 'Copy Always'.

You may ask why we need to add such explicit application configuration file when we have an option to define and declare settings using Azure service definition and service configuration file, which can be read using RoleEnvironment.GetConfigurationSettingValue. One of the situation where we have hard dependency in application configuration file is when we use a third party assembly in our 'RoleEntryPoint' methods which internally uses .NET 'ConfigurationManager' to read its own settings.

For example, when we enable 'Windows Azure In-Role caching' in a Web Role project we can see following settings gets added to web.config file.



When we create an instance of 'DataCacheFactory' (to access cache) in the web application (i.e. from Category-1 code), it internally uses 'ConfigurationManager' to read the above settings from web.config.

Now consider the scenario where we want to access the cache from 'RoleEntryPoint' methods. Creating an instance of 'DataCacheFactory' from RoleEntryPoint' methods  (i.e. from Category-2 code) will fail since 'DataCacheFactory' constructor try to read required settings using 'ConfigurationManager' class that try to load application configuration file which does not exits. This can be fixed by adding application configuration file as explained above.

February 7, 2014

Changing web.config file deployed on Windows Azure WebRole



Some times we might want to change settings in web.config file used by our web application after deploying it to Windows Azure Web Role. This post will take you through the required steps by using an ASP .NET MVC application with Entity Framework as an example.

A less interesting scenario will be changing settings in the 'App.config' file after deploying our application to Windows Azure Worker Role. Once we understand how to work with web.config in Web Role, its very easy to apply the same concept for App.config. At the end of this article I have explained working with App.config. Let us start with the main topic i.e. working with web.config in Web Role.

When we generate Entity Framework Code First model classes from an existing database, we have an option to save the data base connection string along with sensitive data (user name and password) in web.config file.

For example below diagram shows the final screen of  'Entity Model Wizard' asking developer's permission for storing connection string with user id and password as plain text in the web.config:







Once we are done with generating the model classes, we can see connection string get added in web.config - in my case with name 'NorthWindEntities' since I specified this name in the 'Entity Model Wizard' as the name for connection string.







If we open the generated context class, we can see the default constructor calls base class by passing name of the connection string i.e. NorthWindEntities. This means if application create context class using the default constructor, entity framework will use the above connection string.



Once we deploy this application to Windows Azure, it will use the database 'NorthWind-Test' hosted in my SQL Azure server (***.database.windows.net)

Suppose 'NorthWind-Test' is my test data base and once we tested and found application is working fine in Azure, we want to change the database to 'NorthWind-Production' (which is hosted in my production SQL Azure Server). This requires changing the connection string 'NorthWindEntities' in the web.config file deployed on Azure.

To update connection string in web.config programmatic-ally we need to find the location of web.config in Azure deployment. Following screenshot is taken from a web role after RDPing into it, which clearly shows where 'web.config' file is located.



Given below code to get path to web.config (You can map it to above picture):

        public static string RoleRootDir
        {
            get
            {
                return Environment.GetEnvironmentVariable("RdRoleRoot");
            }
        }

        public static string RoleModelFile
        {
            get
            {
                return Path.Combine(RoleRootDir, "RoleModel.xml");
            }
        }
        public static string WebsiteDir
        {
            get
            {
XNamespace _roleModelNs =
  "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
                XDocument roleModelDoc = XDocument.Load(RoleModelFile);
                var siteElements = roleModelDoc.Root.Element(_roleModelNs + "Sites").Elements(_roleModelNs + "Site");

                var result =
                    from siteElement in siteElements
                    where siteElement.Attribute("name") != null
                            && siteElement.Attribute("name").Value == "Web"
                            && siteElement.Attribute("physicalDirectory") != null
                    select Path.Combine(RoleRootDir, siteElement.Attribute("physicalDirectory").Value);

                return result.First();
            }
        }
  

        public static string WebConfigFile
        {
            get
            {
                return Path.Combine(WebsiteDir, "web.config");
            }
        }

Now we know how to get path to web.config, we can easily load web.config to XmlDocument, look for the connection settings with name 'NorthWindEntities' and update it. Below function shows how to do this.

        public static void SetConnectionString(string connectionString)
        {
            var xDoc = XDocument.Load(WebConfigFile);
            var xElement = (from z in xDoc.Root.Element("connectionStrings").Elements()
                            where (z.Attribute("name").Value == "NorthWindEntities") select z).SingleOrDefault();
            xElement.Attribute("connectionString").Value = connectionString;
            xDoc.Save(WebConfigFile);
        }

We need to build the connection string and call the above function to set it in the web.config file. I have declared and defined settings to hold DB server name, DB name, user name and password in Azure service definition and configuration file associated with my cloud project.

ServiceDefinition.csdef

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="NorthWindMVC"
                   xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
                   schemaVersion="2013-10.2.2">
  <WebRole name="MvcWebRoleNorthWind" vmsize="Small">
    <Sites>
      <Site name="Web">
        <Bindings>
          <Binding name="Endpoint1" endpointName="Endpoint1" />
        </Bindings>
      </Site>
    </Sites>
    <Endpoints>
      <InputEndpoint name="Endpoint1" protocol="http" port="80" />
    </Endpoints>
    <Imports>
      <Import moduleName="Diagnostics" />
    </Imports>
    <Runtime executionContext="elevated" />
    <ConfigurationSettings>
      <!-- Entity framework DB configuration -->
      <Setting name="NorthWind.DBConfiguration.Server" />
      <Setting name="NorthWind.DBConfiguration.Host" />
      <Setting name="NorthWind.DBConfiguration.Database" />
      <Setting name="NorthWind.DBConfiguration.User" />
      <Setting name="NorthWind.DBConfiguration.Password" />
    </ConfigurationSettings>
  </WebRole>
</ServiceDefinition>

ServiceConfiguration.[Local|Cloud].cscfg

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="NorthWindMVC"
                      xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
                      osFamily="3"
                      osVersion="*"
                      schemaVersion="2013-10.2.2">
  <Role name="MvcWebRoleNorthWind">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="****" />
      <!-- Entity framework DB configuration -->
      <Setting name="NorthWind.DBConfiguration.Server" value="<server-name>" />
      <Setting name="NorthWind.DBConfiguration.Host" value="database.windows.net" />
      <Setting name="NorthWind.DBConfiguration.Database" value="<db-name>" />
      <Setting name="NorthWind.DBConfiguration.User" value="<user-name>" />
      <Setting name="NorthWind.DBConfiguration.Password" value="<password>" />
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>


Now we can read these configurations from RoleEnvironment, build the connection string and call 'SetConnectionString'

        public static void UpdateConnectionString()
        {

            string connectionFormat =    "metadata=res://*/NorthWindModel.csdl|res://*/NorthWindModel.ssdl|res://*/NorthWindModel.msl;provider=System.Data.SqlClient;provider connection string=\"data source={0}.{1};initial catalog={2};user id={3}@{0};password={4};multipleactiveresultsets=True;application name=EntityFramework\"";

            string connectionString = String.Format(
                connectionFormat,
RoleEnvironment.GetConfigurationSettingValue("NorthWind.DBConfiguration.Server"),            RoleEnvironment.GetConfigurationSettingValue("NorthWind.DBConfiguration.Host"),              RoleEnvironment.GetConfigurationSettingValue("NorthWind.DBConfiguration.Database"),          RoleEnvironment.GetConfigurationSettingValue("NorthWind.DBConfiguration.User"),              RoleEnvironment.GetConfigurationSettingValue("NorthWind.DBConfiguration.Password"));

         SetConnectionString(connectionString);

        }

Only remaining question is when to call above function? we need to call this function whenever we changes values for the settings 'NorthWind.DBConfiguration.*' from Windows Azure portal or any other tool after the deployment.



When user update any configuration (after changing settings and clicking 'Save' from portal configuration page), below events will be called by role run-time:

RoleEntryPoint::RoleEnvironment_Changing(object sender, RoleEnvironmentChangingEventArgs e)
RoleEntryPoint::RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)


The RoleEnvironment_Changing event and the RoleEnvironment_Changed event are used together to identify and manage configuration changes to the service model.

We can call 'UpdateConnectionString' function from  'RoleEnvironment_Changed'.

        void RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)
        {
            MyConfiguration.UpdateConnectionString();
        }


Using RoleEnvironment_Changing event, a role instance can respond to a configuration change in one of the following ways:

1. Accept the configuration change while it is running, without going offline.
2. Take the instance offline, apply the configuration change, and then bring the instance back online.

The connection string related changes in web.config file not requires instance to take offline. Role instance can inform role  run-time by setting Cancel property of RoleEnvironmentChangingEventArgs to false that it don't want to take the instance offline. 

        void RoleEnvironment_Changing(object sender, RoleEnvironmentChangingEventArgs e)
        {
            string[] exemptedCategories = {
                                              "NorthWind.DBConfiguration.Server",
                                              "NorthWind.DBConfiguration.Host",
                                              "NorthWind.DBConfiguration.Database",
                                              "NorthWind.DBConfiguration.User",
                                              "NorthWind.DBConfiguration.Password"
                                           };
            var changes = e.Changes.OfType<RoleEnvironmentConfigurationSettingChange>();
            e.Cancel =
              !changes.All(c => exemptedCategories.Contains(c.ConfigurationSettingName));

        }

Now hook the above two events in RoleEntryPoint::OnStart method

        public override bool  OnStart()
        {
            // Other app specific initialization code here..

            RoleEnvironment.Changing +
                new EventHandler<RoleEnvironmentChangingEventArgs>(RoleEnvironment_Changing);

            RoleEnvironment.Changed +
                new EventHandler<RoleEnvironmentChangedEventArgs>(RoleEnvironment_Changed);
        }


Note: Sometime you might want to call MyConfiguration.UpdateConnectionString() from OnStart() to set the DB connection string as a part of role initialization, in this case make sure you have following entry in the csdef file under WebRole node.

<Runtime executionContext="elevated" />

This will ensure OnStart() runs with admin privilege so that it can update web.config file. I have included this entry in the above sample csdef file.

That's all. :)

Working with App.config in Worker Role:

Now we know how to change web.config file in Web Role. If we want to configure App.config in similar line (when your application is in Worker Role), only thing we need to know is how to locate App.config file associated with your application.

One important point is once you build and host your application in Azure Worker Role the name of the app configuration file will not be App.config. Instead of App.config it will be '<assembly-name>.Dll.config. For example if name of your assembly is 'NorthWindWorkerRole' then configuration file name will be "NorthWindWorkerRole.Dll.config"

The application configuration file will be located under 'approot' (which is under role root)

        public static string AppRootDir
        {
            get
            {
                return Path.Combine(RoleRootDir, "approot");
            }

        }

We can use RoleModel.xml to find the name of the assembly, once we know assembly name then we just have to append '.config' to derive the application configuration file name. Now we can combine AppRootDir and configuration file name to get the full path.

        public static string AppConfigFile
        {
            get
            {
  XNamespace _roleModelNs =
      "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition";
                XDocument roleModelDoc = XDocument.Load(RoleModelFile);
                var netFxEntryElement =
                roleModelDoc.Root.Element(_roleModelNs + "Runtime").Element(_roleModelNs +  
                  "EntryPoint").Element(_roleModelNs + "NetFxEntryPoint");
                return Path.Combine(AppRootDir, netFxEntryElement.Attribute("assemblyName").Value + ".config");
            }
        }

Note: If you are interested in understanding more about configuration files in Web role see this post.