Skip to content
Browse files

Merge pull request #442 from tdykstra/azuremail

fix #441 multi-tier tutorials updates for staging
  • Loading branch information...
2 parents 063a7e4 + 1759e56 commit f7ca0575797aa57f6289d99c2579aa83d9763a8b @mollybostic committed Oct 17, 2012
Showing with 2,023 additions and 1,556 deletions.
  1. BIN DevCenter/dotNET/Media/mtas-1.PNG
  2. BIN DevCenter/dotNET/Media/mtas-2.png
  3. BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-controllers.png
  4. BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-models.png
  5. BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-views.png
  6. BIN DevCenter/dotNET/Media/mtas-add-new-role-project-dialog.PNG
  7. BIN DevCenter/dotNET/Media/mtas-aesp.PNG
  8. BIN DevCenter/dotNET/Media/mtas-ase-add.PNG
  9. BIN DevCenter/dotNET/Media/mtas-ase-add2.PNG
  10. BIN DevCenter/dotNET/Media/mtas-ase-add3.PNG
  11. BIN DevCenter/dotNET/Media/mtas-ase1.PNG
  12. BIN DevCenter/dotNET/Media/mtas-c1.PNG
  13. BIN DevCenter/dotNET/Media/mtas-c2.PNG
  14. BIN DevCenter/dotNET/Media/mtas-c3.PNG
  15. BIN DevCenter/dotNET/Media/mtas-c4.PNG
  16. BIN DevCenter/dotNET/Media/mtas-c5.PNG
  17. BIN DevCenter/dotNET/Media/mtas-c6.PNG
  18. BIN DevCenter/dotNET/Media/mtas-c7.PNG
  19. BIN DevCenter/dotNET/Media/mtas-compute-emulator-icon.png
  20. BIN DevCenter/dotNET/Media/mtas-create-cloud.PNG
  21. BIN DevCenter/dotNET/Media/mtas-create-storage-url-test.PNG
  22. BIN DevCenter/dotNET/Media/mtas-create1.PNG
  23. BIN DevCenter/dotNET/Media/mtas-elip.PNG
  24. BIN DevCenter/dotNET/Media/mtas-enter.PNG
  25. BIN DevCenter/dotNET/Media/mtas-fe.png
  26. BIN DevCenter/dotNET/Media/mtas-file-new-project.png
  27. BIN DevCenter/dotNET/Media/mtas-footer-in-layout.PNG
  28. BIN DevCenter/dotNET/Media/mtas-guid-keys.PNG
  29. BIN DevCenter/dotNET/Media/mtas-home-page-before-adding-controllers.PNG
  30. BIN DevCenter/dotNET/Media/mtas-mailing-list-empty-index-page.png
  31. BIN DevCenter/dotNET/Media/mtas-mailinglist1.PNG
  32. BIN DevCenter/dotNET/Media/mtas-manage-access-keys-dialog.png
  33. BIN DevCenter/dotNET/Media/mtas-manage-keys-in-portal.png
  34. BIN DevCenter/dotNET/Media/mtas-manage-keys.PNG
  35. BIN DevCenter/dotNET/Media/mtas-menu-in-layout.PNG
  36. BIN DevCenter/dotNET/Media/mtas-message-empty-index-page.png
  37. BIN DevCenter/dotNET/Media/mtas-message-processing.png
  38. BIN DevCenter/dotNET/Media/mtas-mvcwebrole-properties-menu.PNG
  39. BIN DevCenter/dotNET/Media/mtas-mvcwebrole-settings-tab.png
  40. BIN DevCenter/dotNET/Media/mtas-new-cloud-project.PNG
  41. BIN DevCenter/dotNET/Media/mtas-new-cloud-service-add-worker-a.PNG
  42. BIN DevCenter/dotNET/Media/mtas-new-cloud-service-dialog-rename.PNG
  43. BIN DevCenter/dotNET/Media/mtas-new-cloud-service-dialog.PNG
  44. BIN DevCenter/dotNET/Media/mtas-new-cloud.PNG
  45. BIN DevCenter/dotNET/Media/mtas-new-mvc4-project.PNG
  46. BIN DevCenter/dotNET/Media/mtas-new-worker-role-project.PNG
  47. BIN DevCenter/dotNET/Media/mtas-opening-layout-cshtml.png
  48. BIN DevCenter/dotNET/Media/mtas-pack.PNG
  49. BIN DevCenter/dotNET/Media/mtas-portal-new-storage.PNG
  50. BIN DevCenter/dotNET/Media/mtas-rt-prop.png
  51. BIN DevCenter/dotNET/Media/mtas-se1.png
  52. BIN DevCenter/dotNET/Media/mtas-se2.PNG
  53. BIN DevCenter/dotNET/Media/mtas-se3.PNG
  54. BIN DevCenter/dotNET/Media/mtas-se4.png
  55. BIN DevCenter/dotNET/Media/mtas-serverExplorer.PNG
  56. BIN DevCenter/dotNET/Media/mtas-storage-acct-conn-string-dialog.png
  57. BIN DevCenter/dotNET/Media/mtas-storage-quick-SM.PNG
  58. BIN DevCenter/dotNET/Media/mtas-storage-quick.PNG
  59. BIN DevCenter/dotNET/Media/mtas-subscribe-diagram.png
  60. BIN DevCenter/dotNET/Media/mtas-subscribe-email.png
  61. BIN DevCenter/dotNET/Media/mtas-subscribers-empty-index-page.png
  62. BIN DevCenter/dotNET/Media/mtas-tableserviceentity.PNG
  63. BIN DevCenter/dotNET/Media/mtas-title-and-logo-in-layout.PNG
  64. BIN DevCenter/dotNET/Media/mtas-wasVSdata.PNG
  65. BIN DevCenter/dotNET/Media/mtas-worker-b-add-existing.PNG
  66. BIN DevCenter/dotNET/Media/mtas-worker-b-add-reference-menu.png
  67. BIN DevCenter/dotNET/Media/mtas-worker-b-reference-manager.png
  68. BIN DevCenter/dotNET/Media/mtas-worker-roles-a-and-b.png
  69. BIN DevCenter/dotNET/Media/mtas-wpi-installer.png
  70. +253 −103 DevCenter/dotNET/Tutorials/multi-tier-with-storage-1-overview.md
  71. +390 −472 DevCenter/dotNET/Tutorials/multi-tier-with-storage-2-download-and-run.md
  72. +1,089 −468 DevCenter/dotNET/Tutorials/multi-tier-with-storage-3-web-role.md
  73. +291 −513 DevCenter/dotNET/Tutorials/multi-tier-with-storage-5-worker-role-b.md
