We will cover some configuration issues when deploying MVC3/Razor Web Apps to the cloud
MVC Configuration
To get MVC 3/Razor working with Azure, follow these instructions:
Once you have a SQL Azure instance running, you will need to connect to it from the SQL Management Studio, ensure you set the default database (it is on master, so ensure you change it to your database name, else it will not connect).
Configuration e.g. Database Connection Strings, SMTP settings etc
When we are developing on a local machine without the development fabric, e.g. F5 from the web project and not from the cloud project, the configuration information will be coming from the web.config or app.config (worker role).
So we need to ensure that ALL cloud configuration information for connection strings and other configuration values are stored at the PACKAGE level i.e. ServiceConfiguration.cscfg
However, when you add key/value pairs to the ServiceConfiguration.cscfg you will need to define them in the ServiceConfiguration.csdef file as well, but without the values.
So lets get this setup
——————————————————————Web,config———————————————————————–
<connectionStrings> <add name="ApplicationServices" connectionString="Data Source=.;Initial Catalog=Readify.Romiko;Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> <add name="RomikoEntities" connectionString="metadata=res://*/Repository.Romiko.csdl|res://*/Repository.Romiko.ssdl|res://*/Repository.Romiko.msl;provider=System.Data.SqlClient;provider connection string="Data Source=.;Initial Catalog=Readify.Romiko;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" /> </connectionStrings>
——————————————————————————————————————————————————–
As we can see above, we got the EF connection string and a SQLMembershipProvider connection string.
Excellent, now lets define these key/value pairs in the ServiceConfiguration.csdef
Now, I use these connections from both a worker and web role, so you need to define it in the respective sections:
——————————————————————ServiceConfiguration.csdef ————————————————-
<ServiceDefinition name="Readify.Romiko.Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"> <WebRole name="Readify.Romiko.Web"> <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> <ConfigurationSettings> <Setting name="RomikoEntities" /> <Setting name="ApplicationServices" /> </ConfigurationSettings> </WebRole> <WorkerRole name="Readify.Romiko.Worker"> <Imports> <Import moduleName="Diagnostics" /> </Imports> <ConfigurationSettings> <Setting name="RomikoEntities" /> <Setting name="SMTPServer" /> <Setting name="SMTPServerPort" /> <Setting name="SMTPUser" /> <Setting name="SMTPPassword" /> </ConfigurationSettings> </WorkerRole> </ServiceDefinition>
———————————————————————————————————————————————————
As you can see above, I only need the EF connection string in the worker role, as I do not use forms authentication there! Also notice, there is no values set, this will be done in the csfg file:
——————————————————————ServiceConfiguration.cscfg —————————————————–
<Role name="Readify.Romiko.Web"> <Instances count="1" /> <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> <Setting name="RomikoEntities" value="metadata=res://*/Repository.Romiko.csdl|res://*/Repository.Romiko.ssdl|res://*/Repository.Romiko.msl;provider=System.Data.SqlClient;provider connection string="Data Source=ifx2adecne.database.windows.net;Initial Catalog=Romiko;Persist Security Info=True;User ID=RomikoApp;Password=St0rmyCloud@pp1"" /> <Setting name="ApplicationServices" value="Data Source=ifx2adecne.database.windows.net;Initial Catalog=Romiko;Persist Security Info=True;User ID=RomikoApp;Password=St0rmyCloud@pp1" /> </ConfigurationSettings> </Role> <Role name="Readify.Romiko.Worker"> <Instances count="1" /> <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> <Setting name="RomikoEntities" value="metadata=res://*/Repository.Romiko.csdl|res://*/Repository.Romiko.ssdl|res://*/Repository.Romiko.msl;provider=System.Data.SqlClient;provider connection string="Data Source=ifx2adecne.database.windows.net;Initial Catalog=Romiko;Persist Security Info=True;User ID=RomikoApp;Password=St0rmyCloud@pp1"" /> <Setting name="SMTPServer" value="smtp.mail.Romiko.com" /> <Setting name="SMTPServerPort" value="25" /> <Setting name="SMTPUser" value="Romiko@romiko.net" /> <Setting name="SMTPPassword" value="derbynew" /> </ConfigurationSettings> </Role>
———————————————————————————————————————————————————
Configuration Classes
Excellent, now we need a way to tell the runtime when to load data from the Azure configuration or the standard .NET configuration files.
All we do is create a helper class and interface, the interface will be used for IoC injection later with Autofac.
Now, the two concrete classes just fetch config info from seperate managers:
public class ConfigurationManagerAzure : IConfigurationManager { public string GetDatabaseConnectionString() { return RoleEnvironment.GetConfigurationSettingValue("RomikoEntities"); } public string AuthenticationProviderConnectionString() { return RoleEnvironment.GetConfigurationSettingValue("ApplicationServices"); } public SmtpSettings GetSmtpSettings() { var smtpSettings = new SmtpSettings(); smtpSettings.SmtpServer = RoleEnvironment.GetConfigurationSettingValue("SMTPServer"); smtpSettings.SmtpServerPort = RoleEnvironment.GetConfigurationSettingValue("SMTPServerPort"); smtpSettings.SmtpUser = RoleEnvironment.GetConfigurationSettingValue("SMTPUser"); smtpSettings.SmtpPassword = RoleEnvironment.GetConfigurationSettingValue("SMTPPassword"); return smtpSettings; } }
public class ConfigurationManagerLocal : IConfigurationManager { public string GetDatabaseConnectionString() { return System.Configuration.ConfigurationManager.ConnectionStrings["RomikoEntities"].ToString(); } public string AuthenticationProviderConnectionString() { return System.Configuration.ConfigurationManager.ConnectionStrings["ApplicationServices"].ToString(); } public SmtpSettings GetSmtpSettings() { var smtpSettings = new SmtpSettings(); smtpSettings.SmtpServer = System.Configuration.ConfigurationManager.AppSettings["SMTPServer"]; smtpSettings.SmtpServerPort = System.Configuration.ConfigurationManager.AppSettings["SMTPServerPort"]; smtpSettings.SmtpUser = System.Configuration.ConfigurationManager.AppSettings["SMTPUser"]; smtpSettings.SmtpPassword = System.Configuration.ConfigurationManager.AppSettings["SMTPPassword"]; return smtpSettings; } }
This is perfect, now what we can do is load the concrete class dynamically via AutoFac, so in the Global.asax.cs file we have something like this:
AutoFac (Web Role)
protected void Application_Start() { RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); var builder = new ContainerBuilder(); builder.RegisterControllers(Assembly.GetExecutingAssembly()); if (RoleEnvironment.IsAvailable) builder.RegisterType<ConfigurationManagerAzure>().AsImplementedInterfaces(); else builder.RegisterType<ConfigurationManagerLocal>().AsImplementedInterfaces(); builder.RegisterType<RomikoEntities>(); builder.RegisterType<AccountMembershipService>().AsImplementedInterfaces(); builder.RegisterType<Membership.AzureMembershipProvider>(); builder.RegisterType<FormsAuthenticationService>().AsImplementedInterfaces(); builder.Register(x => System.Web.Security.Membership.Provider).ExternallyOwned(); Container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(Container)); }
So, from the above we used the RoleEnvironment.isAvailable to detect if it is running on the cloud,, and then load the correct class types into the container.
Just ensure you controllers and helper classes have constructor’s for injecting the IConfiguration and Entities e.g.
Controller:
public HomeController(RomikoEntities entities) { this.entities = entities; }
Helper Class injection setup:
public class AlertManager { private readonly IConfigurationManager configurationManager; private readonly RomikoEntities entities; public PolicyAlertManager( IConfigurationManager configurationManager, RomikoEntities entities) { this.configurationManager = configurationManager; this.entities = entities; }
EF partial class extended
To get EF to use the connection string from the inject configuration concrete class we have this partial class:
public partial class RomikoEntities { public RomikoEntities(IConfigurationManager configurationManager) : this(configurationManager.GetDatabaseConnectionString()) { } }
As we get can, EF has a default constructor that we can call to pass in the connection string.
AccountController and SQLMembershipProvider
Now, we need to make some modifications to the default account controller that comes with an MVC project, if you choose the template.
First of all, we will ensure Autofac registers a method call:
builder.Register(x => System.Web.Security.Membership.Provider).ExternallyOwned();
This is used in the accountmodel class:
I modified the AccountController.cs to have the following constructor injection possible:
private IFormsAuthenticationService formsService; private IMembershipService membershipService; public AccountController(IMembershipService membershipService, IFormsAuthenticationService formsService) { this.MembershipService = membershipService; this.FormsService = formsService; } public IMembershipService MembershipService { get { return membershipService; } set { membershipService = value; } } public IFormsAuthenticationService FormsService { get { return formsService; } set { formsService = value; } }
Then we need to modify the AccountModel class to get the correct constructor parameters, since this is configured via the web.,config and it expects a paramterless constructor:
<membership> <providers> <clear /> <add name="AspNetSqlMembershipProvider" type="Readify.Romiko.Web.Membership.AzureMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" /> </providers> </membership>
So, we have a custom MemberShipProvider that inherits from SqlMembershipProvider:
AzureMembershipProvider
using System.Collections.Specialized; using System.Reflection; using System.Web.Security; using Autofac; using Readify.Romiko.Common.Configuration; namespace Readify.Romiko.Web.Membership { public class AzureMembershipProvider : SqlMembershipProvider { private readonly IConfigurationManager configuration; public AzureMembershipProvider() :this((MvcApplication.Container.Resolve<IConfigurationManager>())) { } public AzureMembershipProvider(IConfigurationManager configuration) { this.configuration = configuration; } public override void Initialize(string name, NameValueCollection config) { base.Initialize(name, config); var connectionString = configuration.AuthenticationProviderConnectionString(); var connectionStringField = typeof(SqlMembershipProvider).GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic); if (connectionStringField != null) connectionStringField.SetValue(this, connectionString); } } }
Notice, above, we have kept the constructor parameter less, but then we use AutoFac to resolve the configuration:
Note MVCApplication is the class name of the Global.asax.cs file! Also we have a static reference to the Container.
Excellent, so now we can inject configuration, however, the AccountModel.cs file needs some pimping as well, remember this resolver:
builder.Register(x => System.Web.Security.Membership.Provider).ExternallyOwned();
Well, we need to implement this in the Account.Model, so it is available, again, we will resolve it like so:
In the AccountModel.cs there is a class called: AccountMembershipService, we will modify the constructor to do some resolving:
public class AccountMembershipService : IMembershipService { private readonly MembershipProvider _provider; public AccountMembershipService() : this(MvcApplication.Container.Resolve<MembershipProvider>()) { } public AccountMembershipService(MembershipProvider provider) { _provider = provider ?? System.Web.Security.Membership.Provider; } β¦β¦..
So, as you can see we just use a parameter less constructor pattern and then within that constructor we resolve types and method/property calls.
So when Membership.Provider is called, the container will get the return value.
Autofac install with Nuget
In VS2010, we can go to View->Other Windows-> Package manager console, and install autofac into the various projects:
Note the default project, ensure you install to the respective projects:
Command to install is:
PM> install-package Autofac.Mvc3
You can read more about this here:
http://code.google.com/p/autofac/wiki/Mvc3Integration
SQL Azure Membership Role Database Setup
This is interesting, as you will need custom scripts that are azure friendly, e.g. SQL Azure does not use USE statements! Makes sense from a security perspective.
Updated ASP.net scripts for use with Microsoft SQL Azure
http://support.microsoft.com/kb/2006191/en-us
AutoFac and Worker Roles:
This is easy to setup, just put the logic in the OnStart Method of the WorkerRole.cs file.
public override bool OnStart() { ServicePointManager.DefaultConnectionLimit = 12; //AutoFac Container var builder = new ContainerBuilder(); if (RoleEnvironment.IsAvailable) builder.RegisterType<ConfigurationManagerAzure>().AsImplementedInterfaces(); else builder.RegisterType<ConfigurationManagerLocal>().AsImplementedInterfaces(); builder.RegisterType<AlertManager>(); builder.RegisterType<RomikoCloudEntities>(); container = builder.Build(); return base.OnStart(); }
As you can see the Alertmanager constructor has all the interfaces defined
public class AlertManager { private readonly IConfigurationManager configurationManager; private readonly RomikoCloudEntities entities; public PolicyAlertManager( IConfigurationManager configurationManager, RomikoCloudEntities entities) { this.configurationManager = configurationManager; this.entities = entities; }
So, I hope this gets you going with MVC3, Azure and all the bells and whistles that it comes with.
Excellent – thanks for sharing! Shame I’ve just this last week written pretty much the same code – doh!
Thank you very much. This article save my time
You Welcome,
There is a better way to implement it, but perhaps that will be a discussion for another blog post π