One of my side projects was to get an MVC3 application that uses the Razor View Engine and Membership hosted on EC2 running Linux. I found some amazingly helpful resources along the way – particularly from Nathan Bridgewater at Integrated Web Systems.
Step one of the project is to get an EC2 instance prepped and ready. Basically I followed the cookbook instructions on Bridgewater’s site - Get Started with Amazon EC2, Run Your .Net MVC3 (RAZOR) Site in the Clould with Linux Mono.
The exact commands I used:
Create new AMI ID ami-ccf405a5 and associate elastic IP (xx.xx.xx.xx)
sudo apt-get update &;& sudo apt-get dist-upgrade –y
wget http://badgerports.org/directhex.ppa.asc
sudo apt-key add directhex.ppa.asc
sudo apt-get install python-software-properties
sudo add-apt-repository 'deb http://ppa.launchpad.net/directhex/ppa/ubuntu lucid main'
sudo apt-get update
sudo apt-get install mono-apache-server4 mono-devel libapache2-mod-mono
cd /srv
sudo mkdir www; cd www
sudo mkdir default
sudo chown www-data:www-data default
sudo chmod 755 default
cd /etc/apache2/sites-available/
sudo vi mono-default (see mono-default, change IP address)
cd /etc/apache2/sites-enabled
sudo rm 000-default
sudo ln -s /etc/apache2/sites-available/mono-default 000-mono
sudo mv /var/www/index.html /srv/www/default
sudo vi /srv/www/default/index.html
sudo apt-get install apache2
sudo service apache2 restart
Test in a browser via IP address (you should see the default apache page)
My mono default:
# xx.xx.xx.xx is my Elastic IP address
ServerName xx.xx.xx.xx
ServerAdmin myemail@domain.com
DocumentRoot /srv/www/default
MonoServerPath xx.xx.xx.xx "/usr/bin/mod-mono-server4"
MonoDebug xx.xx.xx.xx true
MonoSetEnv xx.xx.xx.xx MONO_IOMAP=all
MonoApplications xx.xx.xx.xx "/:/srv/www/default"
Allow from all
Order allow,deny
MonoSetServerAlias xx.xx.xx.xx
SetHandler mono
SetOutputFilter DEFLATE
SetEnvIfNoCase Request_URI "\.(?:gif|jpe?g|png)$" no-gzip dont-vary
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript
Step two is to test mono with a simple Asp.net page. Put this file into /srv/www/default. Edit with sudo and view via browser at http://xx.xx.xx.xx/test.aspx.
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>ASP.Net Test page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script runat="server">
private void Page_Load(Object sender, EventArgs e)
{
lblTest.Text = "This is a successful test.";
}
</script>
</head>
<body>
<h1>
This is a test page</h1>
<asp:Label runat="server" ID="lblTest"></asp:Label>
</body>
</html>
If problems are encountered check logs in /var/log/apache2/access.log or /var/log/apache2/error.log
Step three is to get MySql installed and tested with this simple application.
sudo apt-get install mysql-server
sudo apt-get install libmysql6.1-cil
CREATE DATABASE sample; USE sample;
CREATE TABLE test (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(25));
INSERT INTO sample.test VALUES (null, 'Lucy');
INSERT INTO sample.test VALUES (null, 'Ivan');
INSERT INTO sample.test VALUES (null, 'Nicole');
INSERT INTO sample.test VALUES (null, 'Ursula');
INSERT INTO sample.test VALUES (null, 'Xavier');
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'somepassword';
GRANT ALL PRIVILEGES ON sample.* TO 'testuser'@'localhost';
FLUSH PRIVILEGES;
Put this file into /srv/www/default. Edit with sudo and view via browser at
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="MySql.Data.MySqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>ASP and MySQL Test Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script runat="server">
private void Page_Load(Object sender, EventArgs e)
{
string connectionString = "Server=127.0.0.1;Database=sample;User ID=testuser;Password=somepassword;Pooling=false;";
MySqlConnection dbcon = new MySqlConnection(connectionString);
dbcon.Open();
MySqlDataAdapter adapter = new MySqlDataAdapter("SELECT * FROM test", dbcon);
DataSet ds = new DataSet();
adapter.Fill(ds, "result");
dbcon.Close();
dbcon = null;
SampleControl.DataSource = ds.Tables["result"];
SampleControl.DataBind();
}
</script>
</head>
<body>
<h1>Testing Sample Database</h1>
<asp:DataGrid runat="server" ID="SampleControl" />
</body>
</html>
Step four is to get the simplest possible MVC3 Razor application functioning on Ubuntu / EC2. Again Bridgewater has a more detailed explanation of what to do at his website linked here.
- Go into Visual Studio 2010 and create a new project MV3 / Razor project making no changes to the default project template.
- Build it and locally.
- Ensure that these references are set to “copy local”: System.Web.Mvc, System.Web.Helpers, and System.Web.Routing
- Copy System.Web.Razor, System.Web.WebPages, System.Web.WebPages.Razor, System.Web.WebPages.Deployment into your application’s bin directory. You will find these files in in C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies
- Publish the application to a scratch directory
- Copy the published application to your EC2 machine. I used git bash to tar (tarr –zcvf aws.tar.gz *) the files as Bridgewater recommends but could not get scp to work so I ftp’d the file over.
- On the EC2 machine cd /srv/www/default; sudo mv /home/ubuntu/aws.tar.gz; sudo tar –zxvf *.gz; sudo chown –R www-data;www-data *; sudo chmod 755 *; sudo service restart apache2 restart
- Confirm working from browser by checking default IP address http://xx.xx.xxx
- NB: I had to hit refresh several times before the application would work.
Step five is to use implement membership using MySQL.
- On your Windows machine. Edit the default controller and decorate it with the [Authorize] attribute.
- Edit your web.config shown below. This is where it can get hairy. If you want to run this locally on Windows you need to install the MySQL connector for .Net and Mono http://dev.mysql.com/downloads/connector/net/. Make sure that you reference system.web. On Ubuntu the application uses system.data. The trick is to add them both so you can run the same code on Ubuntu and Windows. Also notice that I’ve made database password clear text. As Nathan notes this is not a good practice.
- On the Ubuntu machine Go into MySQL and create a database called membership.
- Deploy the application to EC2 and test the application using step 4.
<?xml version="1.0"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=152368
-->
<configuration>
<connectionStrings>
<add name="Default"
connectionString="data source=127.0.0.1;user id=aspnet_user;
password=secret_password;database=membership;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" path="/" timeout="2880" />
</authentication>
<!--NOTE that "hashed" isn't supported with the public release of MySql.Web 6.3.5 under
Mono runtime. But I can't bring myself to share sample code that doesn't hash the
passwords by default.
The version included with this sample project is slightly modified to
allow hashed passwords in Mono. I highly recommend checking out the latest version of
MySql .NET Connector. http://dev.mysql.com
Also, I found that you have to rebuild MySql.Data and MySql.Web
using .NET 4.0 profile if you want it to work with Asp.Net 4.0 under Mono. This is a known bug and should
be published in upcoming versions of the connector. -->
<membership defaultProvider="MySqlMembershipProvider">
<providers>
<clear/>
<add name="MySqlMembershipProvider"
type="MySql.Web.Security.MySQLMembershipProvider, mysql.web"
connectionStringName="Default"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="true"
passwordFormat="hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
applicationName="/"
autogenerateschema="true"/>
</providers>
</membership>
<roleManager enabled="true" defaultProvider="MySqlRoleProvider">
<providers>
<clear/>
<add connectionStringName="Default"
applicationName="/"
name="MySqlRoleProvider"
type="MySql.Web.Security.MySQLRoleProvider, mysql.web"
autogenerateschema="true"/>
</providers>
</roleManager>
<profile>
<providers>
<clear/>
<add type="MySql.Web.Security.MySqlProfileProvider, mysql.web"
name="MySqlProfileProvider"
applicationName="/"
connectionStringName="Default"
autogenerateschema="true"/>
</providers>
</profile>
<pages>
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
<!--Don't forget to update this... I left it open to make it easier to debug.-->
<customErrors mode="Off"/>
</system.web>
<system.data>
<DbProviderFactories>
<clear/>
<add name="MySQL Data Provider"
description="ADO.Net driver for MySQL"
invariant="MySql.Data.MySqlClient"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data"/>
</DbProviderFactories>
</system.data>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>