2012年6月4日

Windows Azure - 如何解決"SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used”的問題

這篇原文是來自這裡,而真正的出處,是這裡

其實這個問題,主要是來自Windows Azure 1.2升級到1.3時發生的問題,主要的原因是因為Windows Azure 1.2所使用的iis機制和Windows Azure 1.3的iis不同的關係;雖然目前已經到了1.6 ( 搞不好過幾天又更新了 ),但只要使用到Windows Azure Storage,還是有可能會發生;其次,也因為這樣的改變,所以之前小弟也一直搞不清楚這些的因果關係,查了一些資料後,小弟還是在這邊做個紀錄。

Windows Azure 1.2時代

Windows Azure 1.2時,大家取得Windows Azure Storage會利用下面方式做取得,先利用SetConfigurationSettingPublisher設定連線字串,然後在真正要用到的地方,利用FromConfigurationSetting來取得Account。( FromConfigurationSetting方法的功用就是會利用SetConfigurationSettingPublisher設定好的資訊來傳會一個Account物件。 ),所以會這樣寫。

在WebRole.cs的onStart方法裡面,會這樣寫。( 這個WebRole.cs檔案,會控制Role的生命週期與管理,所以我們建立一個Cloud專案的時候,其實都會默默的幫我們加上這個Class在底下,而早期1.2的時代,我們會利用WebRole.cs裡面的OnStart方法,來撰寫一些Role被啟動,或是被調用時,處理的一些事情,所以我們也會把SetConfigurationSettingPublisher寫在裡面。 )

image

而早期,我們會將SetConfigurationSettingPublisher寫在WebRole.cs裡面的OnStart方法。

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

然後在主要的網頁裡面( 例如Default.aspx.cs ), 想要取得Account,會這樣寫。

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

當然,1.2是沒有問題的,但是這在Windows Azure 1.3之後,就會出錯了。

Windows Azure 1.3時代

左圖是1.2時代的架構圖,當時的Web Role是運作在HWC之上。而右邊是1.3的架構圖,WebRole.cs其實是運作在WallShos.exe這塊,但是真正的網頁執行環境,卻是會落在w3wp.exe這邊,也就是真正完整的IIS。 ( 早期的1.2版本,並不是完整的IIS )。

full iis app domain model

其實,這也就是為什麼1.2升級到1.3的時候,會出現下面這個錯誤。

SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used

image

這個原因其實很簡單,因為我們是在WebRole.cs裡面撰寫SetConfigurationSettingPublisher,來設定連線字串,但我們卻在另外一個地方使用FromCofigurationSetting來產生Account,所以當然會出現"要使用FormConfigurationSetting之前,必須先呼叫SetConfigurationSettingPublisher"的錯誤。

大家的解決方法

通常大家的解決方法,就是把原本的WebRole.cs裡面SetConfigurationSettingPublisher這段程式碼搬到Global.asax裡的Application_Start底下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure;

namespace WebRole
{
    public class Global : System.Web.HttpApplication
    {

        void Application_Start(object sender, EventArgs e)
        {
            // 應用程式啟動時執行的程式碼

            //如果要使用var account = CloudStorageAccount.FromConfigurationSetting("TestConnectionStrting");
            //來存取Account,則必須加入下面這些程式碼。
            Microsoft.WindowsAzure.CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
            });

        }

        void Application_End(object sender, EventArgs e)
        {
            //  應用程式關閉時執行的程式碼

        }

        void Application_Error(object sender, EventArgs e)
        {
            // 發生未處理錯誤時執行的程式碼

        }

        void Session_Start(object sender, EventArgs e)
        {
            // 啟動新工作階段時執行的程式碼

        }

        void Session_End(object sender, EventArgs e)
        {
            // 工作階段結束時執行的程式碼。 
            // 注意: 只有在 Web.config 檔將 sessionstate 模式設定為 InProc 時,
            // 才會引發 Session_End 事件。如果將工作階段模式設定為 StateServer 
            // 或 SQLServer,就不會引發這個事件。

        }

    }
}

其實看了上面的圖,就會了解,因為Global.asax和要使用Account的Default.aspx都是同一區阿!所以這樣調整後,就可以正確執行了。

另一種方法

其實如果沒有甚麼特別需求,小弟我也比較喜歡官方推薦的這種方法,這個方法結合了上面需要在各個地方設置的SetConfigurationSettingPublisher和FromConfigurationSetting,在需要的地方直接利用Parse來取得,算是最方便也是最推薦的了,可以看看下面程式碼。

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

基本上,大致上就是這樣了。

後記

因為這幾種方法,和錯誤訊息,是最容易在初學的時候搞不懂和發生的,像小弟第一次做的時候,常常就搞不懂,為什麼會有那麼多種寫法,但了解時空背景後,就會比較清楚這些的用法,所以小弟我也在這邊紀錄一下。

參考資料

1 則留言: