0

I'm trying to use Azure storage locally. I have a data source class called ExpenseDataSource:

public class ExpenseDataSource
{
    private static CloudStorageAccount storageAccount;
    private ExpenseTableContext context;

    static ExpenseDataSource()
    {
        //CloudStorageAccount.SetConfigurationSettingPublisher(
        //    (configName, configSettingPublisher) =>
        //    {
        //        string connectionString = RoleEnvironment.GetConfigurationSettingValue(configName);
        //        configSettingPublisher(connectionString);
        //    }
        //);

        storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

        CloudTableClient.CreateTablesFromModel(typeof(ExpenseTableContext), storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
    }

    public ExpenseDataSource()
    {
        context = new ExpenseTableContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
        context.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));
    }

    public IEnumerable<ExpenseInfo> Select()
    {
        var results = from g in context.Expenses
                      where g.PartitionKey == "Expense"
                      select g;

        return results;
    }
    // ...
}

(I'm new to Azure, so this class could be sub-optimal in many ways.)

When I try to create an object of type ExpenseDataSource, the following exception occurs:

System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.InvalidOperationException: SetConfigurationSettingPublisher needs to be called before FromConfigurationSetting can be used
   at Microsoft.WindowsAzure.CloudStorageAccount.FromConfigurationSetting(String settingName)
   at WebRole1.ExpenseDataSource..cctor() in [ ... ]
   --- End of inner exception stack trace ---
   at WebRole1.ExpenseDataSource..ctor()
   at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in [ ... ]

However, this is odd, because SetConfiguationSettingPublisher has already been called:

public class WebRole : RoleEntryPoint
{
    public override bool OnStart()
    {
        DiagnosticMonitor.Start("DiagnosticsConnectionString");

        // For information on handling configuration changes
        // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
        RoleEnvironment.Changing += RoleEnvironmentChanging;

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

        return base.OnStart();
    }
    // ...
 }

I am able to hit breakpoints here when I start debugging.

What am I doing wrong here?

Update: I thought that maybe I'd started the dev fabric and ASP.NET localhost out of order, so I killed them both, launched the dev fabic, then launched the ASP project. Still no luck - the same error occurs.

Update 2: I changed my OnStart() to this, but it still doesn't work:

  public override bool OnStart()  
    {
        DiagnosticMonitor.Start("DiagnosticsConnectionString");

        // For information on handling configuration changes
        // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
        RoleEnvironment.Changing += RoleEnvironmentChanging;

        #region Setup CloudStorageAccount Configuration Setting Publisher

        // This code sets up a handler to update CloudStorageAccount instances when their corresponding
        // configuration settings change in the service configuration file.
        CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
        {
            // Provide the configSetter with the initial value
            configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

            RoleEnvironment.Changed += (sender, arg) =>
            {
                if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                    .Any((change) => (change.ConfigurationSettingName == configName)))
                {
                    // The corresponding configuration setting has changed, propagate the value
                    if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                    {
                        // In this case, the change to the storage account credentials in the
                        // service configuration is significant enough that the role needs to be
                        // recycled in order to use the latest settings. (for example, the 
                        // endpoint has changed)
                        RoleEnvironment.RequestRecycle();
                    }
                }
            };
        });
        #endregion

        return base.OnStart();
    }

Update 3: I tried putting the "Setup CloudStorageAccount Configuration Setting Publisher" region in the ExpenseDataSource static initializer, and got the following error:

