Erstellen und hosten einer Website mit Azure in 30 Minuten

Auf unserer Schabus & Friends Tour konntet ihr schon “AzureGram” sehen, mein persönlicher Konkurrent zu bestehenden Foto-Galerie und Filter Apps. Für alle die nicht live dabei waren gibt es hier nochmal Schritt für Schritt die Vorgehensweise eine Webseite von Anfang bis Ende in 30 Minuten auf Azure online zu stellen.

Die Idee

Im Zeitalter von Instagram und Co. möchte ich natürlich selbst eine online Fotogalerie erstellen. Der User soll die Möglichkeit haben Fotos hochzuladen die dann auf einer Foto-Wand angezeigt werden. Im Hintergrund, weil es grad in Mode ist, soll ein Filter (z.b Schwarz-Weiß, Antique, etc) auf das Foto gelegt werden.

Voraussetzungen

Um dieser Anleitung selbst zu Folgen braucht ihr Visual Studio, eine Azure Subscritptionund 30 Minuten Zeit.

Die Umsetzung

Auf Microsoft Azure können alle möglichen
20140625 (1) Webseiten mit allen möglichen Sprachen und Frameworks gehostet werden, es ist aber kein Geheimnis, dass das Ganze mit Microsoft Produkten natürlich am besten funktioniert. Ich beginne mein Vorhaben also mit einer leeren ASP.Net MVC (Model-View-Controller) Applikation. Schon beim erstellen des Projekts in Visual Studio bekomme ich die Option gleich im Hintergrund eine Azure Website zu erstellen.

Natürliche könnte ich auch erst später mit zwei Klicks meine Website auf Azure veröffentlichen.

Hier ist zu beachten dass ich eine Azure Website benutze und nicht eine Virtuelle Maschiene, als Plattform-as-a-Service. Mehr dazu gibt's hier

Im MVC Pattern ist es üblich mit Erstellung des Models zu beginnen, das was in der Datenbank gespeichert werden soll. In meinem Fall ist das das Foto des Users, also eine Submission mit einer URL zum Foto. Das Model sieht also so aus:

    1: public class Submission   2:     {   3:         public int ID { get; set; }   4:         public string PhotoUrl { get; set; }   5:     }

Für die SQL Datenbank stehen mir 20MB gratis zur Verfügung, mehr als genug um jede Menge Strings zu speichern. Aus diesem Grund lege ich auch die Fotos, also Bitmaps, nicht direkt in das Model sondern in den kostengünstigeren Blob Storage.
20140625 (2)Controller und Views können in Visual Studio automatisch erzeugt werden. Rechtsklick auf den Controller Ordner und Auswählen der Option Add Controller öffnet einen Dialog in dem ich Controller und Views für mein gerade erstelltes Model generieren kann.

Damit werden die CRUD Operationen (Create, Read, Update, Delete, also die Basis Operationen auf persistenten Speicher), eine Kontext Klasse, Views und eine Referenz auf eine lokale Datenbank automatisch angelegt.

 

Die lokale Datenbank ersetze ich gleich mit einer produktiven SQL Datenbank die in Azure gehostet wird. Dazu navigiere ich im Management Portal zur gerade erstellten Website und füge unter Linked Resources durch Folgen des Wizards beim Klick auf „Link“ eine neue SQL Datenbank (für das Model) und einen neuen Storage Account (für die Fotos) hinzu.

20140625 (3)

Jetzt müssen nur noch in der Datei web.config die Connection Strings zur Datenbank und Storage Account angepasst werden angepasst werden.

    1: <connectionStrings>   2:     <add name="DEFAULTCONNECTION" connectionString="Server=tcp:tla6cf1wp4.database.windows.net,1433;Database=YOURDBNAME;User ID=YOURUSERID@YOURDBSERVER;Password=YOURPASSWORD;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" providerName="System.Data.SqlClient" />   3:     <add name="StorageConnection" connectionString="DefaultEndpointsProtocol=https;AccountName=YOURACCOUNTNAME;AccountKey=YOURKEY"/>   4: </connectionStrings>

Diese sind wieder im Management Portal unter den Dashboards der einzelnen Ressourcen zu finden.

20140625 (4)

Im Code kümmere ich mich jetzt noch um den Upload der Bilder. Dazu ändere ich erst einmal die View (die Datei Submissions/Index.cshtml) und füge ein HTML Form Element hinzu um Fotos hochzuladen. Der Foto-Upload Button wird an die entsprechende Controller CRUD Operation, also Create, gebunden.

    1: @using (Html.BeginForm("Create", "Submissions",   2:          FormMethod.Post, new { enctype = "multipart/form-data" }))   3:          {   4:              @Html.AntiForgeryToken()   5:              @Html.ValidationSummary(true)   6:              <fieldset>n   7:                  <div class="control-group">   8:                      <label class="control-label" for="filebutton">Photo Upload</label>   9:                      <div class="controls">  10:                          <input id="filebutton" name="photo" class="input-file" type="file">  11:                      </div>  12:                  </div>  13:                  <div class="control-group">  14:                      <div class="controls">  15:                          <button id="singlebutton" type="submit" name="singlebutton" class="btn btn-primary">Submit</button>  16:                      </div>  17:                  </div>  18:              </fieldset>  19:          }

Wenn ich schon dabei bin erstelle ich auch gleich die Foto-Wand durchs iterieren der Submissions in der Datenbank, in umgekehrter Reihenfolge sodass neuere Bilder am Anfang sind.

    1: @foreach (var item in Model.Reverse())   2:        {   3:            <div class="col-lg-3 col-md-4 col-xs-6 thumb">   4:                <div class="thumbnail">   5:                    <img src="@item.PhotoUrl" />   6:                </div>   7:            </div>   8:        }

Mit wenigen Zeilen Code wir dann das Foto in den verlinkten Blob Storage hochgeladen und die Submission in der Datenbank gespeichert. Das ist, wie fast alles in Azure, übrer Restful Services möglich. Für .NET gibt es natürlich SDKs die uns das Leben einfacher machen.

    1: PM> Install-Package WindowsAzure.Storage
    1: // POST: Submissions/Create   2: // Uploads Photo to Blob Storage and saves Model to DB   3: [HttpPost]   4: [ValidateAntiForgeryToken]   5: public async Task<ActionResult> Create([Bind(Include = "ID,PhotoUrl")] Submission submission, HttpPostedFileBase photo)   6: {   7:     CloudStorageAccount storageAccount = CloudStorageAccount.Parse("YOUR STORAGEACCOUNT CONNECTION STRING");   8:     // Create the blob client and reference the container   9:     CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();  10:     CloudBlobContainer container = blobClient.GetContainerReference("images");  11:     // Upload image to Blob Storage  12:     string imageName = String.Format(photo.FileName.Replace("\\", "")); //Backslashes not allowed in Blob Storage  13:     CloudBlockBlob blockBlob = container.GetBlockBlobReference(imageName);  14:     blockBlob.Properties.ContentType = photo.ContentType;  15:     await blockBlob.UploadFromStreamAsync(photo.InputStream);  16:    17:     submission.PhotoUrl = blockBlob.Uri.ToString();  18:       19:     if (ModelState.IsValid)  20:     {  21:         db.Submissions.Add(submission);  22:         db.SaveChanges();  23:         return RedirectToAction("Index");  24:     }  25:     return View(submission);  26: }

Mit Rechtsklick auf das Project und Publish auswählen und das Frontend ist Online.

Die Filter

Es fehlt nun nur noch das Anwenden der Filter. Die Offensichtliche Lösung wäre das vor dem Hochladen der Bilder zu erledigen, solche CPU-Intensiven Aufgaben könnten allerdings die Performance der Website negativ beeinflussen. Ablöse bieten hier Azure Webjos, eine skalierbare Lösung für Hintergrundaufgaben bei Azure Websites. Webjobs sind Programme die auf gewisse Events wie z.B neuer Blob im Storage oder neue Nachricht in einer Queue feuert und spezifische Funktionen ausführt. Ich habe mich für die Methode mit der Queue entschieden und schreibe beim Hochladen der Bilder eine Nachricht mit Name und URL vom Bild in die Queue.

    1: public class AzureGramQueueMsg   2:    {   3:        public string url { get; set; }   4:        public string name { get; set; }   5:    }

