Trouble with Azure on Development Server
I'm having a bit of trouble making Azure work on the dev server. I have a Silverlight app that I would lik开发者_开发知识库e 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?
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();
}
}
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.
精彩评论