System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.Runtime.InteropServices.SEHException: External component has thrown an exception.
   at RoleEnvironmentGetConfigurationSettingValueW(UInt16* , UInt16* , UInt32 , UInt32* )
   at Microsoft.WindowsAzure.ServiceRuntime.Internal.InteropRoleManager.GetConfigurationSetting(String name, String& ret)
   at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue(String configurationSettingName)
   at WebRole1.ExpenseDataSource.<.cctor>b__0(String configName, Func`2 configSetter) in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 26
   at Microsoft.WindowsAzure.CloudStorageAccount.StorageAccountConfigurationSetting..ctor(String configurationSettingName)
   at Microsoft.WindowsAzure.CloudStorageAccount.FromConfigurationSetting(String settingName)
   at WebRole1.ExpenseDataSource..cctor() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 47
   --- End of inner exception stack trace ---
   at WebRole1.ExpenseDataSource..ctor()
   at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseService.svc.cs:line 18

Update 3: Following smarx's suggestion, I changed the static initializer:

    static ExpenseDataSource()
    {
        //storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
        storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"));

        CloudTableClient.CreateTablesFromModel(typeof(ExpenseTableContext), storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
    }

This leads to the following error:

System.TypeInitializationException: The type initializer for 'WebRole1.ExpenseDataSource' threw an exception. ---> System.Runtime.InteropServices.SEHException: External component has thrown an exception.
   at RoleEnvironmentGetConfigurationSettingValueW(UInt16* , UInt16* , UInt32 , UInt32* )
   at Microsoft.WindowsAzure.ServiceRuntime.Internal.InteropRoleManager.GetConfigurationSetting(String name, String& ret)
   at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue(String configurationSettingName)
   at WebRole1.ExpenseDataSource..cctor() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseDataSource.cs:line 20
   --- End of inner exception stack trace ---
   at WebRole1.ExpenseDataSource..ctor()
   at WebRole1.ExpenseService.WebRole1.IExpenseService.GetExpenses() in C:\Users\ODP\Documents\Visual Studio 2010\Projects\ExpenseCalc\WebRole1\ExpenseService.svc.cs:line 18

The error is slightly different from above. Could this be related to the idea that I'm somehow not actually running ASP.NET within the dev fabric?

Ugh. I'm starting to miss Google App Engine storage's simple get() and put() interface.

Nick Heiner
  • 119,074
  • 188
  • 476
  • 699
  • Do the breakpoints get hit in the correct order? – SLaks Oct 11 '10 at 23:39
  • Not sure. The `OnStart()` breakpoint only gets hit when I start the Azure dev fabric for the first time. (Even if I de-attach the debugger, I guess that doesn't kill the role, so the `OnStart()` wouldn't be called on subsequent times.) I am never able to hit breakpoints set in the WCF service or `ExpenseDataSource`. – Nick Heiner Oct 12 '10 at 00:56
  • I'm a bit confused... are you sure you're starting the cloud project each time and not the ASP.NET project? (You can open the dev fabric UI and verify that your app is running.) It sounds from what you're saying that you might be running the ASP.NET project _outside_ of the dev fabric. Also, you might consider using .Parse(RoleEnvironment.GetConfigurationSettingValue(...)) instead of .FromConfigurationSetting(...). The former doesn't require the setting publisher, but it also doesn't automagically fix things when you change your storage key at runtime. – user94559 Oct 12 '10 at 01:18
  • @smarx Yeah, it sounds like I might be running the ASP.NET project outside of the dev fabric. How can I verify/fix this? – Nick Heiner Oct 12 '10 at 01:19
  • 1
    I seem to remember something about static constructors and config settings. It's something to do with the order things are called in. Just to see if it is that problem I'd try moving what's currently static to the actual constructor (not a good long term solution I know) – knightpfhor Oct 12 '10 at 01:44
  • @knightpfhor Interesting idea, but I tried that and am getting the same error. – Nick Heiner Oct 12 '10 at 02:04
  • @Rosarch, just make sure the cloud project is the startup project (bolded) before you F5. – user94559 Oct 12 '10 at 03:08
  • @smarx It's been that way for a while but it still doesn't work. – Nick Heiner Oct 12 '10 at 06:26
  • The SEHException almost certainly means you're not actually running under the dev fabric. – user94559 Oct 12 '10 at 16:06
  • Take a look at this reply, it worked for me http://stackoverflow.com/questions/2957938/why-am-i-getting-sehexception-when-calling-roleenvironment-getconfigurationsetti – Muhammad Omar ElShourbagy Jun 06 '12 at 09:30

4 Answers4

2

1) Make sure that "DataConnectionString" is configured in your settings of WebRole.

  • In your Solution Explorer --> Under the "Roles" folder --> Right-click on | Properties --> Go to Settings tab and click "Add Setting". Enter Name: "DataConnectionString"; Type:"ConnectionString"; Value:"UseDevelopmentStorage=true" (if you want to debug and use local storage) or if you are planning to migrate to Azure-enter your storage account details.

2) (In the above code - Remove the comment for SetConfigurationSettingPublisher). Your code should look like this:

CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
});
var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

Tango
  • 21
  • 2
1

I can think of two reasons:

  1. You are using Azure SDK 1.3 and the SetConfigurationSettingPublisher must be called in your Global.asax.cs Application_Start;
  2. You are not setting the Startup project as the *.CloudService one.
sth
  • 222,467
  • 53
  • 283
  • 367
0

Had the same problem, I didn't had the Azure project as start-up project.

As Muhammad Omar mention in his comment on the question, see this related question as well.

Community
  • 1
  • 1
Andrew
  • 5,395
  • 1
  • 27
  • 47
0

If you're still having problems, try actually selecting the Web Role under the Cloud Project and starting debugging from there, that has worked for me when I've had issues with other methods.

OperatorOverload
  • 724
  • 4
  • 17