Dazu erweitere ich die Create Methode im Controller nach dem Upload des Fotos.

    1: //queue   2: CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();   3: CloudQueue queue = queueClient.GetQueueReference("imgqueue");   4: queue.CreateIfNotExists();   5: //add msg to queue   6: AzureGramQueueMsg azureGramQueueMsg = new AzureGramQueueMsg()   7: {   8:     name = imageName,   9:     url = fullPath  10: };  11: JavaScriptSerializer serializer = new JavaScriptSerializer();  12: CloudQueueMessage message = new CloudQueueMessage(serializer.Serialize(azureGramQueueMsg));  13: queue.AddMessage(message);

Der Webjob soll die Nachricht aus der Queue holen, das Bild herunterladen und einen zufälligen Filter hinzufügen (ich verwende hierfür das aForge Imaging Package), und das ursprüngliche Bild überschreiben. Ich könnte das in vielen verschiedenen Sprachen programmieren da ja alles über RESTful Services erreichbar ist. Ich entscheide mich hier aber für die bequemste Methode, nämlich eine .Net Konsolenapplikation mit den entsprechenden SDKs.

    1: Install-Package Microsoft.Azure.Jobs –Pre
    1: class Program   2:     {   3:         static void Main(string[] args)   4:         {   5:             //access String from Storage Account   6:             string acs = "YOUR STORAGEACCOUNT CONNECTION STRING";   7:             JobHost host = new JobHost(new JobHostConfiguration(acs)); // From nuget: Microsoft.WindowsAzure.Jobs.Host   8:             host.RunAndBlock();   9:         }  10:         public static void ApplyFilter([QueueTrigger("imgqueue")] AzureGramQueueMsg msg, IBinder binder)  11:         {  12:             WebClient wc = new WebClient();  13:             byte[] bytes = wc.DownloadData(msg.url);  14:             MemoryStream ms = new MemoryStream(bytes);  15:             Stream output = binder.Bind<Stream>(new BlobAttribute("images/"+msg.name));  16:             using (var bitmap = new Bitmap(ms))  17:             {  18:                 Random r = new Random();  19:                 int rInt = r.Next(0, 3);  20:                 switch (rInt)  21:                 {  22:                     case 0:        23:                         Pixellate pfilter = new Pixellate();                          24:                         pfilter.ApplyInPlace(bitmap);  25:                         break;  26:                     case 1:  27:                         Jitter jfilter = new Jitter(4);  28:                         jfilter.ApplyInPlace(bitmap);  29:                         break;  30:                     case 2:   31:                         Sepia sfilteR = new Sepia();  32:                         sfilteR.ApplyInPlace(bitmap);  33:                         break;  34:                     case 3:  35:                         Invert ifilter = new Invert();  36:                         ifilter.ApplyInPlace(bitmap);  37:                         break;  38:                 }  39:                 new Bitmap(bitmap).Save(output, ImageFormat.Png);  40:             }  41:         }

Jetzt noch einmal das Programm ausführen um die Binaries zu erzeugen (die sind dann im /bin Ordner zu finden), diese in .zip Datei Packen und im Management Portal hochladen und unser Programm ist fix fertig. 20140625 (5)

Zusammenfassung

In wenigen Minuten habe ich eine Website erstellt und auch gehostet, sie ist unter https://azuregramlive.azurewebsites.net/ zu finden. Durch verwenden von Platform-as-a-Service musst ich mich fast überhaupt nicht um Infrastruktur kümmern und konnte mich Großteils dem Code widmen. Den Source Code findet ihr auf Github. Um das Projekt auszuführen müsst Ihr die hier Azure Datenbanken und Storage Accounts durch eure eigenen ersetzten. Hier noch eine Anleitung wie man zu einer Azure Subscription kommt.