View
BIN DevCenter/dotNET/Media/mtas-1.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-controllers.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-models.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-add-existing-item-to-views.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-add-new-role-project-dialog.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-aesp.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-ase-add.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-ase-add2.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-ase-add3.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-ase1.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c1.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c2.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c3.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c4.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c5.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c6.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-c7.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-compute-emulator-icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-create-cloud.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-create-storage-url-test.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-create1.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-elip.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-enter.PNG
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-fe.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN DevCenter/dotNET/Media/mtas-file-new-project.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-footer-in-layout.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-guid-keys.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-home-page-before-adding-controllers.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-mailing-list-empty-index-page.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-mailinglist1.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-manage-access-keys-dialog.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-manage-keys-in-portal.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-manage-keys.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-menu-in-layout.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-message-empty-index-page.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-message-processing.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-mvcwebrole-properties-menu.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-mvcwebrole-settings-tab.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-cloud-project.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-cloud-service-add-worker-a.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-cloud-service-dialog-rename.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-cloud-service-dialog.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-cloud.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-mvc4-project.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-new-worker-role-project.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-opening-layout-cshtml.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-pack.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-portal-new-storage.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-rt-prop.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-se1.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-se2.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-se3.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-se4.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-serverExplorer.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-storage-acct-conn-string-dialog.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-storage-quick-SM.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-storage-quick.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-subscribe-diagram.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-subscribe-email.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-subscribers-empty-index-page.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-tableserviceentity.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-title-and-logo-in-layout.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-wasVSdata.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-worker-b-add-existing.PNG
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-worker-b-add-reference-menu.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-worker-b-reference-manager.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-worker-roles-a-and-b.png
Diff not rendered.
View
BIN DevCenter/dotNET/Media/mtas-wpi-installer.png
Diff not rendered.
View
356 DevCenter/dotNET/Tutorials/multi-tier-with-storage-1-overview.md
@@ -2,19 +2,27 @@
# .NET Multi-Tier Application Using Storage Tables, Queues, and Blobs
-This tutorial series shows how to create a multi-tier ASP.NET web application that uses Windows Azure Storage tables, queues, and blobs. The tutorial assumes that you have no prior experience using Windows Azure. On completing the tutorial, you'll have a robust and scalable data-driven web application up and running in the cloud.
+This tutorial series shows how to create a multi-tier ASP.NET web application that uses Windows Azure Storage tables, queues, and blobs. The tutorials assume that you have no prior experience using Windows Azure. On completing the series, you'll know how to build a resilient and scalable data-driven web application and deploy it to the cloud.
+
+The series includes the following tutorials:
+
+1. **Introduction to the Azure Email Service application** (this tutorial). An overview of the application and its architecture. You can skip to tutorial 2 if you don't want all the background and just want to get started.
+2. [Configuring and Deploying the Azure Email Service application][tut2]. How to download the sample application, configure it, test it locally, deploy it, and test it in the cloud. If you prefer to start by seeing how to build the solution from scratch, you can skip most of tutorial 2 and return to do the rest after you finish tutorials 3-5. The tutorial explains what you have to do and what you can skip.
+3. [Building the web role for the Azure Email Service application][tut3]. How to build the MVC 4 and Web API components of the application and test them locally. (Includes instructions for running the web UI in a Windows Azure Web Site if you prefer to do that instead of using a Windows Azure Cloud Service web role.)
+4. [Building worker role A for the Azure Email Service application][tut4]. How to build the back-end component that creates queue work items for sending emails, and test it locally.
+5. [Building the web role for the Azure Email Service application][tut5]. How to build the back-end component that processes queue work items for sending emails, and test it locally.
<h2><a name="whyanemaillistapp"></a><span class="short-header">Why Choose This App</span>Why an Email List Service Application</h2>
-We chose an email list service for this sample application because it is the kind of application that needs to be robust and scalable, two features that make it especially appropriate for Windows Azure.
+We chose an email list service for this sample application because it is the kind of application that needs to be resilient and scalable, two features that make it especially appropriate for Windows Azure.
-### Robust
+### Resilient
-If a server fails while sending out emails to a large list, you want to be able to stand up a new server easily and quickly, and you want the application to pick up where it left off without losing or duplicating any emails. A Windows Azure Cloud Service web or worker role (virtual machine) is automatically replaced if it fails. And Windows Azure Storage queues and tables provide a means to implement server-to-server communication that can survive a failure without losing work.
+If a server fails while sending out emails to a large list, you want to be able to spin up a new server easily and quickly, and you want the application to pick up where it left off without losing or duplicating any emails. A Windows Azure Cloud Service web or worker role instance (a virtual machine) is automatically replaced if it fails. And Windows Azure Storage queues and tables provide a means to implement server-to-server communication that can survive a failure without losing work.
### Scalable
-An email service also must be able to handle spikes in traffic, since sometimes you are sending emails to small lists and sometimes to very large lists. In many hosting environments, you have to purchase and maintain sufficient hardware to handle the spikes in workload, and you're paying for all that capacity 100% of the time although you might only use it 5% of the time. With Windows Azure, you pay only for the amount of computing power that you actually need for only as long as you need it. To scale up for a large mailing, you just change a configuration setting to increase the number of servers you have available to process the workload, and this can be done programmatically.
+An email service also must be able to handle spikes in workload, since sometimes you are sending emails to small lists and sometimes to very large lists. In many hosting environments, you have to purchase and maintain sufficient hardware to handle the spikes in workload, and you're paying for all that capacity 100% of the time although you might only use it 5% of the time. With Windows Azure, you pay only for the amount of computing power that you actually need for only as long as you need it. To scale up for a large mailing, you just change a configuration setting to increase the number of servers you have available to process the workload, and this can be done programmatically. For example, you could configure the application so that if the number of work items waiting in the queue exceeds a certain number, additional instances of worker role B are automatically spun up.
<h2><a name="whatyoulllearn"></a><span class="short-header">What You'll Learn</span>What You'll Learn</h2>
@@ -24,28 +32,27 @@ In this tutorial series you'll learn the following:
* How to create a Visual Studio cloud project with an MVC 4 web role and two worker roles.
* How to publish the cloud project to a Windows Azure Cloud Service.
* How to publish the MVC 4 project to a Windows Azure Web Site if you prefer, and still use the worker roles in the Cloud Service.
-* How to use Windows Azure storage queues for communication between tiers or between worker roles.
-* How to use Windows Azure storage tables as a highly scalable data store for non-relational data.
-* How to use Windows Azure storage blobs to store files in the cloud.
+* How to use the Windows Azure Queue storage service for communication between tiers or between worker roles.
+* How to use the Windows Azure Table storage service as a highly scalable data store for non-relational data.
+* How to use the Windows Azure Blob service to store files in the cloud.
* How to use Azure Storage Explorer to work with tables, queues, and blobs.
+* How to use SendGrid to send emails.
<h2><a name="wawsvswacs"></a><span class="short-header">Application architecture</span>Overview of application architecture</h2>
-This is the first tutorial in a series, and it provides an overview of the application and its architecture.
-
-The front-end is a set of web pages and a service method that enable administrators to manage email lists, and subscribers to subscribe and unsubscribe. The front-end uses ASP.NET MVC 4 and Web API, and it runs in a web role in a Windows Azure Cloud Service. The back-end is a pair of worker roles running in the same Cloud Service and do the work of sending emails.
+The front-end of the Azure Email Service application is a set of web pages and a service method that enable administrators to manage email lists, and subscribers to subscribe and unsubscribe. The front-end uses ASP.NET MVC 4 and Web API, and it runs in a web role in a Windows Azure Cloud Service. The back-end is a pair of worker roles that run in the same Cloud Service and do the work of sending emails.
-The application stores email lists and subscriber information in Windows Azure storage tables. It stores email content in blobs (a plain text file and an HTML file for each email). It uses Windows Azure storage queues for communication between the front-end service method and the one of the back-end worker roles, and between the two worker roles.
+The application stores email lists and subscriber information in Windows Azure tables. It stores email content in Windows Azure blobs. And it uses Windows Azure queues for communication between tiers and between the two worker roles.
-The following diagram provides a high-level picture of the application architecture that is used in this tutorial.
+The following diagram provides a high-level picture of this application architecture:
![Application architecture overview][mtas-architecture-overview]
-An alternative architecture that would also work is to run the front-end in a Windows Azure Web Site.
+An alternative architecture that would also work well is to run the front-end in a Windows Azure Web Site.
![Alternative application architecture][mtas-alternative-architecture]
-This alternative architecture might offer some cost benefits, because a Windows Azure Web Site may be less expensive for similar capacity compared to a web role running in a Cloud Service. For this tutorial we have the entire application in a Cloud Service because that simplifies configuration and deployment. The tutorial explains the differences between the two architectures, so that when you implement your own application you can choose the architecture that you prefer.
+This alternative architecture might offer some cost benefits, because a Windows Azure Web Site may be less expensive for similar capacity compared to a web role running in a Cloud Service. For this tutorial we have the entire application in a Cloud Service because that simplifies configuration and deployment. The tutorial explains implementation details that differ between the two architectures, so that when you implement your own application you can choose the architecture that you prefer.
<h2><a name="frontendoverview"></a><span class="short-header">Front-end overview</span>Front-end overview</h2>
@@ -59,453 +66,594 @@ The front-end includes web pages that administrators of the service use to manag
![Message Create Page][mtas-message-create-page]
-Clients of the service are companies that give their customers an opportunity to sign up for a list on the client web site. For example, Contoso University wants a list for History Department announcements. When a student interested in History Department announcements clicks a link on the Contoso University web site, Contoso University makes a web service call to this application. The service method causes an email to be sent to the customer. That email contains a link, and when the recipient clicks the link, a page welcoming the customer to the History Department Announcements list is displayed.
+Clients of the service are companies that give their customers an opportunity to sign up for a list on the client web site. For example, Contoso University wants a list for History Department announcements. When a student interested in History Department announcements clicks a link on the Contoso University web site, Contoso University makes a web service call to the Azure Email Service application. The service method causes an email to be sent to the customer. That email contains a hyperlink, and when the recipient clicks the link, a page welcoming the customer to the History Department Announcements list is displayed.
+
+![Confirmation email][mtas-subscribe-email]
![Welcome to list page][mtas-subscribe-confirmation-page]
-Every email sent by the service includes a hyperlink that can be used to unsubscribe. If a recipient clicks the link, a web page asks for confirmation of intent to unsubscribe. If the recipient clicks the Confirm button, a page is displayed confirming that the person has been removed from the list.
+Every email sent by the service (except the subscribe confirmation) includes a hyperlink that can be used to unsubscribe. If a recipient clicks the link, a web page asks for confirmation of intent to unsubscribe. If the recipient clicks the Confirm button, a page is displayed confirming that the person has been removed from the list.
![Confirm unsubscribe page][mtas-unsubscribe-query-page]
![!Unsubscribe confirmed page][mtas-unsubscribe-confirmation-page]
<h2><a name="backendoverview"></a><span class="short-header">Back-end overview</span>Back-end overview</h2>
-Email lists and email messages scheduled to be sent are stored in Windows Azure Storage tables. When an administrator schedules an email to be sent, a row containing the scheduled date and other data is placed on the Message table. A worker role (virtual machine) running in a Windows Azure Cloud Service periodically scans the Message table looking for messages that need to be sent (we'll call this Worker Role A). When Worker Role A finds a message needing to be sent, it looks up all the email addresses in the destination email list, puts the information needed to send the email in the Message table, and creates a work item on a queue for each email that needs to be sent. A second worker role (Worker Role B) polls the queue for work items. When Worker Role B finds a work item, it processes the item by sending the email and then deletes the work item from the queue. The following diagram shows these relationships.
+The front-end stores email lists and messages to be sent to them in Windows Azure tables. When an administrator schedules a message to be sent, a row containing the scheduled date and other data such as the subject line is added to the Message table. A worker role periodically scans the Message table looking for messages that need to be sent (we'll call this worker role A).
-![Worker roles A and B][mtas-worker-roles-a-and-b]
+When worker role A finds a message needing to be sent, it does the following tasks:
-When Worker Role A creates a queue work item, it also adds a row to the Message table. Worker Role A reads this row to get the information it needs to send the email.
+* Looks up all the email addresses in the destination email list.
+* Puts the information needed to send each email in the Message table.
+* Creates a queue work item for each email that needs to be sent.
-The row in the Message table that provides information for one email also includes a property that indicates whether the email has actually been sent. When Worker Role B sends an email, it updates this property to indicate that the email has been sent. If Worker Role A goes down while creating queue work items for a message, it might create duplicate queue work items when it restarts, but the tracking row ensures that duplicate emails won't be sent. (Worker Role B checks the row before sending an email.)
+A second worker role (worker role B) polls the queue for work items. When worker role B finds a work item, it processes the item by sending the email and then deletes the work item from the queue. The following diagram shows these relationships.
+
+![Worker roles A and B][mtas-worker-roles-a-and-b]
+
+The row in the Message table that provides information needed for one email also includes a property that indicates whether the email has actually been sent. When worker role B sends an email, it updates this property to indicate that the email has been sent. If worker role A goes down while creating queue work items for a message, it might create duplicate queue work items when it restarts, but this indicator ensures that duplicate emails won't be sent. (Worker role B checks it before sending an email.)
![Queue message creation and processing][mtas-message-processing]
-<h2><a name="tables"></a><span class="short-header">Tables</span>Windows Azure Storage Tables</h2>
+Worker role B also polls a subscription queue for work items put there by the Web API service method for new subscriptions. When it finds one, it sends the confirmation email.
+
+![Subscription queue message processing][mtas-subscribe-diagram]
-Windows Azure storage tables are a NoSQL data store, not a relational database. That makes them a good choice when scalability is more important than data normalization and relational integrity. For example, in this application, worker roles create a row every time a queue workitem is created and the row is updated every time an email is sent, which might be a performance bottleneck if a relational database were used.
+<h2><a name="tables"></a><span class="short-header">Tables</span>Windows Azure Tables</h2>
-In a Windows Azure storage table, every row has a *partition key* and a *row key* that uniquely identifies the row. The partition key divides the table up both logically and physically into partitions. Within a partition, the row key uniquely identifies a row.
+Windows Azure tables are a NoSQL data store, not a relational database. That makes them a good choice when efficiency and scalability are more important than data normalization and relational integrity. For example, in this application, one worker role creates a row every time a queue work item is created, and another one retrieves and updates a row every time an email is sent, which might become a performance bottleneck if a relational database were used.
+
+In a Windows Azure table, every row has a *partition key* and a *row key* that uniquely identifies the row. The partition key divides the table up logically into partitions. Within a partition, the row key uniquely identifies a row.
### MailingList table ###
-The MailingList table stores information about mailing lists and information about the subscribers to mailing lists. Administrators use web pages to create and edit mailing lists, and clients and subscribers use a set of web pages and service method to subscribe and unsubscribe.
+The MailingList table stores information about mailing lists and the subscribers to mailing lists. Administrators use web pages to create and edit mailing lists, and clients and subscribers use a set of web pages and service method to subscribe and unsubscribe.
In NoSQL tables, different rows can have different schemas, and this flexibility is commonly used to make one table store data that would require multiple tables in a relational database. For example, to store mailing list data in SQL Database you could use three tables: a MailingList table that stores information about the list, a Subscriber table that stores information about subscribers, and a MailingListSubscriber table that associates mailing lists with subscribers and vice versa. In the NoSQL table in this application, all of those functions are rolled into one table named MailingList.
The row key for the MailingList table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the mailing list. Rows that have the email address as the row key have information about the subscribers to the list.
In other words, rows with row key "0" are equivalent to a MailingList table in a relational database. Rows with row key = email address are equivalent to a Subscriber table and a MailingListSubscriber association table in a relational database.
-Making one table serve multiple purposes in this way facilitates better performance. In a relational database three tables would have to be read, then three sets of rows would have to be sorted and matched up against each other, which takes time. Here just one table is read and its rows are automatically returned in partition key and row key order.
+Making one table serve multiple purposes in this way facilitates better performance. In a relational database three tables would have to be read, and three sets of rows would have to be sorted and matched up against each other, which takes time. Here just one table is read and its rows are automatically returned in partition key and row key order.
-The following grid shows row properties for the rows that show mailing list information (row key = "0").
+The following grid shows row properties for the rows that contain mailing list information (row key = "0").
<table border="1">
+
<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
+<th>Property</th>
+<th>Data Type</th>
+<th>Description</th>
</tr>
+
<tr>
<td>PartitionKey</td>
<td>String</td>
-<td>ListName: The name (unique identifier) of the mailing list. The typical use for the table is to retrieve all information for a mailing list, so this is an efficient way to partition the table.</td>
+<td>ListName: A unique identifier for the mailing list, for example: contoso1. The typical use for the table is to retrieve all information for a specific mailing list, so using the list name is an efficient way to partition the table.</td>
</tr>
+
<tr>
<td>RowKey</td>
<td>String</td>
<td>The constant "0".</td>
</tr>
+
<tr>
<td>Description</td>
<td>String</td>
<td>Description of the mailing List, for example: "Contoso University History Department announcements".</td>
</tr>
+
<tr>
<td>FromEmailAddress</td>
<td>String</td>
-<td>The "From" email address in the emails sent to this list, for example: "contoso.edu".</td>
-</tr></table>
+<td>The "From" email address in the emails sent to this list, for example: donotreply@contoso.edu.</td>
+</tr>
+
+</table>
The following grid shows row properties for the rows that contain subscriber information for the list (row key = email address).
<table border="1">
+
<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
+<th>Property</th>
+<th>Data Type</th>
+<th>Description</th>
</tr>
+
<tr>
<td>PartitionKey</td>
<td>String</td>
-<td>ListName: The name (unique identifier) of the mailing list.</td>
+<td>ListName: The name (unique identifier) of the mailing list, for example: contoso1.</td>
</tr>
+
<tr>
<td>RowKey</td>
<td>String</td>
-<td>EmailAddress: The subscriber email address.</td>
+<td>EmailAddress: The subscriber email address, for example: student1@contoso.edu.</td>
</tr>
+
<tr>
<td>SubscriberGUID</td>
<td>String</td>
-<td>Generated when the email address is added to a list, used in subscribe and unsubscribe links so that it's not too easy to subscribe or unsubscribe someone else's email address. Not expecting major spikes in subscribe/unsubscribe requests, so no need to create a table with this as row key for efficient retrieval..</td>
+<td>Generated when the email address is added to a list, used in subscribe and unsubscribe links so that it's difficult to subscribe or unsubscribe someone else's email address. Because we are not expecting major spikes in subscribe/unsubscribe requests, there is no need to create a table with this as row key for efficient retrieval.</td>
</tr>
+
<tr>
<td>Status</td>
<td>String</td>
-<td>"Pending" or "Verified." When a customer clicks on a subscribe link in a web site, a row for the subscriber is created and set to Pending, and an email is sent to the submitted email address. That email contains a hyperlink that the recipient can click in order to confirm the subscription. When the recipient clicks that link, the status is changed to "Verified". While the status is "Pending", no emails are sent to the address except for the initial email with the Confirm Subscription link.</td>
-</tr></table>
+<td>"Pending" or "Verified". When the row is initially created for a new subscriber, the value is Pending. It changes to Verified only after the new subscriber clicks the Confirm hyperlink in the welcome email. If a message is sent to this list while the row is in Pending status, no email is sent to the prospective subscriber.</td>
+</tr>
+
+</table>
The following list shows an example of what data in the table might look like.
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>contoso1</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>0</td>
</tr>
+
<tr>
<th>Description</th>
<td>Contoso University History Department announcements</td>
</tr>
+
<tr>
<th>FromEmailAddress</th>
<td>donotreply@contoso.edu</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>contoso1</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>student1@domain.com</td>
</tr>
+
<tr>
<th>SubscriberGUID</th>
<td>6f32b03b-90ed-41a9-b8ac-c1310c67b66a</td>
</tr>
+
<tr>
<th>Status</th>
<td>Verified</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>contoso1</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>student2@domain.com</td>
</tr>
+
<tr>
<th>SubscriberGUID</th>
<td>01234567-90ed-41a9-b8ac-c1310c67b66a</td>
</tr>
+
<tr>
<th>Status</th>
<td>Verified</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>fabrikam1</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>0</td>
</tr>
+
<tr>
<th>Description</th>
<td>Fabrikam Engineering job postings</td>
</tr>
+
<tr>
<th>FromEmailAddress</th>
<td>donotreply@fabrikam.com</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>fabrikam1</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>applicant1@domain.com</td>
</tr>
+
<tr>
<th>SubscriberGUID</th>
<td>76543210-90ed-41a9-b8ac-c1310c67b66a</td>
</tr>
+
<tr>
<th>Status</th>
<td>Verified</td>
</tr>
+
</table>
### Message table ###
-The Message table stores information about messages that are scheduled to be sent to a mailing list. Administrators create and edit rows in this table using a web page, and the worker roles use it to pass information about each email from Worker Role A to Worker Role B.
+The Message table stores information about messages that are scheduled to be sent to a mailing list. Administrators create and edit rows in this table using web pages, and the worker roles use it to pass information about each email from worker role A to worker role B.
-The row key for the Message table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the message. Rows that have the email address as the row key have information about the message, the mailing list, and the subscriber -- everything needed to send an email.
+The row key for the Message table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the message, such as the mailing list to send it to and when it should be sent. Rows that have the email address as the row key have all of the information needed to send an email to that email address.
In relational database terms, rows with row key "0" are equivalent to a Message table. Rows with row key = email address are equivalent to a join query view for MailingList, Message, and Subscriber information.
The following grid shows row properties for the Message table rows that have information about the message itself.
<table border="1">
+
<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
+<th>Property</th>
+<th>Data Type</th>
+<th>Description</th>
</tr>
+
<tr>
<td>PartitionKey</td>
<td>String</td>
-<td>MessageRef: A unique value created by getting the Ticks value from <code>DateTime.Now</code> when the row is created. Since rows are returned in partition key order this ensures that when browsing through messages in the UI, they are displayed in the order in which they were created. (A GUID could be used here but would result in random order, and a sort would be necessary).</td>
+<td>MessageRef: A unique value created by getting the Ticks value from <code>DateTime.Now</code> when the row is created. Since rows are returned in partition key order this ensures that when browsing through messages in a web page, they are displayed in the order in which they were created. (A GUID could be used here but would result in random order, and a sort would be necessary).</td>
</tr>
+
<tr>
<td>RowKey</td>
<td>String</td>
-<td>The constant "0".</code>.</td>
+<td>The constant "0".</td>
</tr>
+
+<tr>
<td>ScheduledDate</td>
-<td>String</td>
+<td>Date</td>
<td>The date the message is scheduled to be sent.</td>
</tr>
-<tr>
+
<tr>
<td>SubjectLine</td>
<td>String</td>
<td>The subject line of the email.</td>
</tr>
+
<tr>
<td>ListName</td>
<td>String</td>
-<td>The name of the list that this message is to be sent to. The same as the partition key of the MailingList table.</td>
+<td>The list that this message is to be sent to.</td>
</tr>
+
<tr>
<td>Status</td>
<td>String</td>
-<td><ul><li>"Pending" -- Worker Role A still needs to create queue messages to schedule emails, on the scheduled date.</li>
-<li>"Processing" -- Queue messages have been created for all emails in the list.</li>
-<li>"Completed" -- Worker Role B has finished processing all queue messages (all emails have been sent).</li></ul></td>
+<td><ul><li>"Pending" -- Worker role A needs to create queue messages to schedule emails.</li>
+<li>"Processing" -- Worker role A has created queue work items for all emails in the list, but not all emails have been sent yet.</li>
+<li>"Completed" -- Worker role B has finished processing all queue work items (all emails have been sent).</li></ul></td>
</tr>
+
</table>
-The following grid shows row properties for the rows in the Message table that contain information for an individual email. These rows also help ensure that a message is sent to each email address only once, and they help determine when all emails for a message have been sent. When Worker Role A creates a queue message for an email to be sent to a list, it creates a row in this table. Worker Role B updates the EmailSent property to true when it sends the email. When the EmailSent property is true for all rows for a Message whose status is Processing, Worker Role A sets the Message status to Completed.
+The following grid shows row properties for the rows in the Message table that contain information for an individual email. When worker role A creates a queue message for an email to be sent to a list, it creates a row in this table. Worker role B updates the EmailSent property to true when it sends the email. When the EmailSent property is true for all rows for a Message whose status is Processing, worker role A sets the Message status to Completed.
<table border="1">
+
<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
+<th>Property</th>
+<th>Data Type</th>
+<th>Description</th>
</tr>
+
<tr>
<td>PartitionKey</td>
<td>String</td>
<td>MessageRef.</td>
</tr>
+
<tr>
<td>RowKey</td>
<td>String</td>
-<td>EmailAddress: The destination email address from the MailingList table.</code>.
+<td>EmailAddress: The destination email address from the MailingList table.
</td>
+</tr>
+
<tr>
<td>ScheduledDate</td>
-<td>DateTime</td>
-<td>The date the message is scheduled to be sent, from the Message table.</td>
+<td>Date</td>
+<td>The date the message is scheduled to be sent, from the "0" row of the Message table.</td>
</tr>
+
<tr>
+<td>SubjectLine</td>
+<td>String</td>
+<td>The subject line of the email, from the "0" row of the Message table.</td>
</tr>
+
<tr>
<td>From EmailAddress</td>
<td>String</td>
<td>The "From" email address, from the MailingList table.</td>
</tr>
-<tr>
-<td>SubjectLine</td>
-<td>String</td>
-<td>The subject line of the email, from the Message table.</td>
-</tr>
+
<tr>
<td>EmailSent</td>
<td>Boolean</td>
-<td>null or false means the email has not been sent yet; true means the email has been sent.</td>
+<td>False means the email has not been sent yet; true means the email has been sent.</td>
</tr>
+
</table>
-(These rows don't have blob references for the email body .html and .txt files that contain the body of the email, because that value is derived from the MessageRef value.)
+There is redundant data in these rows, which you would typically avoid in a relational database. But in this case you are trading some of the disadvantages of redundant data for the benefit of greater processing efficiency and scalability. Because all of the data needed for an email is present in one of these rows, worker role B only needs to read one row in order to send an email when it pulls a work item off the queue.
+
+(You might wonder where the body of the email comes from. These rows don't have blob references for the files that contain the body of the email, because that value is derived from the MessageRef value.)
The following list shows an example of what data in the table might look like.
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>634852858215726983</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>0</td>
</tr>
+
<tr>
<th>ScheduledDate</th>
<td>2012-10-15</td>
</tr>
+
<tr>
<th>SubjectLine</th>
<td>New lecture series</td>
</tr>
+
<tr>
<th>ListName</th>
<td>contoso1</td>
</tr>
+
<tr>
<th>Status</th>
<td>Processing</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>634852858215726983</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>student1@contoso.edu</td>
</tr>
+
<tr>
<th>ScheduledDate</th>
<td>2012-10-15</td>
</tr>
+
+<tr>
+<th>SubjectLine</th>
+<td>New lecture series</td>
+</tr>
+
<tr>
<th>FromEmailAddress</th>
<td>donotreply@contoso.edu</td>
</tr>
+
<tr>
-<th>SubjectLine</th>
-<td>New lecture series</td>
+<th>ScheduledDate</th>
+<td>2012-10-15</td>
</tr>
+
<tr>
<th>EmailSent</th>
<td>true</td>
</tr>
+
</table>
<hr/>
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>634852858215726983</td>
</tr>
+
<tr>
-<th>Row Key</td>
-<td>student2@contoso.edu</td>
-</tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>student1@contoso.edu</td>
</tr>
+
<tr>
<th>ScheduledDate</th>
<td>2012-10-15</td>
</tr>
-<tr>
-<th>FromEmailAddress</th>
-<td>donotreply@contoso.edu</td>
-</tr>
-<tr>
+
<tr>
<th>SubjectLine</th>
<td>New lecture series</td>
</tr>
+
+<tr>
+<th>FromEmailAddress</th>
+<td>donotreply@contoso.edu</td>
+</tr>
+
<tr>
<th>EmailSent</th>
<td>false</td>
</tr>
+
</table>
+<hr/>
+
<table border="1">
+
<tr>
<th width="200">Partition Key</th>
<td>634852858215123456</td>
</tr>
+
<tr>
-<th>Row Key</td>
+<th>Row Key</th>
<td>0</td>
</tr>
+
<tr>
<th>ScheduledDate</th>
<td>2012-10-31</td>
</tr>
+
<tr>
<th>SubjectLine</th>
<td>New display at Henry Art Gallery</td>
</tr>
+
<tr>
<th>ListName</th>
<td>contoso2</td>
</tr>
+
<tr>
<th>Status</th>
<td>Pending</td>
</tr>
+
</table>
-<h2><a name="queues"></a><span class="short-header">Queues</span>Windows Azure Storage Queues</h2>
+<br/>
+<br/>
+
+<h2><a name="queues"></a><span class="short-header">Queues</span>Windows Azure Queues</h2>
+
+Windows Azure queues facilitate communication between tiers of this multi-tier application, and between worker roles in the back-end tier. Windows Azure also provides the Service Bus queue service. Service Bus queues have features that are not needed for this application, they are more complex to configure and program, and they may cost more. For information about Service Bus queues, see the following resource:
-Windows Azure storage queues facilitate communication between tiers of a multi-tier application, and between servers (worker roles) in the back-end tier. Windows Azure also provides another kind of queue service that has more features but is more complex to configure and program: Service Bus queues. For information about the difference between storage queues and Service Bus queues, see [Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted][].
+* [Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted][sbqueuecomparison].
-The Azure Email Service application uses two queues.
+The Message table could be used alone to coordinate work between worker role A and worker role B without using the Windows Azure Queue service, but using queues makes the application scalable. Using just the table would work when you have just one instance of worker role B. When you have multiple instances of worker role B, pulling items off the queue is an efficient and effective way to apportion work among the worker role instances.
+
+The Azure Email Service application uses two queues, named AzureMailQueue and AzureMailSubscribeQueue.
### AzureMailQueue ###
-The AzureMailQueue queue coordinates the sending of emails to email lists. Worker Role A places a work item on the queue for each email to be sent, and Worker Role B pulls a work item from the queue and sends the email.
+The AzureMailQueue queue coordinates the sending of emails to email lists. Worker role A places a work item on the queue for each email to be sent, and worker role B pulls a work item from the queue and sends the email.
-The queue message string contains MessageRef (partition key to the Message table) and EmailAddress (row key to the Message table) values. Worker Role B uses these values to look up the row in the Message table that contains all of the information needed to send the email.
+A queue work item contains a comma-delimited string that consists of the MessageRef (partition key to the Message table) and EmailAddress (row key to the Message table) values. Worker role B uses these values to look up the row in the Message table that contains all of the information needed to send the email.
-When traffic spikes, the Cloud Service can be reconfigured so that multiple instances of Worker Role B are instantiated, and each of them can independently pull work items off the queue.
+When traffic spikes, the Cloud Service can be reconfigured so that multiple instances of worker role B are instantiated, and each of them can independently pull work items off the queue.
### AzureMailSubscribeQueue ###
-The AzureMailSubscribeQueue queue coordinates the sending of subscription confirmation emails to email lists. In response to a subscribe service call, the Web API service method places a work item on the queue for the email address that needs confirmation. Worker Role B pulls a work item from the queue and sends the email.
+The AzureMailSubscribeQueue queue coordinates the sending of subscription confirmation emails. In response to a subscribe service method call, the service method places a work item on the queue. Worker role B pulls the work item from the queue and sends the subscription confirmation email.
+
+A queue work item contains the subscriber GUID. This value uniquely identifies an email address and the list to subscribe it to, which is all that worker role B needs to send a confirmation email. The subscriber GUID is not the partition key or the row key, but the volume of subscriptions is not expected to be high enough to cause the search for a GUID to be a performance bottleneck.
+
+<h2><a name="blobs"></a><span class="short-header">Blobs</span>Windows Azure Blobs</h2>
+
+Blobs are "binary large objects." The Windows Azure Blob service provides a means for uploading and storing files in the cloud.
-The queue message string contains the subscriber GUID. This value uniquely identifies an email address and the list to subscribe it to, which is all Worker Role B needs to send a confirmation email. The subscriber GUID is not the partition key or the row key, but the volume of subscriptions is not expected to be high enough to cause the search for GUID to be a performance bottleneck.
+Azure Mail Service administrators store the body of an email in HTML form in an .html file and in plain text in a .txt file. When they schedule an email, they upload these files in the Message Create web page, and the ASP.NET MVC controller for the page stores the uploaded file in a Windows Azure blob.
-<h2><a name="blobs"></a><span class="short-header">Blobs</span>Windows Azure Storage Blobs</h2>
+Blobs are stored in blob containers, much like files are stored in folders. The Azure Mail Service application uses a single blob container, named AzureMailBlobContainer. The name of the blobs in the container is derived by concatenating the MessageRef value with the file extension, for example:
+634852858215726983.html and 634852858215726983.txt.
-The body of an email is stored in .html and .txt files, and in the Create and Edit pages for Messages, administrators upload both file types for each message. When the files are uploaded, they are stored in blobs.
+<h2><a name="futurereleases"></a><span class="short-header">Future releases</span>Functionality left for future releases</h2>
-There is a single blob container, named AzureMailBlobContainer. The name of the blobs in the container is derived by concatenating the MessageRef value with the file extension.
+Like most development projects, deadlines limited what could be included in the first release of this sample application. Some examples of features that could be added in the future include the following:
+
+* Authentication and authorization for administrator web pages.
+* Paging in the Index web pages.
+* Concurrency handling in the web pages. (Warn if one person's changes would be overwritten when two people edit the same table row at the same time.)
+* Handle "poison queue messages" that cause exceptions. When dequeuing queue messages, check the dequeue count to see if it was dequeued repeatedly, and if so log it and don't try to send the email or dequeue the message anymore.
+* A search page to facilitate looking up messages when there are too many to page through.
+* Enable specifying a time of day, not just a date, for sending messages.
+* Schedule emails to be divided up into batches. (For example, send 1,000 a day over seven days instead of 7,000 all at once.) This might be useful to limit email service costs.
+* Schedule a test email to a QA list first to verify that emails look as expected before sending to the full list. This can be done in the initial release but would require separate Message entries for the QA and the production message.
+* Archival functions to delete or archive old records in the Message table.
+* Monitoring functions to check for and report on messages that did not get sent because worker role B failed to send one or more email messages.
+* Support for automatic unsubscribe API offered by services such as Gmail.
+* Store more information to put in emails, such as name for salutation.
+* Store effective dates and cancellation dates to keep history of subscription activity instead of deleting rows to unsubscribe.
<h2><a name="nextsteps"></a><span class="short-header">Next steps</span>Next steps</h2>
-In the next tutorial, you'll download the sample project, configure your environment and configure the project for your environment, and test it locally and in the cloud. In tutorials that we'll add later, we'll show you how to build the project from scratch.
+In the next tutorial, you'll download the sample project, configure your development environment, configure the project for your environment, and test the project locally and in the cloud. In later tutorials you'll see how to build the project from scratch.
+
+(The following See Also links will be moved to the last tutorial in the series.)
+To learn more about how to work with the Windows Azure Table service, Queue service, and Blob service, see the following resources:
-[Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted]: http://msdn.microsoft.com/en-us/library/windowsazure/hh767287.aspx
+* [How to use the Queue Storage Service][queuehowto]
+* [How to use the Table Storage Service][tablehowto]
+* [How to use the Windows Azure Blob Storage Service in .NET][blobhowto]
+
+[tut2]: http://
+[tut3]: http://
+[tut4]: http://
+[tut5]: http://
+[sbqueuecomparison]: http://msdn.microsoft.com/en-us/library/windowsazure/hh767287.aspx
+[queuehowto]: http://www.windowsazure.com/en-us/develop/net/how-to-guides/queue-service/
+[tablehowto]: http://www.windowsazure.com/en-us/develop/net/how-to-guides/table-services/
+[blobhowto]: http://www.windowsazure.com/en-us/develop/net/how-to-guides/blob-storage/
[0]: ../../Shared/media/antares-iaas-preview-01.png
[1]: ../../Shared/media/antares-iaas-preview-05.png
@@ -521,6 +669,8 @@ In the next tutorial, you'll download the sample project, configure your environ
[mtas-unsubscribe-confirmation-page]: ../Media/mtas-unsubscribe-confirmation-page.png
[mtas-worker-roles-a-and-b]: ../Media/mtas-worker-roles-a-and-b.png
[mtas-message-processing]: ../Media/mtas-message-processing.png
-[mtas-unsubscribe-confirmed-page]: ../Media/unsubscribe-confirmed-page.png
-[mtas-unsubscribe-confirmed-page]: ../Media/unsubscribe-confirmed-page.png
-
+[mtas-subscribe-email]: ../Media/mtas-subscribe-email.png
+[mtas-subscribe-diagram]: ../Media/mtas-subscribe-diagram.png
+[]: ../Media/.png
+[]: ../Media/.png
+[]: ../Media/.png
View
862 DevCenter/dotNET/Tutorials/multi-tier-with-storage-2-download-and-run.md
@@ -1,526 +1,444 @@
<div chunk="../chunks/article-left-menu.md" />
+# Configuring and Deploying the Azure Email Service application
-# .NET Multi-Tier Application Using Storage Tables, Queues, and Blobs
+This is the second tutorial in a series of five that show how to build and deploy the Azure Email Service sample application. For information about the application and the tutorial series, see the [first tutorial in the series][firsttutorial].
-This tutorial series shows how to create a multi-tier ASP.NET web application that uses Windows Azure Storage tables, queues, and blobs. The tutorial assumes that you have no prior experience using Windows Azure. On completing the tutorial, you'll have a robust and scalable data-driven web application up and running in the cloud.
+This tutorial shows how to configure your computer for Azure development and how to deploy the Azure Email Service application to a Windows Azure Cloud Service by using Visual Studio 2012 or Visual Studio 2010 Express for Web.
-<h2><a name="whyanemaillistapp"></a><span class="short-header">Why Choose This App</span>Why an Email List Service Application</h2>
+You can open a Windows Azure account for free, and if you don't already have Visual Studio 2012, the SDK automatically installs Visual Studio 2012 for Web Express. So you can start developing for Windows Azure entirely for free.
-We chose an email list service for this sample application because it is the kind of application that needs to be robust and scalable, two features that make it especially appropriate for Windows Azure.
+In this tutorial you'll learn:
-### Robust
+* How to set up your computer for Windows Azure development by installing the Windows Azure SDK.
+* How to configure and test the Azure Email Service application on your local machine.
+* How to publish the email service to to Windows Azure.
-If a server fails while sending out emails to a large list, you want to be able to stand up a new server easily and quickly, and you want the application to pick up where it left off without losing or duplicating any emails. A Windows Azure Cloud Service web or worker role (virtual machine) is automatically replaced if it fails. And Windows Azure Storage queues and tables provide a means to implement server-to-server communication that can survive a failure without losing work.
+<div chunk="../../Shared/Chunks/create-account-and-websites-note.md" />
+
+### Tutorial segments
-### Scalable
+1. [Set up the development environment][]
+2. [Set up a free Windows Azure Account][]
+3. [Create a Windows Azure Storage Account][]
+4. [Optional: Install Azure Storage Explorer][]
+5. [Create a Cloud Server in the Management Portal][]
+6. [Download and Configure the Completed Solution][]
+7. [Run the Application from Visual Studio][]
+8. [Viewing Developer Storage in Visual Studio][]
+9. [Configure the Application for Azure Storage][]
+10. [Deploy the Application to Azure][]
+11. [Promote the Application from Staging to Production][]
+12. [Get a SendGrid account][]
+13. [Configure and View Trace Data][]
-An email service also must be able to handle spikes in traffic, since sometimes you are sending emails to small lists and sometimes to very large lists. In many hosting environments, you have to purchase and maintain sufficient hardware to handle the spikes in workload, and you're paying for all that capacity 100% of the time although you might only use it 5% of the time. With Windows Azure, you pay only for the amount of computing power that you actually need for only as long as you need it. To scale up for a large mailing, you just change a configuration setting to increase the number of servers you have available to process the workload, and this can be done programmatically.
+<h2><a name="setupdevenv"></a><span class="short-header">Set up environment</span>Set up the development environment</h2>
-<h2><a name="whatyoulllearn"></a><span class="short-header">What You'll Learn</span>What You'll Learn</h2>
+To start, set up your development environment by installing the Windows Azure SDK for the .NET Framework.
-In this tutorial series you'll learn the following:
+1. To install the Windows Azure SDK for .NET, click the link that corresponds to the version of Visual Studio you are using. If you don't have Visual Studio installed yet, use the Visual Studio 2012 link.<br/>
+[Windows Azure SDK for Visual Studio 2010][]<br/>
+[Windows Azure SDK for Visual Studio 2012][]<br/>
+If you don't have Visual Studio installed yet, it will be installed by the link.<br/>
-* How to enable your machine for Windows Azure development by installing the Windows Azure SDK.
-* How to create a Visual Studio cloud project with an MVC 4 web role and two worker roles.
-* How to publish the cloud project to a Windows Azure Cloud Service.
-* How to publish the MVC 4 project to a Windows Azure Web Site if you prefer, and still use the worker roles in the Cloud Service.
-* How to use Windows Azure storage queues for communication between tiers or between worker roles.
-* How to use Windows Azure storage tables as a highly scalable data store for non-relational data.
-* How to use Windows Azure storage blobs to store files in the cloud.
-* How to use Azure Storage Explorer to work with tables, queues, and blobs.
+2. When you are prompted to run or save VWDOrVs11AzurePack.exe, click **Run**.
-<h2><a name="wawsvswacs"></a><span class="short-header">Application architecture</span>Overview of application architecture</h2>
+3. In the Web Platform Installer window, click **Install** and proceed with the installation.
-This is the first tutorial in a series, and it provides an overview of the application and its architecture.
+ ![Web Platform Installer - Windows Azure SDK for .NET][mtas-wpi-installer]<br/>
-The front-end is a set of web pages and a service method that enable administrators to manage email lists, and subscribers to subscribe and unsubscribe. The front-end uses ASP.NET MVC 4 and Web API, and it runs in a web role in a Windows Azure Cloud Service. The back-end is a pair of worker roles running in the same Cloud Service and do the work of sending emails.
+When the installation is complete, you have everything necessary to start developing.
-The application stores email lists and subscriber information in Windows Azure storage tables. It stores email content in blobs (a plain text file and an HTML file for each email). It uses Windows Azure storage queues for communication between the front-end service method and the one of the back-end worker roles, and between the two worker roles.
+<h2><a name="setupwindowsazure"></a><span class="short-header">Create Windows Azure Account</span>Set up a free Windows Azure Account</h2>
-The following diagram provides a high-level picture of the application architecture that is used in this tutorial.
+The next step is to create a Windows Azure account.
-![Application architecture overview][mtas-architecture-overview]
+1. Browse to [Windows Azure](http://www.windowsazure.com "Windows Azure").
-An alternative architecture that would also work is to run the front-end in a Windows Azure Web Site.
+2. Click the **Free trial** link and follow the instructions.
-![Alternative application architecture][mtas-alternative-architecture]
+<h2><a name="createWASA"></a><span class="short-header">Create Storage Account</span>Create a Windows Azure Storage Account</h2>
-This alternative architecture might offer some cost benefits, because a Windows Azure Web Site may be less expensive for similar capacity compared to a web role running in a Cloud Service. For this tutorial we have the entire application in a Cloud Service because that simplifies configuration and deployment. The tutorial explains the differences between the two architectures, so that when you implement your own application you can choose the architecture that you prefer.
+1. In your browser, open the [Windows Azure Management Portal][NewPortal].
-<h2><a name="frontendoverview"></a><span class="short-header">Front-end overview</span>Front-end overview</h2>
+2. In the [Windows Azure Management Portal][NewPortal], click **Storage**, then click **New**.
-The front-end includes web pages that administrators of the service use to manage email lists and to create and schedule messages to be sent to the lists.
+ ![New Storage][mtas-portal-new-storage]
-![Mailing List Index Page][mtas-mailing-list-index-page]
+3. Click **Quick Create**.
-![Subscriber Index Page][mtas-subscribers-index-page]
+ Alternatively, you can click the **Create a Storage Account** link, which also selects the Quick Create link.
-![Message Index Page][mtas-message-index-page]
+ ![Quick Create][mtas-storage-quick]
-![Message Create Page][mtas-message-create-page]
+4. In the URL input box, enter a URL prefix. Set the region to the area where you will deploy the application. Uncheck the **Enable Geo-Replication** check box. <ins>Why?</ins> Finally, click **Create Storage Account**. In the image below, a storage account is created with the URL aestest.core.windows.net.
-Clients of the service are companies that give their customers an opportunity to sign up for a list on the client web site. For example, Contoso University wants a list for History Department announcements. When a student interested in History Department announcements clicks a link on the Contoso University web site, Contoso University makes a web service call to this application. The service method causes an email to be sent to the customer. That email contains a link, and when the recipient clicks the link, a page welcoming the customer to the History Department Announcements list is displayed.
+ ![create storage with URL prefix][mtas-create-storage-url-test]
-![Welcome to list page][mtas-subscribe-confirmation-page]
+ This step can take several minutes to complete. While you are waiting, you can repeat these steps and create a production storage acccount. It's often convenient to have a test storage account to use for local development, another test storage account for testing in Windows Azure, and a production storage account. In this tutorial we will primarly use the Azure storage test account when we are running the application from Visual Studio.
-Every email sent by the service includes a hyperlink that can be used to unsubscribe. If a recipient clicks the link, a web page asks for confirmation of intent to unsubscribe. If the recipient clicks the Confirm button, a page is displayed confirming that the person has been removed from the list.
+5. Click the test account you created in the previous step, then click the **Manage Keys** icon.
-![Confirm unsubscribe page][mtas-unsubscribe-query-page]
+ ![Manage Keys][mtas-manage-keys]<br/>
-![!Unsubscribe confirmed page][mtas-unsubscribe-confirmation-page]
+ ![Keys GUID][mtas-guid-keys]<br/>
-<h2><a name="backendoverview"></a><span class="short-header">Back-end overview</span>Back-end overview</h2>
+ You will need the primary or secondary access key throughout this tutorial. The **Primary Access Key** and **Secondary Access Key** both provide a shared secret that you can use to access storage. The secondary key gives the same access as the primary key and is used for backup purposes. You can regenerate each key independently and rotate keys to help insure they are secure. The keys in the image above are not valid, they were regenerated after the image was captured.
-Email lists and email messages scheduled to be sent are stored in Windows Azure Storage tables. When an administrator schedules an email to be sent, a row containing the scheduled date and other data is placed on the Message table. A worker role (virtual machine) running in a Windows Azure Cloud Service periodically scans the Message table looking for messages that need to be sent (we'll call this Worker Role A). When Worker Role A finds a message needing to be sent, it looks up all the email addresses in the destination email list, puts the information needed to send the email in the Message table, and creates a work item on a queue for each email that needs to be sent. A second worker role (Worker Role B) polls the queue for work items. When Worker Role B finds a work item, it processes the item by sending the email and then deletes the work item from the queue. The following diagram shows these relationships.
+<h2><a name="installASE"></a><span class="short-header">Install ASE</span>Optional: Install Azure Storage Explorer</h2><ins>I don't think we should make this optional.</ins>
-![Worker roles A and B][mtas-worker-roles-a-and-b]
+Azure Storage Explorer is a tool that you can use to query and update Windows Azure storage tables, queues, and blobs. You will use it throughout these tutorials to verify that data is updated correctly and to create test data.
-When Worker Role A creates a queue work item, it also adds a row to the Message table. Worker Role A reads this row to get the information it needs to send the email.
+1. Install [Azure Storage Explorer](http://azurestorageexplorer.codeplex.com/ ).
-The row in the Message table that provides information for one email also includes a property that indicates whether the email has actually been sent. When Worker Role B sends an email, it updates this property to indicate that the email has been sent. If Worker Role A goes down while creating queue work items for a message, it might create duplicate queue work items when it restarts, but the tracking row ensures that duplicate emails won't be sent. (Worker Role B checks the row before sending an email.)
+2. Launch Azure Storage Explorer and click **Add Account**.
-![Queue message creation and processing][mtas-message-processing]
+ ![Add ASE Account][mtas-ase-add]<br/>
-<h2><a name="tables"></a><span class="short-header">Tables</span>Windows Azure Storage Tables</h2>
+3. Enter the name of the test storage account and a key that you created previously.
-Windows Azure storage tables are a NoSQL data store, not a relational database. That makes them a good choice when scalability is more important than data normalization and relational integrity. For example, in this application, worker roles create a row every time a queue workitem is created and the row is updated every time an email is sent, which might be a performance bottleneck if a relational database were used.
+ ![Add ASE Account][mtas-ase-add2]<br/>
-In a Windows Azure storage table, every row has a *partition key* and a *row key* that uniquely identifies the row. The partition key divides the table up both logically and physically into partitions. Within a partition, the row key uniquely identifies a row.
+ In the image below, a new table called *mytable* is created.
-### MailingList table ###
+ ![Add ASE Account][mtas-ase-add3]<br/>
-The MailingList table stores information about mailing lists and information about the subscribers to mailing lists. Administrators use web pages to create and edit mailing lists, and clients and subscribers use a set of web pages and service method to subscribe and unsubscribe.
+<h2><a name="createcloudsvc"></a><span class="short-header">Create Cloud Service</span>Create a Cloud Service in the Management Portal</h2>
-In NoSQL tables, different rows can have different schemas, and this flexibility is commonly used to make one table store data that would require multiple tables in a relational database. For example, to store mailing list data in SQL Database you could use three tables: a MailingList table that stores information about the list, a Subscriber table that stores information about subscribers, and a MailingListSubscriber table that associates mailing lists with subscribers and vice versa. In the NoSQL table in this application, all of those functions are rolled into one table named MailingList.
+1. In your browser, open the [Windows Azure Management Portal][NewPortal].
-The row key for the MailingList table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the mailing list. Rows that have the email address as the row key have information about the subscribers to the list.
+2. Click **Cloud Services** then click the **New** icon.
-In other words, rows with row key "0" are equivalent to a MailingList table in a relational database. Rows with row key = email address are equivalent to a Subscriber table and a MailingListSubscriber association table in a relational database.
+ ![Quick Cloud][mtas-new-cloud]<br/>
-Making one table serve multiple purposes in this way facilitates better performance. In a relational database three tables would have to be read, then three sets of rows would have to be sorted and matched up against each other, which takes time. Here just one table is read and its rows are automatically returned in partition key and row key order.
+ Alternatively, you can click the **Create a Cloud Service** link, which also slects Quick Create.
-The following grid shows row properties for the rows that show mailing list information (row key = "0").
+3. Click **Quick Create**.
-<table border="1">
-<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
-</tr>
-<tr>
-<td>PartitionKey</td>
-<td>String</td>
-<td>ListName: The name (unique identifier) of the mailing list. The typical use for the table is to retrieve all information for a mailing list, so this is an efficient way to partition the table.</td>
-</tr>
-<tr>
-<td>RowKey</td>
-<td>String</td>
-<td>The constant "0".</td>
-</tr>
-<tr>
-<td>Description</td>
-<td>String</td>
-<td>Description of the mailing List, for example: "Contoso University History Department announcements".</td>
-</tr>
-<tr>
-<td>FromEmailAddress</td>
-<td>String</td>
-<td>The "From" email address in the emails sent to this list, for example: "contoso.edu".</td>
-</tr></table>
-
-The following grid shows row properties for the rows that contain subscriber information for the list (row key = email address).
-
-<table border="1">
-<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
-</tr>
-<tr>
-<td>PartitionKey</td>
-<td>String</td>
-<td>ListName: The name (unique identifier) of the mailing list.</td>
-</tr>
-<tr>
-<td>RowKey</td>
-<td>String</td>
-<td>EmailAddress: The subscriber email address.</td>
-</tr>
-<tr>
-<td>SubscriberGUID</td>
-<td>String</td>
-<td>Generated when the email address is added to a list, used in subscribe and unsubscribe links so that it's not too easy to subscribe or unsubscribe someone else's email address. Not expecting major spikes in subscribe/unsubscribe requests, so no need to create a table with this as row key for efficient retrieval..</td>
-</tr>
-<tr>
-<td>Status</td>
-<td>String</td>
-<td>"Pending" or "Verified." When a customer clicks on a subscribe link in a web site, a row for the subscriber is created and set to Pending, and an email is sent to the submitted email address. That email contains a hyperlink that the recipient can click in order to confirm the subscription. When the recipient clicks that link, the status is changed to "Verified". While the status is "Pending", no emails are sent to the address except for the initial email with the Confirm Subscription link.</td>
-</tr></table>
-
-The following list shows an example of what data in the table might look like.
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>contoso1</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>0</td>
-</tr>
-<tr>
-<th>Description</th>
-<td>Contoso University History Department announcements</td>
-</tr>
-<tr>
-<th>FromEmailAddress</th>
-<td>donotreply@contoso.edu</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>contoso1</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>student1@domain.com</td>
-</tr>
-<tr>
-<th>SubscriberGUID</th>
-<td>6f32b03b-90ed-41a9-b8ac-c1310c67b66a</td>
-</tr>
-<tr>
-<th>Status</th>
-<td>Verified</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>contoso1</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>student2@domain.com</td>
-</tr>
-<tr>
-<th>SubscriberGUID</th>
-<td>01234567-90ed-41a9-b8ac-c1310c67b66a</td>
-</tr>
-<tr>
-<th>Status</th>
-<td>Verified</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>fabrikam1</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>0</td>
-</tr>
-<tr>
-<th>Description</th>
-<td>Fabrikam Engineering job postings</td>
-</tr>
-<tr>
-<th>FromEmailAddress</th>
-<td>donotreply@fabrikam.com</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>fabrikam1</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>applicant1@domain.com</td>
-</tr>
-<tr>
-<th>SubscriberGUID</th>
-<td>76543210-90ed-41a9-b8ac-c1310c67b66a</td>
-</tr>
-<tr>
-<th>Status</th>
-<td>Verified</td>
-</tr>
-</table>
-
-### Message table ###
-
-The Message table stores information about messages that are scheduled to be sent to a mailing list. Administrators create and edit rows in this table using a web page, and the worker roles use it to pass information about each email from Worker Role A to Worker Role B.
-
-The row key for the Message table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the message. Rows that have the email address as the row key have information about the message, the mailing list, and the subscriber -- everything needed to send an email.
-
-In relational database terms, rows with row key "0" are equivalent to a Message table. Rows with row key = email address are equivalent to a join query view for MailingList, Message, and Subscriber information.
-
-The following grid shows row properties for the Message table rows that have information about the message itself.
-
-<table border="1">
-<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
-</tr>
-<tr>
-<td>PartitionKey</td>
-<td>String</td>
-<td>MessageRef: A unique value created by getting the Ticks value from <code>DateTime.Now</code> when the row is created. Since rows are returned in partition key order this ensures that when browsing through messages in the UI, they are displayed in the order in which they were created. (A GUID could be used here but would result in random order, and a sort would be necessary).</td>
-</tr>
-<tr>
-<td>RowKey</td>
-<td>String</td>
-<td>The constant "0".</code>.</td>
-</tr>
-<td>ScheduledDate</td>
-<td>String</td>
-<td>The date the message is scheduled to be sent.</td>
-</tr>
-<tr>
-<tr>
-<td>SubjectLine</td>
-<td>String</td>
-<td>The subject line of the email.</td>
-</tr>
-<tr>
-<td>ListName</td>
-<td>String</td>
-<td>The name of the list that this message is to be sent to. The same as the partition key of the MailingList table.</td>
-</tr>
-<tr>
-<td>Status</td>
-<td>String</td>
-<td><ul><li>"Pending" -- Worker Role A still needs to create queue messages to schedule emails, on the scheduled date.</li>
-<li>"Processing" -- Queue messages have been created for all emails in the list.</li>
-<li>"Completed" -- Worker Role B has finished processing all queue messages (all emails have been sent).</li></ul></td>
-</tr>
-</table>
-
-The following grid shows row properties for the rows in the Message table that contain information for an individual email. These rows also help ensure that a message is sent to each email address only once, and they help determine when all emails for a message have been sent. When Worker Role A creates a queue message for an email to be sent to a list, it creates a row in this table. Worker Role B updates the EmailSent property to true when it sends the email. When the EmailSent property is true for all rows for a Message whose status is Processing, Worker Role A sets the Message status to Completed.
-
-<table border="1">
-<tr>
-<th>Property</td>
-<th>Data Type</td>
-<th>Description</td>
-</tr>
-<tr>
-<td>PartitionKey</td>
-<td>String</td>
-<td>MessageRef.</td>
-</tr>
-<tr>
-<td>RowKey</td>
-<td>String</td>
-<td>EmailAddress: The destination email address from the MailingList table.</code>.
-</td>
-<tr>
-<td>ScheduledDate</td>
-<td>DateTime</td>
-<td>The date the message is scheduled to be sent, from the Message table.</td>
-</tr>
-<tr>
-</tr>
-<tr>
-<td>From EmailAddress</td>
-<td>String</td>
-<td>The "From" email address, from the MailingList table.</td>
-</tr>
-<tr>
-<td>SubjectLine</td>
-<td>String</td>
-<td>The subject line of the email, from the Message table.</td>
-</tr>
-<tr>
-<td>EmailSent</td>
-<td>Boolean</td>
-<td>null or false means the email has not been sent yet; true means the email has been sent.</td>
-</tr>
-</table>
-
-(These rows don't have blob references for the email body .html and .txt files that contain the body of the email, because that value is derived from the MessageRef value.)
-
-The following list shows an example of what data in the table might look like.
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>634852858215726983</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>0</td>
-</tr>
-<tr>
-<th>ScheduledDate</th>
-<td>2012-10-15</td>
-</tr>
-<tr>
-<th>SubjectLine</th>
-<td>New lecture series</td>
-</tr>
-<tr>
-<th>ListName</th>
-<td>contoso1</td>
-</tr>
-<tr>
-<th>Status</th>
-<td>Processing</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>634852858215726983</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>student1@contoso.edu</td>
-</tr>
-<tr>
-<th>ScheduledDate</th>
-<td>2012-10-15</td>
-</tr>
-<tr>
-<th>FromEmailAddress</th>
-<td>donotreply@contoso.edu</td>
-</tr>
-<tr>
-<th>SubjectLine</th>
-<td>New lecture series</td>
-</tr>
-<tr>
-<th>EmailSent</th>
-<td>true</td>
-</tr>
-</table>
-
-<hr/>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>634852858215726983</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>student2@contoso.edu</td>
-</tr>
-<th>Row Key</td>
-<td>student1@contoso.edu</td>
-</tr>
-<tr>
-<th>ScheduledDate</th>
-<td>2012-10-15</td>
-</tr>
-<tr>
-<th>FromEmailAddress</th>
-<td>donotreply@contoso.edu</td>
-</tr>
-<tr>
-<tr>
-<th>SubjectLine</th>
-<td>New lecture series</td>
-</tr>
-<tr>
-<th>EmailSent</th>
-<td>false</td>
-</tr>
-</table>
-
-<table border="1">
-<tr>
-<th width="200">Partition Key</th>
-<td>634852858215123456</td>
-</tr>
-<tr>
-<th>Row Key</td>
-<td>0</td>
-</tr>
-<tr>
-<th>ScheduledDate</th>
-<td>2012-10-31</td>
-</tr>
-<tr>
-<th>SubjectLine</th>
-<td>New display at Henry Art Gallery</td>
-</tr>
-<tr>
-<th>ListName</th>
-<td>contoso2</td>
-</tr>
-<tr>
-<th>Status</th>
-<td>Pending</td>
-</tr>
-</table>
-
-<h2><a name="queues"></a><span class="short-header">Queues</span>Windows Azure Storage Queues</h2>
-
-Windows Azure storage queues facilitate communication between tiers of a multi-tier application, and between servers (worker roles) in the back-end tier. Windows Azure also provides another kind of queue service that has more features but is more complex to configure and program: Service Bus queues. For information about the difference between storage queues and Service Bus queues, see [Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted][].
-
-The Azure Email Service application uses two queues.
-
-### AzureMailQueue ###
-
-The AzureMailQueue queue coordinates the sending of emails to email lists. Worker Role A places a work item on the queue for each email to be sent, and Worker Role B pulls a work item from the queue and sends the email.
-
-The queue message string contains MessageRef (partition key to the Message table) and EmailAddress (row key to the Message table) values. Worker Role B uses these values to look up the row in the Message table that contains all of the information needed to send the email.
-
-When traffic spikes, the Cloud Service can be reconfigured so that multiple instances of Worker Role B are instantiated, and each of them can independently pull work items off the queue.
-
-### AzureMailSubscribeQueue ###
-
-The AzureMailSubscribeQueue queue coordinates the sending of subscription confirmation emails to email lists. In response to a subscribe service call, the Web API service method places a work item on the queue for the email address that needs confirmation. Worker Role B pulls a work item from the queue and sends the email.
-
-The queue message string contains the subscriber GUID. This value uniquely identifies an email address and the list to subscribe it to, which is all Worker Role B needs to send a confirmation email. The subscriber GUID is not the partition key or the row key, but the volume of subscriptions is not expected to be high enough to cause the search for GUID to be a performance bottleneck.
-
-<h2><a name="blobs"></a><span class="short-header">Blobs</span>Windows Azure Storage Blobs</h2>
-
-The body of an email is stored in .html and .txt files, and in the Create and Edit pages for Messages, administrators upload both file types for each message. When the files are uploaded, they are stored in blobs.
-
-There is a single blob container, named AzureMailBlobContainer. The name of the blobs in the container is derived by concatenating the MessageRef value with the file extension.
-
-<h2><a name="nextsteps"></a><span class="short-header">Next steps</span>Next steps</h2>
-
-In the next tutorial, you'll download the sample project, configure your environment and configure the project for your environment, and test it locally and in the cloud. In tutorials that we'll add later, we'll show you how to build the project from scratch.
-
-
-[Windows Azure Queues and Windows Azure Service Bus Queues - Compared and Contrasted]: http://msdn.microsoft.com/en-us/library/windowsazure/hh767287.aspx
+4. In the URL input box, enter a URL prefix. Set the region to the area where you will deploy the application. Finally, click **Create Cloud Service**.
-[0]: ../../Shared/media/antares-iaas-preview-01.png
-[1]: ../../Shared/media/antares-iaas-preview-05.png
-[2]: ../../Shared/media/antares-iaas-preview-06.png
-[mtas-architecture-overview]: ../Media/mtas-architecture-overview.png
-[mtas-alternative-architecture]: ../Media/mtas-alternative-architecture.png
+ In the image below, a cloud service is created with the URL aescloud.cloudapp.net.
+
+ ![create storage with URL prefix][mtas-create-cloud]
+
+ You can move on to the next step without waiting for this step to complete.
+
+<h2><a name="downloadcnfg"></a><span class="short-header">Download and Configure</span>Download and Configure the Completed Solution</h2>
+
+1. Download the [completed solution](http://code.msdn.microsoft.com/site/search?f%5B0%5D.Type=SearchText&f%5B0%5D.Value=dykstra&sortBy=Date).
+
+2. Unzip the downloaded file and open it in Visual Studio.
+
+3. In **Solution Explorer**, right-click on MvcWebRole and click **Properties**.
+
+ ![Right Click Properties][mtas-rt-prop]<br/>
+
+4. Click the **Settings** tab and press the **Add Setting** button.
+ ![Blob6][]
+
+ A new **Setting1** entry will then show up in the settings grid.
+
+ **Note:** Don't change the default **Service Configuration** value of **All Configurations**.
+
+5. In the **Type** drop-down of the new **Setting1** entry, choose
+ **Connection String**.
+ ![Blob7][]
+
+6. Click the ellipsis (**...**) button at the right end of the **Setting1** entry.
+
+ The **Storage Account Connection String** dialog will open.
+
+7. Keep the default radio button **Use the Windows Azure storage emulator** (the Windows Azure storage simulated on your local machine).
+
+ ![Blob8][]
+
+8. Change the entry **Name** from **Setting1** to **StorageConnectionString**. You will reference this connection string throughout this series.
+
+ ![Blob9][]
+
+### What's Happening Under the Hood
+
+When you add a new setting with the **Add Settings** button, the new setting is added to the XML in the *ServiceDefinition.csdf* file and in each of the two *.cscfg* configuration files. The following XML is added by Visual Studio to the *ServiceDefinition.csdf* file.
+
+ <ConfigurationSettings>
+ <Setting name="StorageConnectionString" />
+ </ConfigurationSettings>
+
+The following XML is added to each *.cscfg* configuration file.
+
+ <Setting name="StorageConnectionString" value="UseDevelopmentStorage=true" />
+
+You can manually add settings to the *ServiceDefinition.csdf* file and the two *.cscfg* configuration files, but using the properties editor has the following advantages for connection strings:
+
+- You only add the new setting in one place, and the correct setting XML is added to all three files.
+- The correct XML is generated for the three settings files. The *ServiceDefinition.csdf* file defines settings that must be in each *.cscfg* configuration file. If the *ServiceDefinition.csdf* file and the two *.cscfg* configuration files settings are inconsistent, you can get the following error message from Visual Studio: *The current service model is out of sync. Make sure both the service configuration and definition files are valid.*
+
+ ![Config error][mtas-er1]<ins>Image missing</ins>
+
+The properties editor will not work until you resolve the problem.
+
+Examine the *ServiceConfiguration.Local.cscfg* file. The XML for worker role A and worker role B also contains a storage connection string specifying the development storage emulator. The storage connection strings in the two worker roles were provided by the download.
+
+<h2><a name="runVS"></a><span class="short-header">Run in VS</span>Run the Application from Visual Studio</h2>
+
+When you run the solution from Visual Studio, the *ServiceConfiguration.Local.cscfg* file is used. Later in the tutorial when you package the application for Azure deployment, the *ServiceConfiguration.Cloud.cscfg* file is used.
+
+1. Press CTRL+F5 to run the application.
+The application home page appears in your browser.
+
+ ![Run the App.][mtas-mailinglist1]
+
+2. Click the **Create New** button and enter some test data, then click the **Create** button.
+
+ ![Run the App.][mtas-create1]
+
+3. Create a couple more mailing list entries.
+
+ ![Mailing List Index Page][mtas-mailing-list-index-page]
+
+4. Click the *Subscribers* button and add some subscribers.
+
+ ![Subscriber Index Page][mtas-subscribers-index-page]
+
+4. Click the *Messages* button and add some messages. Don't change the scheduled date which defaults to one week in the future. The application can't send messages until you configure SendGrid.
+
+ ![Message Create Page][mtas-message-create-page]
+ <br/><br/>
+ ![Message Index Page][mtas-message-index-page]
+
+<h2><a name="StorageExpVS"></a><span class="short-header">Dev Storage</span>Viewing Developer Storage in Visual Studio</h2>
+
+1. Display **Server Explorer** in Visual Studio. From the menu bar choose **View, Server Explorer**.
+
+2. Expand the **(Development)** node underneath the **Windows Azure Storage** node. The following figure shows the blob and table resources you created in the previous steps.
+
+ ![Server Explorer][mtas-serverExplorer]
+
+3. Double click the **MailingList** table. The following image shows a portion of the data in the Message table. Notice the two different schemas in the table.
+
+ ![VS storage explorer][mtas-wasVSdata]
+
+ The **Windows Azure Storage** browser in **Server Explorer** provides a convienent read-only view of Windows Azure Storage resources. You can't use **Server Explorer** to delete Windows Azure Storage resources. You can use [Azure Storage Explorer](http://azurestorageexplorer.codeplex.com/ ) to delete development storage resources.
+
+<h2><a name="conf4azureStorage"></a><span class="short-header">Configure Storage</span>Configure the Application for Azure Storage</h2>
+1. In your browser, open the [Windows Azure Management Portal][NewPortal].
+2. Click the **Storage** Tab, then click the test account you created in the previous step, and then click the **Manage Keys** icon.
+
+ ![Manage Keys][mtas-manage-keys]<br/>
+
+ ![Keys GUID][mtas-guid-keys]<br/>
+
+3. Copy the primary or secondary access key.
+
+4. In Solution Explorer, right-click on MvcWebRole and click **Properties**.
+
+ ![Right Click Properties][mtas-rt-prop]<br/>
+
+5. Click the **Settings** tab. In the **Service Configuration** drop down box, select **Local**.
+
+6. Click the ellipsis (**...**) button at the right end of the **StorageConnectionString** entry. The **Storage Account Connection String** dialog opens.
+
+ ![Right Click Properties][mtas-elip]<br/>
+
+7. In the **Storage Account Connection String** dialog, select the **Enter storage account credentials** radio button. Enter the name of your storage account and the primary or secondary access key you copied from the portal. Click the **OK** button.
+
+ ![Right Click Properties][mtas-enter]<br/>
+
+8. Open the **ServiceConfiguration.Local.cscfg** file and examine the XML markup in the role element for MvcWebRole. The storage account connection string editor updated the storage connection string using the account name and key you provided. Rather than use the storage account connection string editor to update the storage connection string for the two worker roles, copy and paste the MvcWebRole storage connection string element over the storage connection string element in the two worker role elements.
+
+9. Press CTRL+F5 to run the application. Enter some data by clicking the Mailing Lists, Messages and Subscribers links as did previously in this tutorial.
+
+10. Open Azure Storage Explorer and verify the data you entered has been saved to Azure Storage.
+
+ ![ASE][mtas-ase1]<br/>
+
+11. In *Server Explorer*, right click **Windows Azure Storage** and click **Add New Storage Account**.
+
+ ![ASE][mtas-se1]<br/>
+
+12. Enter the name of your storage account and a primary or secondary access key.
+
+ ![ASE][mtas-se2]<br/>
+
+ You can now use Visual Studio to view data stored in Windows Azure storage.
+
+ ![ASE][mtas-se3]<br/>
+
+11. **Optional** <ins>Explain why they might or might not want to do this</ins> Disable Azure Storage Emulator automatic startup. In **Solution Explorer**, right click the **AzureEmailService** cloud project and select **properties**.
+
+ ![ASE][mtas-aesp]<br/>
+
+12. **Optional** Set **Start Windows Azure storage emulator** to **False**.
+
+ ![ASE][mtas-1]<br/>
+
+ **Note**: You should only set this to false if you are not using the storage emulator. This dialog also provides a way to change the **Service Configuration** file from **Local** to **Cloud** (from *ServiceConfiguration.Local.cscfg* to *ServiceConfiguration.Cloud.cscfg*.
+
+13. **Optional** In the Windows system tray, right click on the compute emulator icon and click **Shutdown Storage Emulator**.
+
+ ![ASE][mtas-se4]<br/>
+
+<h2><a name="deployAz"></a><span class="short-header">Deploy to Windows Azure</span>Deploy the Application to Windows Azure</h2>
+
+There are several alternatives for publishing applications to Windows Azure. The Windows Azure Tools for Visual Studio allow you to both create and publish the service package to the Windows Azure environment directly from Visual Studio. The Windows Azure Management Portal provides the means to publish and manage your service using only your browser.
+
+In this section of the tutorial, you publish the application to the staging environment using the Management Portal.
+
+1. Open the cloud configuration file (*ServiceConfiguration.Cloud.cscfg*) and set the StorageConnectionString setting using the account name and access key from the management portal. If you set the the StorageConnectionString setting to the same account name used in the *ServiceConfiguration.Local.cscfg* file, your deployed application and local application will use the same azure storage. It's often helpful to use a different account for the storage connection string setting when deploying to Windows Azure.
+
+2. Verify that the web role and two worker role elements all define the same StorageConnectionString.
+
+2. If it is not already open, launch Visual Studio as administrator and open the AzureEmailService solution.
+
+3. Generate the package to publish to the cloud. To do this, right-click the **AzureEmailService** cloud project and select **Package**.
+
+ ![Package][mtas-2]<br/>
+
+4. In the **Package Windows Azure Application** dialog, verify the **Service Configuration** is set to **Cloud**, then click **Package**.
+
+ ![Cloud Package][mtas-pack]<br/>
+
+ Visual Studio builds the project and generates the service package. File Explorer opens with the current folder set to the location where the generated package was created.
+
+ ![File][mtas-fe]<br/>
+
+ **Note**: Using the package approach requires that you create a new package each time you make changes to the application (any of the web or worker roles).
+
+ **Note**: Although the procedure is not shown here, you can use the Publish Cloud Service feature in the Windows Azure Tools to publish your service package directly from Visual Studio.
+
+4. Switch back to the Management Portal browser window and click the cloud service you created.
+
+ ![Cloud Service][mtas-c1]<br/>
+
+5. Click **Staging**, then click **Upload a New Staging Deployment**.
+
+ ![Staging][mtas-c2]<br/>
+
+6. Enter a deployment name. Click the **Package** and **Configuration** input boxes and browse to the *app.publish* folder that Visual Studio created and opened in a previous step. Select the package and configuration files. Check the check box **Deploy even if one or more roles contains a single instance**. Finally, click the check icon to upload the package. This step can take several minutes to complete.<ins>We should explain the single instance issue.</ins>
+
+ ![Staging2][mtas-c3]<br/>
+
+7. In the portal, click **Dashboard**.
+
+ ![Dashboard][mtas-c4]<br/>
+
+8. Click the **Site URL** to launch the application.
+
+ ![Dashboard][mtas-c5]<br/>
+
+9. Enter some data to test the application.
+
+**Note**: To deploy changes to the application, you must follow the steps in this section to create a new cloud package, then upload the new package and configuration files.
+
+<h2><a name="swap"></a><span class="short-header">Production</span>Promote the Application from Staging to Production</h2>
+
+1. Click the Cloud icon in the left pane, then click your cloud service, finally click **Swap**.
+
+2. Click **Yes** to complete the VIP Swap. This step can take several minutes to complete.
+
+ ![Dashboard][mtas-c6]
+
+3. Click the Cloud icon in the left pane, then click your cloud service, finally click **Production**.
+
+4. Notice the **Site URL** has changed from a GUID prefix to the name of your cloud service. Click or copy the **Site URL** to test the application in production.
+
+ ![Dashboard][mtas-c7]
+
+ If you haven't changed the storage account settings, the data you entered while testing the staged version of the application is retained.
+
+<h2><a name="sendGrid"></a><span class="short-header">SendGrid</span>Get a SendGrid account</h2>
+
+1. Follow the instructions in [How to Send Email Using SendGrid with Windows Azure](http://www.windowsazure.com/en-us/develop/net/how-to-guides/sendgrid-email-service/ "SendGrid") to sign up for a free account.
+
+<h2><a name="trace"></a><span class="short-header">Trace</span>Configure and View Trace Data</h2>
+1. Edit the *ServiceConfiguration.*.cscfg* files. Find the Trace (diagnostics) settings element in the ConfigurationSettings element. The followng code shows the diagnostics setting element:
+
+ <ConfigurationSettings>
+ <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"
+ value="UseDevelopmentStorage=true" />
+ </ConfigurationSettings>
+
+2. Change the value attribute from "UseDevelopmentStorage=true" to the following:
+
+ value="DefaultEndpointsProtocol=https;AccountName=[StorageAccount];AccountKey=[Account Key]"
+
+ where "[StorageAccount]" and "[Account Key]" are your storage account and keys obtained from from the storage tab of the Windows Azure portal.
+
+ **Note:** The storage account doesn't need to be the same account the application uses for reading and writing application data. It's often more convienent to use a test or trace account to store trace data.
+
+[Set Up the development environment]: #setupdevenv
+[Set up a free Windows Azure Account]: #setupwindowsazure
+[Create a Windows Azure Storage Account]: #createWASA
+[Optional: Install Azure Storage Explorer]: #installASE
+[Create a Cloud Server in the Management Portal]: #createcloudsvc
+[Download and Configure the Completed Solution]: #downloadcnfg
+[Run the Application from Visual Studio]: #runVS
+[Viewing Developer Storage in Visual Studio]: #StorageExpVS
+[Configure the Application for Azure Storage]: #conf4azureStorage
+[Deploy the Application to Azure]: #deployAz
+[Promote the Application from Staging to Production]: #swap
+[Get a SendGrid account]: #sendGrid
+[Configure and View Trace Data]: #trace
+
+
+[firsttutorial]: http://
+
+
+[mtas-wpi-installer]: ../Media/mtas-wpi-installer.png
+[mtas-portal-new-storage]: ../Media/mtas-portal-new-storage.png
+[mtas-storage-quick]: ../Media/mtas-storage-quick.png
+[mtas-create-storage-url-test]: ../Media/mtas-create-storage-url-test.png
+[mtas-manage-keys]: ../Media/mtas-manage-keys.png
+[mtas-guid-keys]: ../Media/mtas-guid-keys.PNG
+[mtas-new-cloud]: ../Media/mtas-new-cloud.png
+[mtas-create-cloud]: ../Media/mtas-create-cloud.png
+[mtas-ase-add]: ../Media/mtas-ase-add.png
+[mtas-ase-add2]: ../Media/mtas-ase-add2.png
+[mtas-ase-add3]: ../Media/mtas-ase-add3.png
+[mtas-rt-prop]: ../Media/mtas-rt-prop.png
+[mtas-mailinglist1]: ../Media/mtas-mailinglist1.png
+[mtas-create1]: ../Media/mtas-create1.png
[mtas-mailing-list-index-page]: ../Media/mtas-mailing-list-index-page.png
[mtas-subscribers-index-page]: ../Media/mtas-subscribers-index-page.png
-[mtas-message-index-page]: ../Media/mtas-message-index-page.png
[mtas-message-create-page]: ../Media/mtas-message-create-page.png
-[mtas-subscribe-confirmation-page]: ../Media/mtas-subscribe-confirmation-page.png
-[mtas-unsubscribe-query-page]: ../Media/mtas-unsubscribe-query-page.png
-[mtas-unsubscribe-confirmation-page]: ../Media/mtas-unsubscribe-confirmation-page.png
-[mtas-worker-roles-a-and-b]: ../Media/mtas-worker-roles-a-and-b.png
-[mtas-message-processing]: ../Media/mtas-message-processing.png
-[mtas-unsubscribe-confirmed-page]: ../Media/unsubscribe-confirmed-page.png
-[mtas-unsubscribe-confirmed-page]: ../Media/unsubscribe-confirmed-page.png
+[mtas-message-index-page]: ../Media/mtas-message-index-page.png
+[mtas-serverExplorer]: ../Media/mtas-serverExplorer.png
+[mtas-wasVSdata]: ../Media/mtas-wasVSdata.png
+[mtas-elip]: ../Media/mtas-elip.png
+[mtas-enter]: ../Media/mtas-enter.png
+[mtas-ase1]: ../Media/mtas-ase1.png
+[mtas-se1]: ../Media/mtas-se1.png
+[mtas-se2]: ../Media/mtas-se2.png
+[mtas-se3]: ../Media/mtas-se3.png
+[mtas-aesp]: ../Media/mtas-aesp.png
+[mtas-1]: ../Media/mtas-1.png
+[mtas-se4]: ../Media/mtas-se4.png
+[mtas-2]: ../Media/mtas-2.png
+[mtas-pack]: ../Media/mtas-pack.png
+
+[mtas-fe]: ../Media/mtas-fe.png
+[mtas-c1]: ../Media/mtas-c1.png
+[mtas-c2]: ../Media/mtas-c2.png
+[mtas-c3]: ../Media/mtas-c3.png
+[mtas-c4]: ../Media/mtas-c4.png
+[mtas-c5]: ../Media/mtas-c5.png
+[mtas-c6]: ../Media/mtas-c6.png
+[mtas-c7]: ../Media/mtas-c7.png
+[mtas-er1]: ../Media/mtas-er1.png
+[mtas-]: ../Media/mtas-.png
+[mtas-]: ../Media/mtas-.png
+[mtas-]: ../Media/mtas-.png
+[mtas-]: ../Media/mtas-.png
+
+
+
+[blob6]: ../Media/blob6.png
+[blob7]: ../Media/blob7.png
+[blob8]: ../Media/blob8.png
+[blob9]: ../Media/blob9.png
+
+[mtas-storage-quick-SM]: ../Media/mtas-storage-quick-SM.png
+[0]: ../../Shared/media/antares-iaas-preview-01.png
+[1]: ../../Shared/media/antares-iaas-preview-05.png
+[2]: ../../Shared/media/antares-iaas-preview-06.png
+[Image001]: ../Media/Dev-net-getting-started-001.png
View
1,557 DevCenter/dotNET/Tutorials/multi-tier-with-storage-3-web-role.md
@@ -1,526 +1,1147 @@
<div chunk="../chunks/article-left-menu.md" />
-# .NET Multi-Tier Application Using Storage Tables, Queues, and Blobs
+# Building the web role for the Azure Email Service application - 3 of 5.
-This tutorial series shows how to create a multi-tier ASP.NET web application that uses Windows Azure Storage tables, queues, and blobs. The tutorial assumes that you have no prior experience using Windows Azure. On completing the tutorial, you'll have a robust and scalable data-driven web application up and running in the cloud.
+This is the third tutorial in a series of five that show how to build and deploy the Azure Email Service sample application. For information about the application and the tutorial series, see the [first tutorial in the series][firsttutorial].
-<h2><a name="whyanemaillistapp"></a><span class="short-header">Why Choose This App</span>Why an Email List Service Application</h2>
+In this tutorial you'll learn:
-We chose an email list service for this sample application because it is the kind of application that needs to be robust and scalable, two features that make it especially appropriate for Windows Azure.
+* How to create a solution that contains a Cloud Service project, an ASP.NET MVC 4 web role, and a worker role; or how to create a solution that contains an ASP.NET MVC 4 web project and a Cloud Service project with a worker role.
+* How to configure the web role or web project to use your Windows Azure Storage account.
+* How to create MVC 4 controllers and views that work with Windows Azure tables.
+* How to create a Web API controller that works with Windows Azure tables and queues.
+
+### Tutorial segments
-### Robust
+1. [Create the Visual Studio solution][createsolution]
+2. [Create and test the Mailing List controller and views][mailinglist]
+3. [Create and test the Message controller and views][message]
+4. [Create and test the Subscriber controller and views][subscriber]
+5. [Create and test the Web API controller and service method][webapi]
+4. [Create and test the Subscribe controller and view][subscribe]
+4. [Create and test the Unsubscribe controller and view][unsubscribe]
+8. [Next steps][nextsteps]
-If a server fails while sending out emails to a large list, you want to be able to stand up a new server easily and quickly, and you want the application to pick up where it left off without losing or duplicating any emails. A Windows Azure Cloud Service web or worker role (virtual machine) is automatically replaced if it fails. And Windows Azure Storage queues and tables provide a means to implement server-to-server communication that can survive a failure without losing work.
+<h2><a name="cloudproject"></a><span class="short-header">Create cloud project</span>Create the Visual Studio solution</h2>
-### Scalable
+Begin by deciding whether you want to build the application architecture used in the downloaded sample: a Windows Azure Cloud Service with a web role and two worker roles. The alternative is to run the web UI and Web API service method in a Windows Azure Web Site, and keep only the worker roles in a Cloud Service. Then follow the steps in whichever of the two following "Create the project" sections that applies to your chosen architecture.
-An email service also must be able to handle spikes in traffic, since sometimes you are sending emails to small lists and sometimes to very large lists. In many hosting environments, you have to purchase and maintain sufficient hardware to handle the spikes in workload, and you're paying for all that capacity 100% of the time although you might only use it 5% of the time. With Windows Azure, you pay only for the amount of computing power that you actually need for only as long as you need it. To scale up for a large mailing, you just change a configuration setting to increase the number of servers you have available to process the workload, and this can be done programmatically.
+### Create a Cloud Service project with a web role and a worker role
-<h2><a name="whatyoulllearn"></a><span class="short-header">What You'll Learn</span>What You'll Learn</h2>
+Skip this section if you want to run the web UI and Web API service method in a Windows Azure Web Site.
-In this tutorial series you'll learn the following:
+1. Start Visual Studio 2012 or Visual Studio 2012 for Web Express, with administrative privileges.
-* How to enable your machine for Windows Azure development by installing the Windows Azure SDK.
-* How to create a Visual Studio cloud project with an MVC 4 web role and two worker roles.
-* How to publish the cloud project to a Windows Azure Cloud Service.
-* How to publish the MVC 4 project to a Windows Azure Web Site if you prefer, and still use the worker roles in the Cloud Service.
-* How to use Windows Azure storage queues for communication between tiers or between worker roles.
-* How to use Windows Azure storage tables as a highly scalable data store for non-relational data.
-* How to use Windows Azure storage blobs to store files in the cloud.
-* How to use Azure Storage Explorer to work with tables, queues, and blobs.
+ The Windows Azure compute emulator which enables you to test your cloud project locally requires administrative privileges.
-<h2><a name="wawsvswacs"></a><span class="short-header">Application architecture</span>Overview of application architecture</h2>
+2. From the **File** menu select **New Project**.
-This is the first tutorial in a series, and it provides an overview of the application and its architecture.
+ ![New Project menu][mtas-file-new-project]
-The front-end is a set of web pages and a service method that enable administrators to manage email lists, and subscribers to subscribe and unsubscribe. The front-end uses ASP.NET MVC 4 and Web API, and it runs in a web role in a Windows Azure Cloud Service. The back-end is a pair of worker roles running in the same Cloud Service and do the work of sending emails.
+3. In the **New Project** dialog box, make sure that the **.NET Framework** drop-down list is set to **.NET Framework 4**.
-The application stores email lists and subscriber information in Windows Azure storage tables. It stores email content in blobs (a plain text file and an HTML file for each email). It uses Windows Azure storage queues for communication between the front-end service method and the one of the back-end worker roles, and between the two worker roles.
+ As this tutorial is being written, Windows Azure Web Sites and Windows Azure Cloud Service web roles do not support ASP.NET 4.5.
-The following diagram provides a high-level picture of the application architecture that is used in this tutorial.
+1. Expand **C#** and select **Cloud** under **Installed Templates** and then select **Windows Azure Cloud Service**.
-![Application architecture overview][mtas-architecture-overview]
+2. Name the application **AzureEmailService** and click **OK**.<br/>
-An alternative architecture that would also work is to run the front-end in a Windows Azure Web Site.
+ ![New Project dialog box][mtas-new-cloud-project]
-![Alternative application architecture][mtas-alternative-architecture]
+5. In the **New Windows Azure Cloud Service** dialog box, select **ASP.NET MVC 4 Web Role** and click the arrow that points to the right.
-This alternative architecture might offer some cost benefits, because a Windows Azure Web Site may be less expensive for similar capacity compared to a web role running in a Cloud Service. For this tutorial we have the entire application in a Cloud Service because that simplifies configuration and deployment. The tutorial explains the differences between the two architectures, so that when you implement your own application you can choose the architecture that you prefer.
+ ![New Windows Azure Cloud Project dialog box][mtas-new-cloud-service-dialog]
-<h2><a name="frontendoverview"></a><span class="short-header">Front-end overview</span>Front-end overview</h2>
+6. In the column on the right, hover the pointer over **MvcWebRole1**, and then click the pencil icon to change the name of the web role.
-The front-end includes web pages that administrators of the service use to manage email lists and to create and schedule messages to be sent to the lists.
+7. Enter MvcWebRole as the new name, then click **OK**.
-![Mailing List Index Page][mtas-mailing-list-index-page]
+ ![New Windows Azure Cloud Project dialog box - renaming the web role][mtas-new-cloud-service-dialog-rename]
-![Subscriber Index Page][mtas-subscribers-index-page]
+8. Follow the same procedure to add a **Worker Role**, name it WorkerRoleA, and then click **OK**.
-![Message Index Page][mtas-message-index-page]
+ ![New Windows Azure Cloud Project dialog box - adding a worker role][mtas-new-cloud-service-add-worker-a]
-![Message Create Page][mtas-message-create-page]
+5. In the **New ASP.NET MVC 4 Project** dialog box, select the **Internet Application** template.
-Clients of the service are companies that give their customers an opportunity to sign up for a list on the client web site. For example, Contoso University wants a list for History Department announcements. When a student interested in History Department announcements clicks a link on the Contoso University web site, Contoso University makes a web service call to this application. The service method causes an email to be sent to the customer. That email contains a link, and when the recipient clicks the link, a page welcoming the customer to the History Department Announcements list is displayed.
+6. In the **View Engine** drop-down list make sure that **Razor** is selected, and then click **OK**.
-![Welcome to list page][mtas-subscribe-confirmation-page]
+ ![New Project dialog box][mtas-new-mvc4-project]
-Every email sent by the service includes a hyperlink that can be used to unsubscribe. If a recipient clicks the link, a web page asks for confirmation of intent to unsubscribe. If the recipient clicks the Confirm button, a page is displayed confirming that the person has been removed from the list.
+### Create a web application project and add a Cloud Service project with a web role to the solution
-![Confirm unsubscribe page][mtas-unsubscribe-query-page]
+Skip this section if you are running the web UI and Web API service method in a Cloud Service web role.
-![!Unsubscribe confirmed page][mtas-unsubscribe-confirmation-page]
+1. Start Visual Studio 2012 or Visual Studio 2012 for Web Express, with administrative privileges.
+
+2. From the **File** menu select **New Project**.
-<h2><a name="backendoverview"></a><span class="short-header">Back-end overview</span>Back-end overview</h2>
+1. Select **Web** under **Installed Templates**, and then select the **ASP.NET MVC 4 Web Application** template.
-Email lists and email messages scheduled to be sent are stored in Windows Azure Storage tables. When an administrator schedules an email to be sent, a row containing the scheduled date and other data is placed on the Message table. A worker role (virtual machine) running in a Windows Azure Cloud Service periodically scans the Message table looking for messages that need to be sent (we'll call this Worker Role A). When Worker Role A finds a message needing to be sent, it looks up all the email addresses in the destination email list, puts the information needed to send the email in the Message table, and creates a work item on a queue for each email that needs to be sent. A second worker role (Worker Role B) polls the queue for work items. When Worker Role B finds a work item, it processes the item by sending the email and then deletes the work item from the queue. The following diagram shows these relationships.
+2. Name the application **AzureEmailService** and click **OK**.
-![Worker roles A and B][mtas-worker-roles-a-and-b]
+5. In the **New ASP.NET MVC 4 Project** dialog box, select the **Internet Application** template.
-When Worker Role A creates a queue work item, it also adds a row to the Message table. Worker Role A reads this row to get the information it needs to send the email.
+6. In the **View Engine** drop-down list make sure that **Razor** is selected, and then click **OK**.
-The row in the Message table that provides information for one email also includes a property that indicates whether the email has actually been sent. When Worker Role B sends an email, it updates this property to indicate that the email has been sent. If Worker Role A goes down while creating queue work items for a message, it might create duplicate queue work items when it restarts, but the tracking row ensures that duplicate emails won't be sent. (Worker Role B checks the row before sending an email.)
+7. In **Solution Explorer**, right-click the new solution and select **Add Project**.
-![Queue message creation and processing][mtas-message-processing]
+3. In the **New Project** dialog box, make sure that the **.NET Framework** drop-down list is set to **.NET Framework 4**.
-<h2><a name="tables"></a><span class="short-header">Tables</span>Windows Azure Storage Tables</h2>
+1. Expand **C#** and select **Cloud** under **Installed Templates** and then select **Windows Azure Cloud Service**.
-Windows Azure storage tables are a NoSQL data store, not a relational database. That makes them a good choice when scalability is more important than data normalization and relational integrity. For example, in this application, worker roles create a row every time a queue workitem is created and the row is updated every time an email is sent, which might be a performance bottleneck if a relational database were used.
+2. Name the project **WindowsAzureCloudService** and click **OK**.
-In a Windows Azure storage table, every row has a *partition key* and a *row key* that uniquely identifies the row. The partition key divides the table up both logically and physically into partitions. Within a partition, the row key uniquely identifies a row.
+5. In the **New Windows Azure Cloud Service** dialog box, select **Worker Role** and click the arrow that points to the right.
-### MailingList table ###
+6. In the column on the right, hover the pointer over **WorkerRole1**, and then click the pencil icon to change the name of the worker role.
-The MailingList table stores information about mailing lists and information about the subscribers to mailing lists. Administrators use web pages to create and edit mailing lists, and clients and subscribers use a set of web pages and service method to subscribe and unsubscribe.
+7. Enter WorkerRoleA as the new name, then click **OK**.
-In NoSQL tables, different rows can have different schemas, and this flexibility is commonly used to make one table store data that would require multiple tables in a relational database. For example, to store mailing list data in SQL Database you could use three tables: a MailingList table that stores information about the list, a Subscriber table that stores information about subscribers, and a MailingListSubscriber table that associates mailing lists with subscribers and vice versa. In the NoSQL table in this application, all of those functions are rolled into one table named MailingList.
+### Set the page header, menu, and footer
-The row key for the MailingList table can be one of two things: the constant "0" or the email address of the subscriber. Rows that have row key "0" include information about the mailing list. Rows that have the email address as the row key have information about the subscribers to the list.
+In this section you update the headers, footers, and menu items that are shown on every page for the administrator web UI. The application will have three sets of administrator web pages: one for Mailing Lists, one for Messages, and one for Subscribers.