This tutorial shows how to create a multi-tier ASP.NET MVC application that uses the WebJobs SDK to work with Azure queues and Azure blobs in an Azure Website. The application also uses Azure SQL Database.
The sample application is an advertising bulletin board. Users create an ad by entering text and uploading an image. They can see a list of ads with thumbnail images, and they can see the full size image when they select an ad to see the details. Here's a screenshot:
You can download the Visual Studio project from the MSDN Code Gallery.
The tutorial assumes that you know how to work with ASP.NET MVC or Web Forms projects in Visual Studio. The sample application uses MVC, but most of the tutorial also applies to Web Forms.
The tutorial instructions work with the following products:
If you don't have one of these, Visual Studio 2013 Express for Web will be installed automatically when you install the Azure SDK.
The tutorial shows how to do the following tasks:
The sample application uses the queue-centric work pattern to off-load the CPU-intensive work of creating thumbnails to a backend process.
The app stores ads in a SQL database, using Entity Framework Code First to create the tables and access the data. For each ad the database stores two URLs, one for the full-size image and one for the thumbnail.
When a user uploads an image, the frontend website stores the image in an Azure blob, and it stores the ad information in the database with a URL that points to the blob. At the same time, it writes a message to an Azure queue. A backend process running as an Azure WebJob uses the WebJobs SDK to poll the queue for new messages. When a new message appears, the WebJob creates a thumbnail for that image and updates the thumbnail URL database field for that ad. Here's a diagram that shows how the parts of the application interact:
WebJobs run in the context of a website and are not scalable separately. For example, if you have one Standard website instance, you only have 1 instance of your background process running, and it is using some of the server resources (CPU, memory, etc.) that otherwise would be available to serve web content.
If traffic varies by time of day or day of week, and if the backend processing you need to do can wait, you could schedule your WebJobs to run at low-traffic times. If the load is still too high for that solution, you can consider alternative environments for your backend program, such as the following:
This tutorial shows how to run the frontend in a website and the backend as a WebJob in the same website. For information about how to choose the best environment for your scenario, see Azure Websites, Cloud Services, and Virtual Machines Comparison.
To start, set up your development environment by installing the Azure SDK for Visual Studio 2013.
If you don't have Visual Studio installed, Visual Studio Express for Web will be installed along with the SDK.
Depending on how many of the SDK dependencies you already have on your machine, installing the SDK could take a long time, from several minutes to a half hour or more.
The tutorial instructions have been written using the next preview release of Visual Studio 2013 Update 4. The only difference for Visual Studio 2013 Update 3 is in the create-from-scratch section where you create the WebJob project: with Update 4 the WebJobs SDK packages are automatically included in the project; without Update 4 you have to install the packages manually.
An Azure storage account provides resources for storing queue and blob data in the cloud. It is also used by the WebJobs SDK to store logging data for the dashboard.
In a real-world application, you typically create separate accounts for application data versus logging data, and separate accounts for test data versus production data. For this tutorial you'll use just one account.
Open the Server Explorer window in Visual Studio.
Right-click the Azure node, and then click Connect to Microsoft Azure.
Sign in using your Azure credentials.
Right-click Storage under the Azure node, and then click Create Storage Account.
In the Create Storage Account dialog, enter a name for the storage account.
The name must be must be unique (no other Azure storage account can have the same name). If the name you enter is already in use you'll get a chance to change it.
The URL to access your storage account will be {name}.core.windows.net.
Set the Region or Affinity Group drop-down list to the region closest to you.
This setting specifies which Azure datacenter will host your storage account. For this tutorial your choice won't make a noticeable difference, but for a production site you want your web server and your storage account to be in the same region to minimize latency and data egress charges. The website (which you'll create later) should be as close as possible to the browsers accessing your site in order to minimize latency.
Set the Replication drop-down list to Locally redundant.
When geo-replication is enabled for a storage account, the stored content is replicated to a secondary datacenter to enable failover to that location in case of a major disaster in the primary location. Geo-replication can incur additional costs. For test and development accounts, you generally don't want to pay for geo-replication. For more information, see How To Manage Storage Accounts.
Click Create.
Download and unzip the completed solution.
Start Visual Studio.
From the File menu choose Open > Project/Solution, navigate to where you downloaded the solution, and then open the solution file.
Press CTRL+SHIFT+B to build the solution.
By default, Visual Studio automatically restores the NuGet package content, which was not included in the .zip file. If the packages don't restore, install them manually by going to the Manage NuGet Packages for Solution dialog and clicking the Restore button at the top right.
In Solution Explorer, make sure that ContosoAdsWeb is selected as the startup project.
Open the application Web.config file in the ContosoAdsWeb project.
The file contains a SQL connection string and an Azure storage connection string for working with blobs and queues.
The SQL connection string points to a SQL Server Express LocalDB database.
The storage connection string is an example that has placeholders for the storage account name and access key. You'll replace this with a connection string that has the name and key of your storage account.
<connectionStrings> <add name="ContosoAdsContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=ContosoAds; Integrated Security=True; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" /> <add name="AzureWebJobsStorage" connectionString="DefaultEndpointsProtocol=https;AccountName=[accountname];AccountKey=[accesskey]"/> </connectionStrings>
The storage connection string is named AzureWebJobsStorage because that's the name the WebJobs SDK uses by default. The same name is used here so you only have to set one connection string value in the Azure environment.
In Server Explorer, right-click your storage account under the Storage node, and then click Properties.
In the Properties window, click Storage Account Keys, and then click the ellipsis.
Copy the Connection String.
Replace the storage connection string in the Web.config file with the connection string you just copied. Make sure you select everything inside the quotation marks but not including the quotation marks before pasting.
Open the App.config file in the ContosoAdsWebJob project.
This file has two storage connection strings, one for application data and one for logging. For this tutorial you'll use the same account for both. The connection strings have placeholders for the storage account keys.
<configuration> <connectionStrings> <add name="AzureWebJobsDashboard" connectionString="DefaultEndpointsProtocol=https;AccountName=[accountname];AccountKey=[accesskey]"/> <add name="AzureWebJobsStorage" connectionString="DefaultEndpointsProtocol=https;AccountName=[accountname];AccountKey=[accesskey]"/> <add name="ContosoAdsContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=ContosoAds; Integrated Security=True; MultipleActiveResultSets=True;"/> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
By default, the WebJobs SDK looks for connection strings named AzureWebJobsStorage and AzureWebJobsDashboard. As an alternative, you can store the connection string however you want and pass it in explicitly to the JobHost
object.
Replace both storage connection strings with the connection string you copied earlier.
Save your changes.
To start the web frontend of the application, press CTRL+F5.
The default browser opens to the home page. (The web project runs because you've made it the startup project.)
To start the WebJob backend of the application, right-click the ContosoAdsWebJob project in Solution Explorer, and then click Debug > Start new instance.
A console application window opens and displays logging messages indicating the WebJobs SDK JobHost object has started to run.
In your browser, click Create an Ad.
Enter some test data and select an image to upload, and then click Create.
The app goes to the Index page, but it doesn't show a thumbnail for the new ad because that processing hasn't happened yet.
Meanwhile, after a short wait a logging message in the console application window shows that a queue message was received and has been processed.
After you see the logging messages in the console application window, refresh the Index page to see the thumbnail.
Click Details for your ad to see the full-size image.
You've been running the application on your local computer, and it's using a SQL Server database located on your computer, but it's working with queues and blobs in the cloud. In the following section you'll run the application in the cloud, using a cloud database as well as cloud blobs and queues.
You'll do the following steps to run the application in the cloud:
After you've created some ads while running in the cloud, you'll view the WebJobs SDK dashboard to see the rich monitoring features it has to offer.
Close the browser and the console application window.
In Solution Explorer, right-click the ContosoAdsWeb project, and then click Publish.
In the Profile step of the Publish Web wizard, click Microsoft Azure Websites.
In the Select Existing Website box, click Sign In.
After you're signed in, click New.
In the Create site on Microsoft Azure dialog box, enter a unique name in the Site name box.
The complete URL will consist of what you enter here plus .azurewebsites.net (as shown next to the Site name text box). For example, if the site name is ContosoAds, the URL will be ContosoAds.azurewebsites.net.
In the Region drop-down list choose the same region you chose for your storage account.
This setting specifies which Azure datacenter your website will run in. Keeping the website and storage account in the same datacenter minimizes latency and data egress charges.
In the Database server drop-down list choose Create new server.
Alternatively, if your subscription already has a server, you can select that server from the drop-down list.
Enter an administrator Database username and Database password.
If you selected New SQL Database server you aren't entering an existing name and password here, you're entering a new name and password that you're defining now to use later when you access the database. If you selected a server that you created previously, you'll be prompted for the password to the administrative user account you already created.
Click Create.
Visual Studio creates the solution, the web project, the Azure Website, and the Azure SQL Database instance.
In the Connection step of the Publish Web wizard, click Next.
In the Settings step, clear the Use this connection string at runtime check box, and then click Next.
You don't need to use the publish dialog to set the SQL connection string because you'll set that value in the Azure environment later.
You can ignore the warnings on this page.
For this tutorial, the default values of the options under File Publish Options are fine.
In the Preview step, click Start Preview.
You can ignore the warning about no databases being published. Entity Framework Code First will create the database; it doesn't need to be published.
the preview window shows that binaries and configuration files from the WebJob project will be copied to the app_data\jobs\continuous folder of the website.
Click Publish.
Visual Studio deploys the application and opens the home page URL in the browser.
You won't be able to use the site until you set connection strings in the Azure environment in the next section. You'll see either an error page or the home page depending on site and database creation options you chose earlier.
It's a security best practice to avoid putting sensitive information such as connection strings in files that are stored in source code repositories. Azure provides a way to do that: you can set connection string and other setting values in the Azure environment, and ASP.NET configuration APIs automatically pick up those values when the app runs in Azure. In this section you'll set connection string values in Azure.
In Server Explorer, right-click your website under the Websites node, and then click View Settings.
The Azure Website window opens on the Configuration tab.
Change the name of the DefaultConnection connection string to ContosoAdsContext.
Azure automatically created this connection string when you created the site with an associated database, so it already has the right connection string value. You're just changing the name to what your code is looking for.
Add two new connection strings, named AzureWebJobsStorage and AzureWebJobsDashboard. Set type to Custom, and set the connection string value to the same value that you used earlier for the Web.config and App.config files. (Make sure you include the entire connection string, not just the access key, and don't include the quotation marks.)
These connection strings are used by the WebJobs SDK, one for application data and one for logging. As you saw earlier, the one for application data is also used by the web frontend code.
Click Save.
In Server Explorer, right-click the website, and then click Stop Website.
After the website stops, right-click the website again, and then click Start Website.
The WebJob automatically starts when you publish, but it stops when you make a configuration change. To restart it you can either restart the site or restart the WebJob in the Azure management portal. It's generally recommended to restart the site after a configuration change.
Refresh the browser window that has the site URL in its address bar.
The home page appears.
Create an ad, as you did when you ran the application locally.
The Index page shows without a thumbnail at first.
Refresh the page after a few seconds, and the thumbnail appears.
If the thumbnail doesn't appear, the WebJob may not have started automatically. In that case, go to the WebJobs tab in the
In the Azure management portal, select your website.
Click the WebJobs tab.
click the URL in the Logs column for your WebJob.
A new browser tab opens to the WebJobs SDK dashboard. The dashboard shows that the WebJob is running and shows a list of functions in your code that the WebJobs SDK triggered.
Click one of the functions to see details about its execution
The Replay Function button on this page causes the WebJobs SDK framework to call the function again, and it gives you a chance to change the data passed to the function first.
When you're finished testing, delete the website and the SQL Database instance. The website is free, but the SQL Database instance and storage account accrue charges (minimal due to small size). Also, if you leave the site running, anyone who finds your URL can create and view ads. In the Azure management portal, go to the Dashboard tab for your website, and then click the Delete button at the bottom of the page. You can then select a check box to delete the SQL Database instance at the same time. If you just want to temporarily prevent others from accessing the site, click Stop instead. In that case, charges will continue to accrue for the SQL Database and Storage account. You can follow a similar procedure to delete the SQL database and storage account when you no longer need them.
For this sample application, web site activity always precedes the creation of a queue message, so there is no problem if the website goes to sleep and terminates the WebJob due to a long period of inactivity. When a request comes in, the site wakes up and the WebJob is restarted.
For WebJobs that you want to keep running even when the website itself is inactive for a long period of time, you can use the AlwaysOn feature of Azure Websites.
In this section you'll do the following tasks:
In Visual Studio, choose New > Project from the File menu.
In the New Project dialog, choose Visual C# > Web > ASP.NET Web Application.
Name the project ContosoAdsWeb, name the solution ContosoAdsWebJobsSDK (change the solution name if you're putting it in the same folder as the downloaded solution), and then click OK.
In the New ASP.NET Project dialog, choose the MVC template, and clear the Host in the cloud check box under Microsoft Azure.
Selecting Host in the cloud enables Visual Studio to automatically create a new Azure Website and SQL Database. Since you already created these earlier, you don't need to do so now while creating the project. If you want to create a new one, select the check box. You can then configure the new website and SQL database the same way you did earlier when you were deploying the application.
Click Change Authentication.
In the Change Authentication dialog, choose No Authentication, and then click OK.
In the New ASP.NET Project dialog, click OK.
Visual Studio creates the solution and the web project.
In Solution Explorer, right-click the solution (not the project), and choose Add > New Project.
In the Add New Project dialog, choose Visual C# > Windows Desktop > Class Library template.
Name the project ContosoAdsCommon, and then click OK.
This project will contain the Entity Framework context and the data model which both the frontend and backend will use. As an alternative you could define the EF-related classes in the web project and reference that project from the WebJob project. But then your WebJob project would have a reference to web assemblies which it doesn't need.
Right-click the web project (not the solution or the class library project), and then click Add > New Azure WebJob Project.
In the Add Azure WebJob dialog, enter ContosoAdsWebJob as both the Project name and the WebJob name. Leave WebJob run mode set to Run Continuously.
Click OK.
Visual Studio creates a Console application that is configured to deploy as a WebJob whenever you deploy the web project. To do that it performed the following tasks after creating the project:
For more information about these changes, see How to Deploy WebJobs by using Visual Studio.
The new-project template for a WebJob project automatically installs the WebJobs SDK NuGet package Microsoft.Azure.WebJobs and its dependencies.
One of the WebJobs SDK dependencies that is installed automatically in the WebJob project is the Azure Storage Client Library (SCL). However, you need to add it to the web project to work with blobs and queues.
Open the Manage NuGet Packages dialog for the solution.
In the left pane, select Installed packages.
Find the Azure Storage package, and then click Manage.
In the Select Projects box, select the ContosoAdsWeb check box, and then click OK.
All three projects use the Entity Framework to work with data in SQL Database.
In the left pane, select Online.
Find the EntityFramework NuGet package, and install it in all three projects.
Both web and WebJob projects will work with the SQL database, so both need a reference to the ContosoAdsCommon project.
In the ContosoAdsWeb project, set a reference to the ContosoAdsCommon project. (Right-click the ContosoAdsWeb project, and then click Add >Reference. In the Reference Manager dialog box, select Solution > Projects > ContosoAdsCommon, and then click OK.)
In the ContosoAdsWebJob project, set a reference to the ContosAdsCommon project.
The WebJob project needs references for working with images and for accessing connection strings.
System.Drawing
and System.Configuration
.This tutorial does not show how to create MVC controllers and views using scaffolding, how to write Entity Framework code that works with SQL Server databases, or the basics of asynchronous programming in ASP.NET 4.5. So all that remains to do is copy code and configuration files from the downloaded solution into the new solution. After you do that, the following sections will show and explain key parts of the code.
To add files to a project or a folder, right-click the project or folder and click Add > Existing Item. Select the files you want and click Add. If asked whether you want to replace existing files, click Yes.
In the ContosoAdsCommon project, delete the Class1.cs file and add in its place the following files from the downloaded project.
In the ContosoAdsWeb project, add the following files from the downloaded project.
In the ContosoAdsWebJob project, add the following files from the downloaded project.
You can now build, run, and deploy the application as instructed earlier in the tutorial. Before you do that, however, stop the WebJob that is still running in the first website you deployed to. Otherwise that WebJob will process queue messages created locally or by the app running in a new website, since all are using the same storage account.
The following sections explain the code related to working with the WebJobs SDK and Azure Storage blobs and queues. For the code specific to the WebJobs SDK, see the Program.cs section.
The Ad.cs file defines an enum for ad categories and a POCO entity class for ad information.
public enum Category { Cars, [Display(Name="Real Estate")] RealEstate, [Display(Name = "Free Stuff")] FreeStuff } public class Ad { public int AdId { get; set; } [StringLength(100)] public string Title { get; set; } public int Price { get; set; } [StringLength(1000)] [DataType(DataType.MultilineText)] public string Description { get; set; } [StringLength(1000)] [DisplayName("Full-size Image")] public string ImageURL { get; set; } [StringLength(1000)] [DisplayName("Thumbnail")] public string ThumbnailURL { get; set; } [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PostedDate { get; set; } public Category? Category { get; set; } [StringLength(12)] public string Phone { get; set; } }
The ContosoAdsContext class specifies that the Ad class is used in a DbSet collection, which Entity Framework will store in a SQL database.
public class ContosoAdsContext : DbContext { public ContosoAdsContext() : base("name=ContosoAdsContext") { } public ContosoAdsContext(string connString) : base(connString) { } public System.Data.Entity.DbSet<Ad> Ads { get; set; } }
The class has two constructors. The first of them is used by the web project, and specifies the name of a connection string that is stored in the Web.config file or the Azure runtime environment. The second constructor enables you to pass in the actual connection string. That is needed by the WebJob project, since it doesn't have a Web.config file. You saw earlier where this connection string was stored, and you'll see later how the code retrieves the connection string when it instantiates the DbContext class.
The BlobInformation
class is used to store information about an image blob in a queue message.
public class BlobInformation { public Uri BlobUri { get; set; } public string BlobName { get { return BlobUri.Segments[BlobUri.Segments.Length - 1]; } } public string BlobNameWithoutExtension { get { return Path.GetFileNameWithoutExtension(BlobName); } } public int AdId { get; set; } }
Code that is called from the Application_Start
method creates an images blob container and an images queue if they don't already exist. This ensures that whenever you start using a new storage account, the required blob container and queue will be created automatically.
The code gets access to the storage account by using the storage connection string from the Web.config file or Azure runtime environment.
var storageAccount = CloudStorageAccount.Parse (ConfigurationManager.ConnectionStrings["AzureWebJobsStorage"].ToString());
Then it gets a reference to the images blob container, creates the container if it doesn't already exist, and sets access permissions on the new container. By default new containers only allow clients with storage account credentials to access blobs. The website needs the blobs to be public so that it can display images using URLs that point to the image blobs.
var blobClient = storageAccount.CreateCloudBlobClient(); var imagesBlobContainer = blobClient.GetContainerReference("images"); if (imagesBlobContainer.CreateIfNotExists()) { imagesBlobContainer.SetPermissions( new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob }); }
Similar code gets a reference to the blobnamerequest queue and creates a new queue. In this case no permissions change is needed. TheResolveBlobName section later in the tutorial explains why the queue that the web application writes to is used just for getting blob names and not for generating thumbnails.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); var imagesQueue = queueClient.GetQueueReference("blobnamerequest"); imagesQueue.CreateIfNotExists();
The _Layout.cshtml file sets the app name in the header and footer, and creates an "Ads" menu entry.
The Views\Home\Index.cshtml file displays category links on the home page. The links pass the integer value of the Category
enum in a querystring variable to the Ads Index page.
<li>@Html.ActionLink("Cars", "Index", "Ad", new { category = (int)Category.Cars }, null)</li> <li>@Html.ActionLink("Real estate", "Index", "Ad", new { category = (int)Category.RealEstate }, null)</li> <li>@Html.ActionLink("Free stuff", "Index", "Ad", new { category = (int)Category.FreeStuff }, null)</li> <li>@Html.ActionLink("All", "Index", "Ad", null, null)</li>
In the AdController.cs file the constructor calls the InitializeStorage
method to create Azure Storage Client Library objects that provide an API for working with blobs and queues.
Then the code gets a reference to the images blob container as you saw earlier in Global.asax.cs. While doing that it sets a default retry policy appropriate for a web app. The default exponential backoff retry policy could hang the web app for longer than a minute on repeated retries for a transient fault. The retry policy specified here waits 3 seconds after each try for up to 3 tries.
var blobClient = storageAccount.CreateCloudBlobClient(); blobClient.DefaultRequestOptions.RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(3), 3); imagesBlobContainer = blobClient.GetContainerReference("images");
Similar code gets a reference to the images queue.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); queueClient.DefaultRequestOptions.RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(3), 3); imagesQueue = queueClient.GetQueueReference("blobnamerequest");
Most of the controller code is typical for working with an Entity Framework data model using a DbContext class. An exception is the HttpPost Create
method, which uploads a file and saves it in blob storage. The model binder provides an HttpPostedFileBase object to the method.
[HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create( [Bind(Include = "Title,Price,Description,Category,Phone")] Ad ad, HttpPostedFileBase imageFile)
If the user selected a file to upload, the code uploads the file, saves it in a blob, and updates the Ad database record with a URL that points to the blob.
if (imageFile != null && imageFile.ContentLength != 0) { blob = await UploadAndSaveBlobAsync(imageFile); ad.ImageURL = blob.Uri.ToString(); }
The code that does the upload is in the UploadAndSaveBlobAsync
method. It creates a GUID name for the blob, uploads and saves the file, and returns a reference to the saved blob.
private async Task<CloudBlockBlob> UploadAndSaveBlobAsync(HttpPostedFileBase imageFile) { string blobName = Guid.NewGuid().ToString() + Path.GetExtension(imageFile.FileName); CloudBlockBlob imageBlob = imagesBlobContainer.GetBlockBlobReference(blobName); using (var fileStream = imageFile.InputStream) { await imageBlob.UploadFromStreamAsync(fileStream); } return imageBlob; }
After the HttpPost Create
method uploads a blob and updates the database, it creates a queue message to inform the back-end process that an image is ready for conversion to a thumbnail.
BlobInformation blobInfo = new BlobInformation() { AdId = ad.AdId, BlobUri = new Uri(ad.ImageURL) }; var queueMessage = new CloudQueueMessage(JsonConvert.SerializeObject(blobInfo)); await thumbnailRequestQueue.AddMessageAsync(queueMessage);
The code for the HttpPost Edit
method is similar except that if the user selects a new image file any blobs that already exist for this ad must be deleted.
if (imageFile != null && imageFile.ContentLength != 0) { await DeleteAdBlobsAsync(ad); imageBlob = await UploadAndSaveBlobAsync(imageFile); ad.ImageURL = imageBlob.Uri.ToString(); }
Here is the code that deletes blobs when you delete an ad:
private async Task DeleteAdBlobsAsync(Ad ad) { if (!string.IsNullOrWhiteSpace(ad.ImageURL)) { Uri blobUri = new Uri(ad.ImageURL); await DeleteAdBlobAsync(blobUri); } if (!string.IsNullOrWhiteSpace(ad.ThumbnailURL)) { Uri blobUri = new Uri(ad.ThumbnailURL); await DeleteAdBlobAsync(blobUri); } } private static async Task DeleteAdBlobAsync(Uri blobUri) { string blobName = blobUri.Segments[blobUri.Segments.Length - 1]; CloudBlockBlob blobToDelete = imagesBlobContainer.GetBlockBlobReference(blobName); await blobToDelete.DeleteAsync(); }
The Index.cshtml file displays thumbnails with the other ad data:
<img src="@Html.Raw(item.ThumbnailURL)" />
The Details.cshtml file displays the full-size image:
<img src="@Html.Raw(Model.ImageURL)" />
The Create.cshtml and Edit.cshtml files specify form encoding that enables the controller to get the HttpPostedFileBase
object.
@using (Html.BeginForm("Create", "Ad", FormMethod.Post, new { enctype = "multipart/form-data" }))
An <input>
element tells the browser to provide a file selection dialog.
<input type="file" name="imageFile" accept="image/*" class="form-control fileupload" />
When the WebJob starts, the Main
method calls Initialize
to instantiate the Entity Framework database context. Then it calls the WebJobs SDKJobHost.RunAndBlock
method to begin single-threaded execution of triggered functions on the current thread.
static void Main(string[] args) { Initialize(); JobHost host = new JobHost(); host.RunAndBlock(); } private static void Initialize() { db = new ContosoAdsContext(); }
The WebJobs SDK calls this method when a queue message is received. The method creates a thumbnail and puts the thumbnail URL in the database.
public static void GenerateThumbnail( [QueueTrigger("thumbnailrequest")] BlobInformation blobInfo, [Blob("images/{BlobName}", FileAccess.Read)] Stream input, [Blob("images/{BlobNameWithoutExtension}_thumbnail.jpg"), FileAccess.Write] CloudBlockBlob outputBlob) { using (Stream output = outputBlob.OpenWrite()) { ConvertImageToThumbnailJPG(input, output); outputBlob.Properties.ContentType = "image/jpeg"; } var id = blobInfo.AdId; Ad ad = Program.db.Ads.Find(id); if (ad == null) { throw new Exception(String.Format("AdId {0} not found, can't create thumbnail", id.ToString())); } ad.ThumbnailURL = outputBlob.Uri.ToString(); Program.db.SaveChanges(); }
The QueueTrigger
attribute directs the WebJobs SDK to call this method when a new message is received on the thumbnailrequest queue.
[QueueTrigger("thumbnailrequest")] BlobInformation blobInfo,
The BlobInformation
object in the queue message is automatically deserialized into the blobInfo
parameter. When the method completes, the queue message is deleted. If the method fails before completing, the queue message is not deleted; after a 10-minute lease expires, the message is released to be picked up again and processed. This sequence won't be repeated indefinitely if a message always causes an exception. After 5 unsuccessful attempts to process a message, the message is moved to a queue named {queuename}-poison. The maximum number of attempts is configurable.
The two Blob
attributes provide objects that are bound to blobs: one to the existing image blob and one to a new thumbnail blob that the method creates.
[Blob("images/{BlobName}", FileAccess.Read)] Stream input, [Blob("images/{BlobNameWithoutExtension}_thumbnail.jpg")] CloudBlockBlob outputBlob)
Blob names come from properties of the BlobInformation
object received in the queue message (BlobName
and BlobNameWithoutExtension
). To get the full functionality of the Storage Client Library you can use the CloudBlockBlob
class to work with blobs. If you want to reuse code that was written to work with Stream
objects, you can use the Stream
class.
* If your website runs on multiple VMs, this program will run on each machine, and each machine will wait for triggers and attempt to run functions. In some scenarios this can lead to some functions processing the same data twice, so functions should be idempotent (written so that calling them repeatedly with the same input data doesn't produce duplicate results). * For information about how to implement graceful shutdown, see Graceful Shutdown.
* The code in the ConvertImageToThumbnailJPG
method (not shown) uses classes in the System.Drawing
namespace for simplicity. However, the classes in this namespace were designed for use with Windows Forms. They are not supported for use in a Windows or ASP.NET service.
If you compare the amount of code in the GenerateThumbnails
method in this sample application with the worker role code in the Cloud Service version of the application, you can see how much work the WebJobs SDK is doing for you. The advantage is greater than it appears, because the Cloud Service sample application code doesn't do all of the things (such as poison message handling) that you would do in a production application, and which the WebJobs SDK does for you.
In the Cloud Service version of the application, the record ID is the only information in the queue message, and the background process gets the image URL from the database. In the WebJobs SDK version of the application, the queue message includes the image URL so that it can be provided to the Blob
attributes. If the queue message didn't have the blob URL, you could use the Blob attribute in the body of the method instead of in the method signature.
A program that uses the WebJobs SDK doesn't have to run in Azure in a WebJob. It can run locally, and it can also run in other environments such as a Cloud Service worker role or a Windows service. However, you can only access the WebJobs SDK dashboard through an Azure Website. To use the dashboard you have to connect the website to the storage account you're using by setting the AzureWebJobsDashboard connection string on theConfigure tab of the management portal. Then you can get to the Dashboard by using the URL https://{websitename}.scm.azurewebsites.net/azurejobs/#/functions. For more information, see Getting a dashboard for local development with the WebJobs SDK, but note that it shows an old connection string name.
In this tutorial you've seen a simple multi-tier application that uses the WebJobs SDK for backend processing. The application has been kept simple for a getting-started tutorial. For example, it doesn't implement dependency injection or the repository and unit of work patterns, it doesn't use an interface for logging, it doesn't use EF Code First Migrations to manage data model changes or EF Connection Resiliency to manage transient network errors, and so forth.
For more information, see Azure Web Jobs Recommended Resources.
http://azure.microsoft.com/en-us/documentation/articles/websites-dotnet-webjobs-sdk-get-started/