views:

19

answers:

2

I'm having a bit of trouble making Azure work on the dev server. I have a Silverlight app that I would like to connect to Azure, so I'm exposing a REST API from the Web Role.

Here is my service class:

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ExpenseService
{
    private ExpenseDataSource expenseData = new ExpenseDataSource();

    [OperationContract]
    [WebGet(UriTemplate="expenses", ResponseFormat=WebMessageFormat.Xml)]
    public List<String> GetExpenses()
    {
        return expenseData.Select().ToList();
    }
}

The type initialization of ExpenseDataSource is failing:

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);
    }
    // ...
}

The error is:

SEHException was caught

External component has thrown an exception.

I'm not sure what I'm doing wrong. I'm trying to follow along with this tutorial. The author describes needing to do something differently for non-Azure environments, like NUnit tests. Do I need to do something for running on the dev server? Should I just configure this app to use the real Azure storage, even though it's running off my machine?

Update: If I comment out the ExpenseDataSource constructor and just use fake data, the service works fine.

Update 2: @Maupertuis answered that I can't set up the storage account from a static initializer. However, this comes directly from a MS Windows Azure code sample:

public class GuestBookEntryDataSource
{
    private static CloudStorageAccount storageAccount;
    private GuestBookDataContext context;

    static GuestBookEntryDataSource()
    {
        storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");

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

Could this ever work?

A: 

As I said earlier (aaarg.. SO outage), you can't initialize your Cloud Apps from inside a static constructor, as the static constructor will be called on type initialization. Type initialization doesn't happen when the assembly is loaded, but when your type is used for the first time.

you should use create a class which derives from the class RoleEntryPoint. it's OnStart method will be called on Role startup.

Here is a sample:

public class WebRole: RoleEntryPoint
    {
        public override bool OnStart()
            {
            #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();
        }
    }
Maupertuis
Yeah the SO outage is a huge pain. Thanks for the answer, I'll try this. The reason I thought that would be ok is from a Windows Azure training kit that I downloaded from MSFT (posted above). Odd.
Rosarch
I put the `SetConfigurationSettingPublisher` call in `OnStart()`, but it's never called. I put a breakpoint there which is not hit. I thought that `OnStart()` always ran?
Rosarch
Is your WebRole class public? does it inherit from RoleEntryPoint? Is it the only RoleEntryPoint class of your app?
Maupertuis
Yes to all those. I'm pretty sure the problem is that the dev server was already running.
Rosarch
A: 

You shouldn't call SetConfigurationSettingPublisher from a static constructor. It is not guaranteed to run at all, and if it runs, there is no guarantee neither about when it will run.

If fact, there are guarantees that it will be executed on Type initialization. It's right before the first time that your class ExpenseDataSource is used. If you don't use it in your application, the static constructor won't be executed either. As SetConfigurationSettingPublisher must be called before the first request (if I remember well), you will understand that the static constructor isn't really the way to go.

You should create a class deriving from RoleEntryPoint, whose OnStart override will always be called by the Azure platform when it starts:

public class WebRole: RoleEntryPoint
    {
        public override bool OnStart()
        {
            #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();
        }
    }

It's an example, you can put here whatever initialization code is required by your application.

Maupertuis