Windows 8 and ClickOnce : the definitive answer revisited

April 14, 2013

Since I posted the article titled Windows 8 and Click Once: the definitive answer, it became clear that it was not actually the definitive answer.

I got a ping on twitter from Phil Haack from GitHub telling me that this did not fix their Smart Screen filter problem.

After talking to him, and seeing his build and signing commands, I discovered they recently changed their signing certificate. For those of you who remember the early days of ClickOnce (2005) when you changed the signing certificate and everybody had to uninstall and reinstall the application, this seemed too likely an indicator to ignore.

Reputation

I didn’t talk in my article about “reputation” (and I should have, so I duly apologize here for that). In my first conversations with Microsoft, they mentioned that an application had a reputation, and this reputation had some bearing on the appearance of the Smart Screen Filter, and this reputation was built based on how many people had installed your application.

When I asked how many people had to install your application before the Smart Screen filter stopped interrupting the running of the application, I could not get a clear answer. Of course, this makes sense that they wouldn’t want to make their algorithm public, because you could publish your app, install it X number of times yourself, and make it reputable. (I’m not suggesting you do that, but if you do, please post back and tell us your results. Inquiring minds want to know.)

Since we’ve been in business for a few years, and have well over a thousand customers (probably thousands) who have installed the desktop application, this didn’t impact us. The reason I didn’t mention it in the blog post is because I created a new Windows Forms test application and deployed it solely for the purpose of testing the information in the article, and had no problem with the Smart Screen Filter. I installed the application maybe a dozen times while messing with the build commands, so I figured, “Wow, the number of installs required is pretty small.” Haha!

So on behalf of Phil, I pinged my contact at Microsoft, and he went off to investigate. After a bit of research, he found some information internal to Microsoft. I won’t quote it directly in case I shouldn’t, but the gist of it was this: The digital certificate information may be taken into account when determining the reputation of the application. A-HA! I thought to myself (and immediately started humming that song, “Take On Me”.)

So the problem at GitHub is probably due to the certificate being updated right about the same time they start signing their assembly for customers using Windows 8. I expect that fairly soon, as people install or get updates (if they are using automatic updates), their reputation will be sterling and nobody will ever see the Smart Screen Filter again when installing GitHub.

Knowing this, it makes sense that my test application didn’t get stopped even though it was a brand new application. I signed it with my company’s signing certificate, which has been in use for several months.

Which leads me to another issue I noticed when talking to Phil. I noticed that rather than using PostBuild or BeforePublish commands, he was using AfterCompile commands to sign his executable. I asked him about it.

PostBuild, BeforePublish, and AfterCompile, oh my!

Apparently when Phil signs his executable using PostBuild or BeforePublish commands, when the user installs it, he gets the dreaded “exe has a different computed hash than specified in the manifest” error. He found that using AfterCompile instead fixed the problem.

I went back to Microsoft, and they soon verified the problem, and said it is due to WPF applications having a different set of targets and execution order, so the standard AfterBuild/BeforePublish commands don’t quite work. So the bottom line is this: The signing of the exe doesn’t work right with BeforePublish or PostBuild if you are using VS2012 and you have a WPF application. In that case, you must use AfterCompile. So in the original post, use case #3, but put in AfterCompile instead of BeforePublish.

If you are using VS2010, OR you have a Windows Forms or Console application, you can use PostBuild or BeforePublish with no problem.

Hopefully we now have the definitive answer to handling the Smart Screen filter and signing a ClickOnce application that will be run on a Windows 8 machine.

Thanks to Zarko Hristovski and Paul Keister, who also reported the problem with the BeforePublish command, and who verified that AfterCompile worked for them. Thanks to Phil Haack for the answer to a problem I didn’t know existed yet. And thanks to Saurabh Bhatia at Microsoft for his help with Windows 8 and ClickOnce.

Tech Days San Francisco, 2-3 May 2013, through Azure-colored glasses

April 9, 2013

Living in the San Francisco Bay Area is awesome if you work in tech. There are so many companies springing up all the time and so many interesting places to work. The hard part of working in tech is keeping up with the current technologies and learning the new skills that can help you advance your career. A great way to do that is to keep your eyes open for conferences, dev days, tech days, etc., in your area, sign up and go. There are so many great opportunities being offered by the community leaders in your area.

A really interesting opportunity is coming up in the San Francisco Bay Area in early May – Tech Days SF. While primarily for IT Pros, there are also sessions that will be interesting to developers. What developer couldn’t benefit from knowing more on the IT Pro side? I was recently talking to another Azure MVP, and we agreed that now with all of the features in Windows Azure, it would behoove us to learn about virtual networks and some of the other IT-type features we never had to know when just doing software development.

There are some great speakers coming, which I doubly appreciate, because I managed to poach Glenn Block from Microsoft to speak at the Azure Meetup in San Francisco the night before (5/1) about mobile services (official announcement coming soon). And there is going to be a wide variety of topics; here is a random selection that just coincidentally seem Azure-related or Azure-useful:

  • Windows Azure
  • Managing the Cloud from the CmdLine
  • Microsoft IT – Adopted O365 and Azure
  • Windows Azure Virtual Machines (IAAS)
  • PowerShell Tips and Tricks (You can use PowerShell scripts with Windows Azure)
  • Manage Server 2012 Like a Pro or Better, Like an Evil Overlord (I like the title)

This is just one of many opportunities available to keep your skills up-to-date. So check it out, sign up, and go expand your knowledge!

(Reminder – There’s also a Global Windows Azure Bootcamp in San Francisco on 4/27!)

Global Windows Azure Bootcamp SF April 27th 2013

April 6, 2013

What is it?

On April 27th, the Windows Azure community is going to have a Global Windows Azure Bootcamp. This will be a one-day hands-on deep dive class for developers in locations all over the world. Last I heard, the count was up to around 80 locations.

The local Windows Azure experts will be in attendance to run the bootcamp in each location, provide training, answer questions, and provide support with doing the labs. I heard a rumor that there is also going to be a huge rendering project that each site can run that will test the power and capability of Windows Azure. It should be a lot of fun, so please sign up and attend the one closest to you.

What about the San Francisco Bay Area?

I will be organizing and running the event in San Francisco; registration is here. The event location is the Microsoft office in San Francisco (835 Market Street, Suite 700). This is adjacent to and above the Westfield Shopping Mall, so after the event, you can go to the Microsoft Specialty Store and get a new Surface Pro tablet, because you’ll love mine so much you’ll want your own.

Each bootcamp’s agenda and material are up to the organizer, so you will be at my mercy. I mean, I will be deciding what we’re going to do. Since it’s three weeks off, and everybody knows that developers usually don’t write talks until the night before the event, I haven’t decided on the agenda yet. (Don’t worry, this time I’m not going to wait until the night before.)

I’ve attended these in the past, and always think the leaders talk too much and the developers develop too little, so I am going to try to focus on the development rather than the talking. I’ll do introductory talks with overview information, and then provide a corresponding lab that we can do to understand the topic. Since I live in the San Francisco Bay Area, and there are a lot of non-Microsoft developers, my current thinking is that I will focus on Windows Azure Web Sites, Infrastructure as a Service (IAAS), and Mobile services, which can be used by everybody.

What do I need to bring?

You need to install the prerequisites BEFORE the class. It can take a couple of hours to get set up, so if you don’t do it ahead of time, you won’t get nearly as much out of the class and will probably be concentrating so hard, you will miss some of my crackling jokes and dry witty comments. Also note that your Commodore 64 will probably not work with Windows Azure’s SDK.

Here’s what you need to have on your system to make the most out of the day:

This should be a lot of fun, and will be a great introduction to some of the cool things you can do with Windows Azure. (For more information about how much fun I’ve had with Windows Azure, check out this blog post.) Sign up for the event closest to you and have a great time!

Windows 8 and ClickOnce : the definitive answer

February 24, 2013

There have been a lot of copies of Windows 8 sold since it came out a few months ago, and the Surface Pro was just released. (In fact, I’m writing this on my brand new Surface Pro, which I really like, but that’s a subject for another time.)

If you’re using ClickOnce deployment, you’re probably wondering how (or if) it’s going to work with Windows 8. I’ve worked with Saurabh Bhatia at Microsoft to ensure that this article will cover what you need to know. We use ClickOnce at GoldMail (whose product is now called Point Across) for our desktop product and VSTO applications, as well as several internal utility applications, so I’ve also tested this on our products to make sure it’s accurate.

If you are hosting your deployment on a file share or on an intranet, you won’t have to make any changes. You can go get ice cream now while the rest of us soldier on.

If you are hosting your deployment on the internet, you will eventually get calls from your customers who have upgraded to Windows 8 or purchased a Windows 8 machine. So let’s talk about that.

I’m not going to talk about the bootstrapper right now; that’s going to come up later. For now, let’s concentrate on the ClickOnce application itself. When a user installs a ClickOnce application on Windows 8, here’s what happens:

  • ClickOnce gets the manifest, checks the certificate, and shows the ClickOnce prompt with “trusted publisher” or “unknown publisher” (depending on your signing certificate).
  • The user clicks the Install button.
  • It checks the certificate on the executable. If it’s not signed, the Smart Screen Filter is triggered.

So here’s what the user experience looks like when you install a ClickOnce application on Windows 8:

You get the standard install prompt:

The publisher is known because I am signing the deployment with a signing certificate purchased from a Certificate Authority – in this case, Verisign.

If you click Install, it shows the standard install dialog and actually installs the application. But then it shows a blue band across your screen saying, “Windows SmartScreen prevented an unrecognized app from starting. Running this app might put your PC at risk.”

There is a small “More Info” link under the warning, and a big “OK” button on the bottom of the dialog. Which one would you click? Which one would your customers click? Most people will click the OK button.

If the user clicks OK, the dialog closes, and nothing else happens. Now let’s say the user goes to TileWorld (I’m borrowing David Pogue’s name for the new Windows 8 interface formerly known as Metro). The user can see the application there in the list of apps because it actually got installed. If he clicks on it to run it, nothing happens. So congratulations! The user has installed your application, but he can’t run it.

What happens if the user clicks “More Info” instead of “OK”? He sees the following screen, and he can choose “Run Anyway” or “Don’t run”.

For “Publisher”, it says “Unknown publisher” – this is referring to the executable, which is not specifically signed. Only the manifests are signed. This has never been a requirement for ClickOnce deployments. Until now.

If the user chooses “Run Anyway”, it will run the application. Yay! And when he goes back to TileWorld and tries to run it from there the next time, it will work and will not prompt him again. Yay!

So let’s say he clicks “Run Anyway”, and now he has no problem running your application. What happens when an update is published and he installs it? Uh-oh. The smart screen filter interrupts again, and he has to select “More Info” and “Run Anyway” again.

Is there a way to circumvent your ClickOnce application being captured and stopped by the Smart Screen Filter? Yes. Otherwise, this would be a much shorter (and depressing) article. All you have to do is sign the application executable after building it and before deploying it. For this, you need your signing certificate and signtool.exe, which is one of the .NET Framework tools. There are three points in the build/publish process at which you can do this:

1. Post-publish

2. Post-build

3. Pre-publish

#1: Signing the application executable post-publish

To do it post-publish, you have to do the following:

  • a. Publish the files to a local directory.
  • b. Use signtool to sign the exe for the application.
  • c. Use mage or mageUI to re-sign the application manifest (.exe.manifest).
  • d. Use mage or mageUI to re-sign the deployment manifest (.application).
  • e. Copy the files to the deployment location.

If you’ve already automated your deployment with a script and msbuild, this may be the choice you make. If you publish directly from Visual Studio, the other two options are easier.

#2: Signing the application executable post-build

To do this, you define a post-build command in your project. Assuming your certificate (pfx file) is in the top level of your project, you can use something like this:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\signtool.exe" sign /f "$(ProjectDir)TestWin8CO_TemporaryKey.pfx" /p nightbird /v "$(ProjectDir)obj\x86\$(ConfigurationName)\$(TargetFileName)"

  • The double quotes are required.
  • “C:Program Files (x86)Microsoft SDKsWindows\v7.0A\bin\signtool.exe” is the path to the signtool application, used to sign the executable.
  • $(ProjectDir) points to the top directory of the project. The subfolder “\obj\x86” will vary depending on your build output path. The above was created and tested on VS2010. On VS2012, my subfolder is just \obj.
  • $(ConfigurationName) is the build configuration name, such as Debug or Release – this is required because it signs it in the obj directory and has to know which folder to use.
  • $(TargetFileName) is the name of the application executable.
  • TestWin8CO_TemporaryKey.pfx is the name of my certificate file, which is in the top folder of my project.
  • /p nightbird – this is the password for my temporary certificate

I have specified the full path to signtool.exe. I tried to do this with one of the msbuild variables that points to the location of the .NET framework files, but it doesn’t work – it doesn’t translate the variable until after it executes the statement. If you print it out in the post-build command, it shows the right location in the Visual Studio output window, but gives you an error that it can’t find it when it actually runs this statement. I’m saving you some time here, because I messed around with that for quite a while trying to get it to work, and after asking Saurabh at Microsoft, he couldn’t get it to work without specifying the whole path, either. So if you get it to work with a msbuild variable, let me know how.

After you’ve created your version of the post-build command, you need to put it in the project properties. Double-click on Properties and click on the Build Events tab. Put your command in the Post-build event command line box.

Now build the project, and the output window will show the results.

If you now publish the application and put the files in the deployment directory, the user can install it and will not see the Smart Screen Filter. Yay!

What if you have multiple programmers working on the application, and they all build and run the application? Every programmer must have signtool.exe in the exact same location for this post-build command to work for everybody. If you have a 32-bit machine, the folder for the “Microsoft SDKs” is under “C:Program Files”, without the “(x86)” on the end. And someone might actually install Windows to a drive other than C. If their signtool.exe file is not in the same location, they can’t build and run the application, which means they can’t put in changes and test them.

Only the person publishing the application really needs this build command to work. So how do you execute this only for the person publishing the application? You can set up a pre-publish command.

#3: Signing the application executable pre-publish (recommended solution)

The pre-publish command is executed after building the application and right before publishing it. There is no box for this under Build Events, so you have to add it to the project yourself. (Be sure to clear out the post-build event command line before doing this.)

To add a pre-publish command, right-click on the project in Visual Studio and select “Unload Project”.

Now right-click on the project again and select “Edit yourprojectname.csproj”.

It will open the csproj file in Visual Studio so you can edit it. Go down to the bottom and add a new section before the </Project> line. You’re going to put your pre-publish command line in this section.

<Target Name=”BeforePublish”>

</Target>

So what do you put in this section? You are going to specify a command to execute, so you have to use Exec Command, and put the command to execute in double quotes. Since you can’t put double-quotes inside of double-quotes (at least, not if you want it to work), you need to change the double-quotes in your command to &quot; instead. So my build command from above now looks like this:

<Exec Command="&quot;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\signtool.exe&quot; sign /f &quot;$(ProjectDir)TestWin8CO_TemporaryKey.pfx&quot; /p nightbird /v &quot;$(ProjectDir)obj\x86\$(ConfigurationName)\$(TargetFileName)&quot;" />

After making this match your parameters, save the csproj file and then close it. Then right-click on the project and reload it:

Now if you build your project, you won’t see anything about signing the application executable in the output window. It will only do it if you publish, and there won’t be logging letting you know it signed it. How do you know if it worked? Go to the folder you published to, and look in the Application Files folder. Locate the application executable in the folder for the new version. Right-click on it, choose properties. Look for a tab called “Digital Signatures”. If it’s not found, it’s not signed. If you do see it, go to that tab; it will show the signature list and the signer of the certificate. You can double-click on the signer and then view the signing certificate.

How will the application work after publishing it with a signed executable?

If you sign your executable and your deployment with a valid certificate from a Certificate Authority like Verisign using one of the methods above, when the user clicks install, it will install without stopping and showing the SmartScreen filter, and updates will do the same. Yay!

Do I have to use a certificate from a Certificate Authority to circumvent the Smart Screen Filter?

Yes.

Is there any workaround?

No.

If you try the old tried and true “install the certificate in the trusted publishers store on the client computer”, you will find that this does not circumvent the Smart Screen Filter. You must have a certificate from a valid Certificate Authority. Without one, your customer will get the Smart Screen filter when he installs the application, and every time he installs an update.

What about the bootstrapper (setup.exe)?

The bootstrapper (setup.exe) is signed the same way as the ClickOnce deployment; this happens when you publish. When run, this installs the prerequisites and then calls the ClickOnce application installation. If your certificate is not from a valid CA, the Smart Screen Filter will catch it. This isn’t as critical a problem as the ClickOnce deployment itself because in most cases, your users will only run this the first time.

What about VSTO applications?

If your VSTO application is deployed via a file share or the intranet zone, you will not be impacted. If your VSTO application is deployed via the Internet zone, you may be impacted.

There is no executable for a VSTO application, just an assembly, so you don’t have to do any extra signing. However, the following is true:

If you sign your deployment with a certificate from a CA, everything will work fine, and the Smart Screen filter will not interrupt either the setup.exe or the vsto file from installing the app or keep the app from running.

If you are using a test certificate, setup.exe will be caught by the Smart Screen filter. If you click ‘Run Anyway’, it will install the prerequisites, but it will not let you install the VSTO application.

If you install the test certificate in the Trusted Publishers store, setup.exe will still be caught by the Smart Screen filter, but the VSTO application can be installed and run. This is strongly advised against, as installing the certificate on the user’s machine introduces a significant security risk.

Which method to you recommend?

The advantage of the post-build command is that it is transparent. You can easily go into the Build properties and see there is a post-build command. A pre-publish command is kind of hidden in the project file. However, everybody has to have signtool.exe in the same place, and for us that’s a non-starter. Also, if I did leave the post-build command in there, someone might change it to match their path and check in the change, causing a problem when we actually try to build the application for production.

I used the post-build methods to test my build command until I got it to work, and then ported it to a pre-publish command. 

To summarize, a flowchart:

In summary, here’s a flowchart to help you easily see whether your users will get the Smart Screen filter when they install your application on Windows 8.

One last note: The first version of VS2012 had a bug where the bootstrapper created when publishing a ClickOnce application would not work on a Windows XP machine. This problem was fixed in the first update.

[edit: Fixed build paths, some \’s were missing. Added recommendation. –Robin 2.26.2013]

[edit: After publishing this article, I heard from a couple of people who were still having problems. Please check out the next blog article about this if you are still having problems with the Smart Screen filter, or getting the dreaded “exe has a different computed hash than the manifest” error. –Robin 4.14.2013]

Windows Azure Conference Streamed Live Wednesday 11-14-2012

November 12, 2012

I’m really excited about the upcoming Windows Azure community event this Wednesday. It’s a full day of Windows Azure sessions streamed live on Channel 9. It starts with Scott Guthrie’s  opening keynote. Scott’s a Corporate VP at Microsoft who can actually write code, and gives a very impressive demo. That will be a great start to a very interesting day, with developers showing you some of the things they know about Windows Azure that will help you develop applications on Microsoft’s awesome cloud platform. I think that’s one thing that’s really cool about this conference – it’s not the usual “here’s what windows azure is”, etc. – it’s *how* to apply all those great Windows Azure features in real-world situations.

The schedule for the individual sessions is here. They have sessions on:

  • the new Windows Azure Mobile Services,
  • building Windows Azure applications with HTML5 and Web Sockets,
  • how to handle throttling and transient faults in Azure services,
  • security and compliance challenges with hybrid clouds,
  • how to achieve great performance at low cost,
  • building cross-platform media applications using the Windows Azure Media Services,
  • building elastic, autoscalable solutions, and
  • Node.js.

Some of the Windows Azure MVPs are also speaking. Michael Collier (twitter: @MichaelCollier) will show you how to automate the deployment of your new cloud solutions. Magnus Martensson (twitter: @noopman) showing how to achieve continuous delivery. Andy Cross (twitter: @andybareweb) will talk about Windows Azure and embedded devices.

Now, you might wonder about the cost of all this Azure greatness. Hard as it is to believe, it’s free! Just register here and all will be revealed to you.

Silicon Valley Code Camp, Scott Guthrie, and me

October 5, 2012

What do Silicon Valley Code Camp, Scott Guthrie, and I have in common? We’ll all be in Los Altos Hills in the Silicon Valley area this weekend (10/7-10/8).

Yes, that’s right, Scott Guthrie is speaking at SVCC this Sunday 10/7 at 9:15 a.m. PST. Scott is a Corporate VP of Microsoft’s Server and Tools business, and I can personally say that since he’s been put in charge of Windows Azure, things are hopping even more than they already were. A year ago, who would have thought they would ever let you run Linux on Microsoft’s premier cloud computing platform? And yet, that’s what’s happening, in addition to adding a lot more exciting features and options that make the cloud accessible to everyone, not just those developing on the Microsoft stack. Scott is going to give you an overview and do a bunch of demos. He’s a great speaker, and you shouldn’t miss this if you can help it. I hope to see you there!

If you’re looking to learn something about Azure on Saturday 10/6, I’m speaking at 3:30. I’m going to do a robo talk about Windows Azure development, and show how to create a WCF service that accesses SQL Azure (and Windows Azure Table Storage, if I have time), and uses blobs and queues and web roles and worker roles and diagnostics. You should come just to see if I can squeeze the whole talk into 1:15. I’ll also take general architecutre questions and answer questions about my company’s successful migration to Windows Azure.

At 5:00, I’ll talk about how everyone can use blob storage – it’s not just for use in Windows Azure. You can use blob storage in your web application or desktop application without ever touching anything else in Windows Azure. Come see how! I know it’s the end of the day, but I promise you won’t fall asleep!

Azure for Developers Tutorial Step 7: Use Table Storage instead of a SQL Database

July 8, 2012

This is the seventh and final step of the Azure for Developers tutorial, in which we set up a WCF service running in Azure to provide CRUD operations to a client application. For more information, please check out the Introduction.

We have a WCF service running in a web role that reads from and writes to a SQL Database. It submits messages to an Azure queue, and there is a worker role that retrieves the entries from the queue and writes them to blob storage. We have the diagnostics working, and we have a client that calls the service.

Why do I care?

If you have a lot of data, it’s much less expensive to store it in Windows Azure Tables than in a SQL Database. But table storage is like indexed sequential flat files from days of yore – there are no secondary indeces. You get to define a partition key for your table; Microsoft tries to keep all of the data in a partition together. You don’t want to have one partition with all of your millions of records in it – this is not efficient. But you might split the data by what country your customer is in, or by a range of customer id’s, or something like that. You also can define a Row Key, which, when combined with the partition key, makes up the primary key for the table. So if country was your partition key, the row key might be customerID, for example.

You can store different kinds of data in the same table, but this is not a good design idea, as it will confuse the people filling in for you when you’re on vacation.

Let’s see the code…

Let’s add a class and write some code to replicate the same calls we make to the SQL Database, but use table storage instead. For GetCustomerList, we are returning a dataset, so we’ll create the dataset programmatically so we don’t have to make any changes to our client for it to run against Table Storage instead of a SQL Database.

To access table storage, we will associate table entities with a model class called Customer and use a context to track instances of that class, which represent entities to be insert in the table or retrieved from the table.

Right-click on References in the CustomerServicesWebRole and select “Add Reference”. Go to the .NET tab and look for System.Data.Services.Client and select it and click OK.

Now right-click on the CustomerServicesWebRole and select Add Class. Call the class Customer. This is going to be our data model class definition. First, let’s add the basic properties:

public string FirstName { get; set; }
public string LastName { get; set; }
public string FavoriteMovie { get; set; }
public string FavoriteLanguage { get; set; }

Now let’s add the properties required for table storage. You need to have properties for the PartitionKey and RowKey. These two combined make up the primary key. I’m going to make my partition key “customer”, which might lead one to believe that I’m going to put customers and something else in the same table. Let’s assume that I have accounts for customers and accounts for employees, and the fields are the same for both types of data. I’m only going to use this model for customers, though, so I’m going to set the partition key when instantiating a new object.

private readonly string partitionKey = "customer";
public string PartitionKey { get; set; }
public string RowKey { get; set; }

Now we need a default constructor, and a constructor that accepts parameters. In the constructor, I am setting the partition key and the rowkey. I’m using firstname + lastname as the rowkey. I realize this is innately stupid, but I just want a simple example. When you write something you’re actually going to use in production, pick your partition key and row key carefully.

public Customer() { }

public Customer(string firstName, string lastName, string favoriteMovie, string favoriteLanguage)
{
  PartitionKey = partitionKey;
  RowKey = firstName + " " + lastName;

  FirstName = firstName;
  LastName = lastName;
  FavoriteMovie = favoriteMovie;
  FavoriteLanguage = favoriteLanguage;
}

Now our class needs an attribute to specify the primary key for our entities:

  [DataServiceKey("PartitionKey", "RowKey")]
  public class Customer

And we need a using statement:

using System.Data.Services.Common;

That takes care of the Customer class. Now we need a class that will replicate the classes that access the SQL database. These are relatively short, so let’s put all of them in one class. Right-click on the web role project and select Add Class. Call the class TableStorageMethods.

Add these using statements:

using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.Diagnostics;
using System.Data.Services.Client;
using System.Data;

Next, add some private variables and a constructor. We need a variable for the table client that you’re going to use to access table storage, and I’m putting the table name in as a private variable rather than hardcode it.

private CloudTableClient cloudTableClient;
string tableName = "customer";

public TableStorageMethods()
{
  //get a reference to the cloud storage account, and then make sure the table exists
  CloudStorageAccount cloudStorageAccount = 
    CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("DataConnectionString"));
  cloudTableClient = cloudStorageAccount.CreateCloudTableClient();
  cloudTableClient.CreateTableIfNotExist(tableName);
}

We need four methods. First, let’s look at AddCustomer. You have to get the data service context object, and then add the record and save the changes.

internal string ST_AddCustomer(string firstName, string lastName,
  string favoriteMovie, string favoriteLanguage)
{
  Trace.TraceInformation("[AddCustomer] called. FirstName = {0}, LastName = {1}, Movie = {2}, "
    + "Language = {3}", firstName, lastName, favoriteMovie, favoriteLanguage);

  Customer cust = new Customer(firstName, lastName, favoriteMovie, favoriteLanguage);

  string errorMessage = string.Empty;

  try
  {
    //add the record to the table
    TableServiceContext tableServiceContext = cloudTableClient.GetDataServiceContext();
    tableServiceContext.AddObject(tableName, cust);
    tableServiceContext.SaveChanges();
  }
  //you might want to handle these two exceptions differently
  catch (DataServiceRequestException ex)
  {
    errorMessage = "Error adding entry.";
    Trace.TraceError("[ST_AddCustomer] firstName = {0}, lastName = {1}, exception = {2}", 
      firstName, lastName, ex);
  }
  //this exception could be caused by a problem with the storage account
  catch (StorageClientException ex)
  {
    errorMessage = "Error adding entry.";
    Trace.TraceError("[ST_AddCustomer] firstName = {0}, lastName = {1}, exception = {2}", 
      firstName, lastName, ex);
  }
  //general catch
  catch (Exception ex)
  {
    errorMessage = "Error adding entry.";
    Trace.TraceError("[ST_AddCustomer] firstName = {0}, lastName = {1}, exception = {2}", 
      firstName, lastName, ex);
  }
  return errorMessage;
}

We need a method to get the record and retrieve the favorites for a specific customer. We’re using a linq query to retrieve the record with a matching partition key and row key.

internal string ST_GetCustomerFavorites(out string favoriteMovie, out string favoriteLanguage,
  string firstName, string lastName)
{
  Trace.TraceInformation("[GetCustomerFavorites] called. FirstName = {0}, LastName = {1}", 
    firstName, lastName);
  string errorMessage = string.Empty;
  favoriteMovie = string.Empty;
  favoriteLanguage = string.Empty;

  Customer cust = new Customer(firstName, lastName, string.Empty, string.Empty);

  try
  {
    TableServiceContext tableServiceContext = cloudTableClient.GetDataServiceContext();
    IQueryable<Customer> entities = (from e in tableServiceContext.CreateQuery<Customer>(tableName)
                                     where e.PartitionKey == cust.PartitionKey && e.RowKey == cust.RowKey
                                     select e);

    Customer getCust = entities.FirstOrDefault();
    favoriteMovie = getCust.FavoriteMovie;
    favoriteLanguage = getCust.FavoriteLanguage;
  }
  catch (Exception ex)
  {
    Trace.TraceError("[ST_GetCustomerFavorites] firstName = {0}, lastName = {1}, exception = {2}", 
      firstName, lastName, ex);
    errorMessage = "Error retrieving data.";
  }
  return errorMessage;
}

We need a method to update the favorite movie and favorite language for a specific person:

internal string ST_SetCustomerFavorites(string firstName, string lastName,
  string favoriteMovie, string favoriteLanguage)
{
  Trace.TraceInformation("[SetCustomerFavorites] FirstName = {0}, LastName = {1}, Movie = {2}, "
    + "Language = {3}", firstName, lastName, favoriteMovie, favoriteLanguage);

  string errorMessage = string.Empty;

  Customer cust = new Customer(firstName, lastName, favoriteMovie, favoriteLanguage);

  try
  {
    TableServiceContext tableServiceContext = cloudTableClient.GetDataServiceContext();
    IQueryable<Customer> entities = 
      (from e in tableServiceContext.CreateQuery<Customer>(tableName)
       where e.PartitionKey == cust.PartitionKey && e.RowKey == cust.RowKey
       select e);

    Customer entity = entities.FirstOrDefault();
    entity.FavoriteLanguage = favoriteLanguage;
    entity.FavoriteMovie = favoriteMovie;

    tableServiceContext.UpdateObject(entity);
    tableServiceContext.SaveChanges();

  }
  catch (Exception ex)
  {
    Trace.TraceError("[ST_SetCustomerFavorites] FirstName = {0}, LastName = {1}, ex = {2}", 
      firstName, lastName, ex);
    errorMessage = "Error setting customer favorites.";
  }
  return errorMessage;
}

And lastly, we need a method to get the list of customers. I don’t want to change my client application based on the data source, and the SQL Database method returns a dataset, so I’ve written this one to also return a dataset.

internal string ST_GetListOfCustomers(out DataSet customers)
{
  Trace.TraceInformation("[GetListOfCustomers] called.");
  string errorMessage = string.Empty;

  //since the SQL Azure version returns a dataset, create a dataset and return it.
  //this way you don't have to change the client code
  customers = new DataSet();
  DataTable dt = new DataTable();
  DataColumn wc = new DataColumn("ID", typeof(Int32));
  wc.AutoIncrement = true;
  wc.AutoIncrementSeed = 1;
  wc.AutoIncrementStep = 1;
  dt.Columns.Add(wc);

  dt.Columns.Add("FirstName", typeof(String));
  dt.Columns.Add("LastName", typeof(String));
  dt.Columns.Add("FavoriteMovie", typeof(String));
  dt.Columns.Add("FavoriteLanguage", typeof(String));

  try
  {
    //retrieve the list of customers
    TableServiceContext tableServiceContext = cloudTableClient.GetDataServiceContext();
    DataServiceQuery<Customer> dataServiceQuery = 
      tableServiceContext.CreateQuery<Customer>(tableName);
    IEnumerable<Customer> entities = 
      dataServiceQuery.Where(e => e.PartitionKey == "customer").AsTableServiceQuery<Customer>();
    if (entities != null)
    {
      //add the entries to the DataTable
      foreach (Customer cust in entities)
      {
        DataRow newRow = dt.NewRow();
        newRow["FirstName"] = cust.FirstName;
        newRow["LastName"] = cust.LastName;
        newRow["FavoriteMovie"] = cust.FavoriteMovie;
        newRow["FavoriteLanguage"] = cust.FavoriteLanguage;
        dt.Rows.Add(newRow);
      }
    }
    else
    {
      Trace.TraceError("[ST_GetListOfCustomers] No rows found in table.");
      errorMessage = "No rows found in table.";
    }
  }
  catch (Exception ex)
  {
    Trace.TraceError("[ST_GetListOfCustomers] ex = {0}", ex);
    errorMessage = "Error getting list of customers.";
  }
  //add the data table to the dataset
  customers.Tables.Add(dt);

  return errorMessage;
}

Now we need to change our service to call the TableStorageMethods instead of the SQL Database methods. Let’s put in a toggle that we can change back and forth.

Open CustomerServices.svc in the web role and add an enumeration under the private variables for the queue.

public enum DataBaseType { sqlazure, tablestorage }
private DataBaseType currentDataBase = DataBaseType.tablestorage;

Now let’s change each method to check the value of currentDataBase and call the appropriate routine. When I defined the names of the methods for table storage, I used the same names as the SQL Database methods but with “ST_” prefixed to them so I can easily change these.

In GetFavorites, change this:

CustomerFavorites cf = new CustomerFavorites();
errorMessage = cf.GetCustomerFavorites(out favoriteMovie, out favoriteLanguage, 
  firstName, lastName);

to this:

if (currentDataBase == DataBaseType.sqlazure)
{
  CustomerFavorites cf = new CustomerFavorites();
  errorMessage = cf.GetCustomerFavorites(out favoriteMovie, out favoriteLanguage,
    firstName, lastName);
}
else
{
  TableStorageMethods tsm = new TableStorageMethods();
  errorMessage = tsm.ST_GetCustomerFavorites(out favoriteMovie, out favoriteLanguage,
    firstName, lastName);
}

We’ll follow the same pattern for the rest. In UpdateFavoritesByName, change this:

CustomerFavoritesUpdate cfu = new CustomerFavoritesUpdate();
errorMessage = cfu.SetCustomerFavorites(firstName, lastName, favoriteMovie, favoriteLanguage);

to this:

if (currentDataBase == DataBaseType.sqlazure)
{
  CustomerFavoritesUpdate cfu = new CustomerFavoritesUpdate();
  errorMessage = cfu.SetCustomerFavorites(firstName, lastName, favoriteMovie, favoriteLanguage);
}
else
{
  TableStorageMethods tsm = new TableStorageMethods();
  errorMessage = tsm.ST_SetCustomerFavorites(firstName, lastName, favoriteMovie, favoriteLanguage);
}

In AddACustomer, change this:

CustomerFavoritesAdd cfa = new CustomerFavoritesAdd();
errorMessage = cfa.AddCustomer(firstName, lastName, favoriteMovie, favoriteLanguage);

to this:

if (currentDataBase == DataBaseType.sqlazure)
{
  CustomerFavoritesAdd cfa = new CustomerFavoritesAdd();
  errorMessage = cfa.AddCustomer(firstName, lastName, favoriteMovie, favoriteLanguage);
}
else
{
  TableStorageMethods tsm = new TableStorageMethods();
  errorMessage = tsm.ST_AddCustomer(firstName, lastName, favoriteMovie, favoriteLanguage);
}

And lastly, in GetCustomerList, change this:

CustomerList cl = new CustomerList();
errorMessage = cl.GetListOfCustomers(out customers);

to this:

if (currentDataBase == DataBaseType.sqlazure)
{
  CustomerList cl = new CustomerList();
  errorMessage = cl.GetListOfCustomers(out customers);
}
else
{
  TableStorageMethods tsm = new TableStorageMethods();
  errorMessage = tsm.ST_GetListOfCustomers(out customers);
}

Now let’s run our service. We don’t need to update our service reference, because we didn’t make any changes to the service contract. Run the client application, and click Get Customer List. We will see nothing, because we haven’t added any records to the version running against Table Storage yet.

So add a couple of records, and then retrieve the customer list. So now everything is running against Azure table storage. If I run the Cerebrata Cloud Storage Studio and look in my development storage, and I can see the customer table with the entries I just added.

So now we have a WCF service running in a web role that performs CRUD operations against SQL Azure or Windows Azure Table Storage, and writes diagnostic information. Our WCF service has a method that lets us add an entry to the queue. Then we have a worker role that retrieves the entry from the queue and writes it to blob storage. We have a client that calls the WCF service.

If you set the connection strings correctly in the ServiceConfiguration.Cloud.cscfg file, you can publish your service to the cloud. Then just change the URL at the top of the DAC class in the TestClient, and it will point to that service. Then you can run your client application against the service running in Azure.

That wraps up the 7-part series called “Azure for Developers”, showing the features of Windows Azure and talking about how I’ve used them in my production environment at GoldMail. For a completed version of the code, check out the version from the June 2012 San Diego Code Camp talk, which you can download from here.

Azure for Developers Tutorial Step 6: Processing the queue

July 8, 2012

This is the sixth step of the Azure for Developers tutorial, in which we set up a WCF service running in Azure to provide CRUD operations to a client application. For more information, please check out the Introduction.

In the last post, we changed our service to write messages to a queue. In this post, we will set up a worker role to pull the messages off of the queue and process them. But first, a few words about queues.

How would you use a queue?

GoldMail is an application that lets you add "slides” (pictures, screenshots, PowerPoint slides, etc.), and you record audio (voice or sound file) over them and then “share” the GoldMail, which sends your assets up to the cloud. When someone plays your GoldMail, the player retrieves those assets. When played on a mobile device, we don’t want to return the same large images we are using for our desktop player.

We could resize the images on the customer’s computer and send them up with the originals, but why should he have to wait for that to happen? So we use a queue and a worker role to handle this. When the service gets the “finished” message from the desktop application, it puts a message on the queue. A worker role picks the message off of the queue, downloads the large images and resizes them to small images, and then uploads the small images to the same folder where the original images reside and flips a boolean in the database.

Another use of queues if to offload database updates that don’t need to be written immediately. If someone sends a GoldMail to a thousand people, and half of them view it, we would get 500 updates to our database at the same time. To meter these updates, we write those update requests to a queue, and we have a worker role that pulls the entries off of the queue and updates the database.

What is Invisibility?

I have a lot of trace logging, and when I put the processing of the mobile slides in, I noticed that there was a problem. It looked like the first instance of the worker role was picking the entry off the queue, and before it could finish, the second role was picking the entry off the queue and trying to process it, too.

When you read a message from the queue, it doesn’t really remove the message from the queue, it marks it as invisible. After a certain amount of time, if the message has not been deleted from the queue, it reappears to be processed again. The default time is 30 seconds. It takes about 1 minute and 30 seconds to process a hundred slides. So the message was becoming visible again and the second instance of the worker role was picking it up and trying to process it, even though the first instance was still working on it. Oops.

When you take the message off of the queue, you can set the amount of time you want it to be invisible. I ran hundreds of GoldMails through the worker role and determined that the longest conversion time was a minute and a half, so I set the invisibility time to 2 minutes. (Better safe than sorry).

How can I mess up my production queue processing?

Queues are storage, and can be accessed by any instance of any service. We had another case where we published a new version of our service to the staging instance in preparation for going into production, and it started processing entries from the production queue, and since we had changed the format of the messages, the processing failed. Oops.

Since we publish our worker roles and web roles in the same Azure service, we can’t stop one without stopping the other, and we don’t want to stop production. So when we’re publishing new versions, we change the queue name (which is in the Azure role configuration) for the worker role to an unused queue and publish it to staging. After doing the VIP swap, we stop the old production service now in staging, then change the queue name in the production configuration, at which point the worker role will start processing any accumulated messages.

What if a queue message just won’t process?

If we have a problem processing an entry from the queue, we don’t delete it from the queue, we let it come back around and try processing it again. You obviously don’t want to do this infinitely, or you could end up with one message stopping up your queue. You should check the dequeue count for the message, and if it’s over some threshold, add the message to an error queue and delete it from the regular queue. Then check the error queue every now and then to see if you’ve had any problems.

Can we get back to the code now?

With all that information, let’s now add processing for our queue. We’ll add a worker role. Open our AzureCustomerServices solution. In the cloud project, right-click on Roles, and select “Add New Worker Role Project”.

You’ll be prompted for the role to add.

Pick the Worker Role, and name it CustomerWorker, then click Add.

First, let’s set up the configuration. Double-click on the ServiceConfiguration.Local.cscfg in the cloud project. If you go down to the bottom, you will find that it has added a new section for the worker role. Add these configuration settings:

      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
               value="UseDevelopmentStorage=true" />
      <Setting name="DataConnectionString" value="UseDevelopmentStorage=true" />
      <!-- frequency, in seconds, to retrieve the perf counters -->
      <Setting name="PerfMonSampleRate" value="60" />
      <!-- frequency, in seconds, to transfer the perf counters to the logs from the system-->
      <Setting name="PerfMonScheduledTransferPeriod" value="120" />
      <Setting name="ProcessQueueName" value="codecampqueue" />
      <Setting name="QueueMessageVisibilityTime" value="120" />

 

Then open ServiceConfiguration.Cloud.cscfg and find the worker role section at the bottom, then make the same changes, or put in your real values if you want to test the service in the cloud:

      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
           value="DefaultEndpointsProtocol=https;AccountName=PUTYOURACCOUNTNAMEHERE;
           AccountKey=PUTYOURACCOUNTKEYHERE" />
      <Setting name="DataConnectionString" value="DefaultEndpointsProtocol=https;
               AccountName=PUTYOURACCOUNTNAMEHERE;AccountKey=PUTYOURACCOUNTKEYHERE" />
      <!-- frequency, in seconds, to retrieve the perf counters -->
      <Setting name="PerfMonSampleRate" value="60" />
      <!-- frequency, in seconds, to transfer the perf counters to the logs from the system-->
      <Setting name="PerfMonScheduledTransferPeriod" value="120" />
      <Setting name="ProcessQueueName" value="codecampqueue" />
      <Setting name="QueueMessageVisibilityTime" value="120" />

Now open ServiceDefinition.csdef and find the worker role section at the bottom, and add a section for the configuration settings.

    <ConfigurationSettings>
      <Setting name="DataConnectionString" />
      <Setting name="PerfMonSampleRate" />
      <Setting name="PerfMonScheduledTransferPeriod" />
      <Setting name="ProcessQueueName" />
      <Setting name="QueueMessageVisibilityTime" />
    </ConfigurationSettings>


I’ll explain the settings as we use them. Now let’s add a class to our worker role project and call it GlobalStaticProperties. Rather than retrieve the configuration values from the role repeatedly, I retrieve them from this class, which only reads them the first time they are retrieved. So right-click on CustomerServicesWebRole and choose Add Class. Name it GlobalStaticProperties and click OK. Change the class from Public to Internal Static.

Add these using statements at the top:

using Microsoft.WindowsAzure.ServiceRuntime;
using System.Diagnostics;

And here is the code you need to put in the class. I’ve put comments inline to explain what each variable is.

internal static class GlobalStaticProperties
{

  private static string _ProcessQueueName;
  /// <summary>
  /// name of the queue
  /// </summary>
  internal static string ProcessQueueName
  {
    get
    {
      if (string.IsNullOrEmpty(_ProcessQueueName))
      {
        _ProcessQueueName = RoleEnvironment.GetConfigurationSettingValue("ProcessQueueName");
        Trace.TraceInformation("[CustomerWorker.GlobalStaticProperties] "
          + "ProcessQueueName to {0}", _ProcessQueueName);
      }
      return _ProcessQueueName;
    }
  }

  private static int _QueueMessageVisibilityTime { get; set; }
  /// <summary>
  /// This is the amount of time the message remains invisible after being
  /// read from the queue, before it becomes visible again (unless it is deleted)
  /// </summary>
  internal static int QueueMessageVisibilityTime
  {
    get
    {
      if (_QueueMessageVisibilityTime <= 0)
      {
        //hasn't been loaded yet, so load it 
        string VisTime = 
          RoleEnvironment.GetConfigurationSettingValue("QueueMessageVisibilityTime");
        int intTest = 0;
        bool success = int.TryParse(VisTime, out intTest);
        if (!success || intTest <= 0)
        {
          _QueueMessageVisibilityTime = 120;
        }
        else
        {
          _QueueMessageVisibilityTime = intTest;
        }
        Trace.TraceInformation("[CustomerWorker.GlobalStaticProperties] " 
          + "Setting QueueMessageVisibilityTime to {0}", _QueueMessageVisibilityTime);
      }
      return _QueueMessageVisibilityTime;
    }
  }
}

Now we have the configuration set up and handled, we want to add code to set up the diagnostics configuration. We need the diagnostics configuration code to execute when the worker role starts up, so open WorkerRole.cs. Add this using statement at the top:

using Microsoft.WindowsAzure.Diagnostics.Management;


In the OnStart() method, right after this code:

// Set the maximum number of concurrent connections 
ServicePointManager.DefaultConnectionLimit = 12;

add the diagnostics configuration code. I’ve included comments to explain the code. This is identical to the code we put in the web role back in part 1.

// Get a reference to the initial default configuration.
string wadConnectionString = "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";

// First, get a reference to the storage account where the diagnostics will be written. 
// It is recommended that you use a separate account for diagnostics and data, so the 
//   performance of your data access is not impacted by the diagnostics.
CloudStorageAccount storageAccount =
    CloudStorageAccount.Parse(
    RoleEnvironment.GetConfigurationSettingValue(wadConnectionString));

// Get an reference to the diagnostic manager for the role instance, 
//   and then get the default initial configuration, which we will then change.
RoleInstanceDiagnosticManager roleInstanceDiagnosticManager =
    storageAccount.CreateRoleInstanceDiagnosticManager(RoleEnvironment.DeploymentId,
    RoleEnvironment.CurrentRoleInstance.Role.Name, RoleEnvironment.CurrentRoleInstance.Id);
DiagnosticMonitorConfiguration config = DiagnosticMonitor.GetDefaultInitialConfiguration();

// Change the polling interval for checking for configuration changes
//   and the buffer quota for the logs. 
config.ConfigurationChangePollInterval = TimeSpan.FromSeconds(30.0);
config.DiagnosticInfrastructureLogs.BufferQuotaInMB = 256;

// The diagnostics data is written locally and then transferred to Azure Storage. 
// These are the transfer intervals for doing that operation.
config.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1.0); //for trace logs
config.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(1.0); //for iis logs

// Configure the monitoring of one Windows performance counter
// and add it to the configuration.
int sampleRate = 0;
int scheduledTransferPeriod = 0;
bool success = false;
//this is sample rate, in seconds, for the performance monitoring in %CPU.
//By making this configurable, you can change the azure config rather than republish the role.
success = int.TryParse(RoleEnvironment.GetConfigurationSettingValue("PerfMonSampleRate"),
  out sampleRate);
if (!success || sampleRate <= 0)
  sampleRate = 60;  //default is 60 seconds
success =
  int.TryParse(RoleEnvironment.GetConfigurationSettingValue("PerfMonScheduledTransferPeriod"),
  out scheduledTransferPeriod);
if (!success || scheduledTransferPeriod <= 0)
  scheduledTransferPeriod = 120;  //default is 120 seconds

PerformanceCounterConfiguration perfConfig
    = new PerformanceCounterConfiguration();
perfConfig.CounterSpecifier = @"\Processor(*)\% Processor Time";
perfConfig.SampleRate = TimeSpan.FromSeconds((double)sampleRate);
config.PerformanceCounters.DataSources.Add(perfConfig);
config.PerformanceCounters.ScheduledTransferPeriod =
  TimeSpan.FromSeconds((double)scheduledTransferPeriod);

// Configure monitoring of Windows Application and System Event logs,
// including the quota and scheduled transfer interval, and add them 
// to the configuration.
WindowsEventLogsBufferConfiguration eventsConfig
    = new WindowsEventLogsBufferConfiguration();
eventsConfig.BufferQuotaInMB = 256;
eventsConfig.ScheduledTransferLogLevelFilter = LogLevel.Undefined; //was warning
eventsConfig.ScheduledTransferPeriod = TimeSpan.FromMinutes(2.0); //was 10
eventsConfig.DataSources.Add("Application!*");
eventsConfig.DataSources.Add("System!*");
config.WindowsEventLog = eventsConfig;

//set the configuration to be used by the current role instance
roleInstanceDiagnosticManager.SetCurrentConfiguration(config);

//add an event handler for the configuration being changed while the role is running
RoleEnvironment.Changing += 
  new EventHandler<RoleEnvironmentChangingEventArgs>(RoleEnvironment_Changing);
return base.OnStart();

Add the event handler for the RoleEnvironment Changing event:

void RoleEnvironment_Changing(object sender, RoleEnvironmentChangingEventArgs e)
{
  // If a configuration setting is changing
  if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
  {
    // Set e.Cancel to true to restart this role instance
    e.Cancel = true;
  }
}


That takes care of the diagnostics. Now let’s put in the code for handling the queue. We need to define our queue, so add this at the top of the WorkerRole class:

CloudQueue queue;

Now we need to add a method to be run only once after the worker role starts up that makes sure the queue exists, and if it doesn’t, it creates it. Let’s call it StartupQueue(). I’ve added comments to explain what the code is doing.

private void StartUpQueue()
{
  //get a reference to the storage account
  CloudStorageAccount storageAccount =
      CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue(
      "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"));

  // initialize the queue client that will be used to access the queue 
  CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient();
  string queueName = GlobalStaticProperties.ProcessQueueName;
  //get a reference to the queue
  queue = queueStorage.GetQueueReference(queueName);

  //only initialize this once after the role starts up
  //so check this boolean and loop until it manages to make sure the queue is present 
  //because this role can't run without the queue
  bool storageInitialized = false;
  while (!storageInitialized)
  {
    try
    {
      // create the message queue if it doesn't already exist
      queue.CreateIfNotExist();
      // set this to true, because at this point, we know it's there
      storageInitialized = true;
    }
    catch (StorageClientException ex)
    {
      // for this error, give a reminder about the dev storage service being started
      if (ex.ErrorCode == StorageErrorCode.TransportError)
      {
        Trace.TraceError("[CustomerWorker.StartUpQueue] Storage services initialization failure."
          + " Check your storage account configuration settings. If running locally,"
          + " ensure that the Development Storage service is running. Message: '{0}'", 
          ex.Message);
        //sleep 5 seconds and then loop back around and try again
        System.Threading.Thread.Sleep(5000);
      }
      else
      {
        Trace.TraceError("[CustomerWorker.StartUpQueue] StorageClientException thrown. "
          + "Ex = {0}", ex.ToString());
        throw;
      }
    }
    catch (Exception ex)
    {
      Trace.TraceError("[CustomerWorker.StartupQueue] Exception thrown "
        + "trying to initialize the queue. Ex = {0}", ex.ToString());
      throw;
    }
  }
}

Now we need to add the processing. At the top of the worker role, you have a Run() method that looks like this:

public override void Run()
{
  // This is a sample worker implementation. Replace with your logic.
  Trace.WriteLine("$projectname$ entry point called", "Information");

  while (true)
  {
    Thread.Sleep(10000);
    Trace.WriteLine("Working", "Information");
  }
}

After all these years of programming, it totally goes against my grain to put in an infinite loop, but that’s how the worker role works. It will run until it breaks out of the loop or the service goes down. We’re going to replace this code. Our code will call our StartupQueue method, and then loop infinitely looking for a message on the queue, and processing the message when it finds one.

public override void Run()
{
  //start up the queue
  StartUpQueue();

  //loop infinitely until the service shuts down
  while (true)
  {
    try
    {
        // retrieve a new message from the queue, set the visibility
      // this is hours, minutes, seconds, and the global static property is in seconds
      TimeSpan visTimeout = 
        new TimeSpan(0, 0, GlobalStaticProperties.QueueMessageVisibilityTime);
      CloudQueueMessage msg = queue.GetMessage(visTimeout);
      if (msg != null)
      {
        Trace.TraceInformation("[CustomerWorker.Run] message = {0}, time = {1}, "
          + "next visible time = {2}",
          msg.AsString, DateTime.UtcNow, msg.NextVisibleTime.Value.ToString());
        string errorMessage = string.Empty;
        //process the message 
        //assume comma-delimited, first is command. check it and handle the message accordingly
        string[] msgFields = msg.AsString.Split(new char[] { ',' });
        string command = msgFields[0];
        switch (command)
        {
          case "process":
            string firstName = msgFields[1];
            string lastName = msgFields[2];
            string favoriteMovie = msgFields[3];
            string favoriteLanguage = msgFields[4];
            ProcessQueue pq = new ProcessQueue();
            pq.ProcessQueueEntry(firstName, lastName, favoriteMovie, favoriteLanguage,
              container);
            break;
        }

        // remove message from queue
        //http://blog.smarx.com/posts/deleting-windows-azure-queue-messages-handling-exceptions            
        try
        {
          queue.DeleteMessage(msg);
        }
        catch (StorageClientException ex)
        {
          if (ex.ExtendedErrorInformation.ErrorCode == "MessageNotFound")
          {
            // pop receipt must be invalid
            // ignore or log (so we can tune the visibility timeout)
          }
          else
          {
            // not the error we were expecting
            throw;
          }
        }
      }
      else
      {
        //no message found, sleep for 5 seconds
        Thread.Sleep(5000);
      }
    }
    catch (Exception ex)
    {
      Trace.TraceError("[CustomerWorker.Run] Exception thrown "
        + "trying to read from the queue = {0}", ex.ToString());
      Thread.Sleep(5000);
    }
  }    
}

This reads the message off of the queue. The message will be null if there is no message available. After retrieving it, we know it is a comma-delimited string, so just split it into an array to get the different values. I always send a command as the first variable. Your worker role may only process one command, but if you set it up this way, you can send other commands to your worker role to be handled as well.

We want our ProcessQueue class to write the data to blob storage. To do that, it needs a reference to the blob container to pass in to ProcessQueue. So define the container for the blob at the top of the Worker Role class, under the definition for the queue:

CloudBlobContainer container;

In the OnStart event, right before calling base.OnStart(), let’s add some code to set up our container.

//****************Container****************
//get a reference to the blob client
CloudBlobClient cbc = storageAccount.CreateCloudBlobClient();
//get a reference to the container, and create it if it doesn't exist
container = cbc.GetContainerReference("codecamp");
container.CreateIfNotExist();
//now set the permissions so the container is private, 
//  but the blobs are public, so they can be accessed with a specific URL
BlobContainerPermissions permissions = new BlobContainerPermissions();
permissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(permissions);

This basically makes sure the container exists, and sets the reference to the container. If you don’t set the permissions on the container after it’s created, it will make the container and blobs private, and you won’t be able to access the blobs through a URL. This code sets the permissions so the container as private, but the blobs are public. This means nobody can iterate through the blobs in the container, but they can access a blob if they have a URL for it.

Now let’s set up a method to process our queue entry. Right-click on the CustomerWorker project and select AddClass. Call the class ProcessQueue. You need a method that will take the four attributes, format them, and write the result to the container in blob storage. Here’s the ProcessQueueEntry method:

public string ProcessQueueEntry(string firstName, string lastName, string favoriteMovie, 
  string favoriteLanguage, CloudBlobContainer container)
{
  string errorMessage = string.Empty;

  try
  {
    Trace.TraceInformation("[ProcessQueueEntry] for command [process], " +
        "firstName = {0}, lastName = {1}, favoriteMovie = {2}, favoriteLanguage = {3}",
        firstName, lastName, favoriteMovie, favoriteLanguage);

    //let's write the information to blob storage. First, create the message.
    string messageToWrite = string.Format("FirstName = {0}{1}LastName={2}{1}" +
      "FavoriteMovie = {3}{1}FavoriteLanguage={4}",
      firstName, Environment.NewLine, lastName,
      favoriteMovie, favoriteLanguage);

    //now create the file name -- I'm putting the date/time stamp in the name.
    string fileName = "test_" + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd-hh-mm-ss",
      new System.Globalization.CultureInfo("en-US")) + ".txt";

    //get a reference to the blob 
    var blob = container.GetBlobReference(fileName);

    //upload the text to the blob 
    blob.UploadText(messageToWrite);

  }
  catch (Exception ex)
  {
    errorMessage = "Error processing entry.";
    Trace.TraceError("[ProcessQueueEntry] Exception thrown = {0}", ex);
  }
  return errorMessage;
}

Now run the service. The entries that were in the queue before this step will now be processed. You can look in the development storage blob storage account for a folder called “codecamp”, and you should see your blobs in there. Here’s one of mine, opened in Notepad.

Now run the client and fill in a first and last name and click AddToQueue and it will add it to the queue, and then the worker role will pick it up and process it and write it to blob storage. If it doesn’t work right, put some breakpoints into the service and debug it while it’s running.

So we now have a WCF service running in a web role that reads from and writes to a Windows Azure SQL Database. It submits messages to an Azure queue, and there is a worker role that retrieves the entries from the queue and writes them to blob storage. We have a client that we can use to test the service.

We also have the diagnostics working. If I check the WADLogsTable in development storage, I can see the messages that are being logged:

I’m just showing the message field; it also stores the deploymentID, role instance, etc. There are also tables for the other diagnostics, and in blob storage, I can see my IIS logs.

What if you want to use Windows Azure table storage instead of a SQL database? In the next part, we’ll change our service to do exactly that, and be able to toggle back and forth between the two methods.

Azure for Developers Tutorial Step 5: Adding messages to a queue

July 8, 2012

This is the fifth step of the Azure for Developers tutorial, in which we set up a WCF service running in Azure to provide CRUD operations to a client application. For more information, please check out the Introduction.

In this step, we will add a method to our service to allow the user to put messages on the queue. We will let him pass in the first and last name, and we’ll retrieve the Favorites and then put a message on the queue with the four fields in it.

Open CustomerServices.svc in our project. Add these using statements at the top:

using Microsoft.WindowsAzure.StorageClient;
using System.Diagnostics;
using Microsoft.WindowsAzure;
using System.Net;
using Microsoft.WindowsAzure.ServiceRuntime;

Then in the class itself, add these private variables.

private static bool storageInitialized = false;
private static object gate = new Object();
private static CloudQueueClient queueStorage;
private static CloudQueue queue;

We will add a method for adding a message to the queue. In that method, we will call InitializeQueue(). The first time the queue is initialized, it will set a boolean called storageInitialized to true, and then subsequent calls will know it is already initlaized. The object gate is used for locking, to make sure multiple people don’t initialize the queue at the same time.

CloudQueueClient is the client for accessing the queue, and CloudQueue is the queue itself.

So let’s add InitializeQueue(). I’ve added comments in-line to explain what it’s doing.

//initialize the queue, but only the first time 
private void InitializeStorage()
{
  //if it's already initialized, return
  if (storageInitialized)
  {
    return;
  }
  //lock the object
  lock (gate)
  {
    //if someone else initialize the queue while you had the object locked,
    //  return
    if (storageInitialized)
    {
      return;
    }
    //try initializing the queue
    try
    {
      Trace.TraceInformation("[CustomerServices.InitializeStorage] Initializing storage queue");
      // read account configuration settings and get a reference 
      //  to the storage account
      CloudStorageAccount storageAccount =
          CloudStorageAccount.Parse(
          RoleEnvironment.GetConfigurationSettingValue(
          "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"));

      // get a reference to the queue client
      queueStorage = storageAccount.CreateCloudQueueClient();
      // this uses the entry in GlobalStaticProperties that is set in the role config
      queue = queueStorage.GetQueueReference(GlobalStaticProperties.ProcessQueueName);
      //create the queue if it doesn't already exist
      queue.CreateIfNotExist();
    }
    catch (WebException ex)
    {
      //try to give some help
      Trace.TraceError("[CustomerServices.InitializeStorage] WebException thrown trying " 
        + " to initialize the storage services. " 
        + " Check the storage account config settings. If running locally, "
        + "be sure Dev Storage svc is running. Exception = {0}",
          ex.ToString());
      return;
    }
    catch (Exception ex)
    {
      Trace.TraceError("[CustomerServices.InitializeStorage] Exception thrown trying "
        + "to initialize the storage. Exception = {0}", ex.ToString());
      return;
    }
    //this is only set to true if it doesn't throw an exception
    storageInitialized = true;
  }
}

We’re going to add a method to add a message to the queue. This actually formats the message and adds it to the queue. (In my production services, I put this code in a separate class.) Our message is going to be a comma-delimited string. I always pass a ‘command as the first entry in case I want to use the queue to process different requests.

If any of the four input fields have commas in them, they won’t parse right. At GoldMail, we hit this in a case where we are passing the user agent string to the backend in a message, and the user agent string had commas in it. We had to put in special handling for this case.

If you have more information than you want to put in a string, you can write it to blob storage and put a URL to the file in blob storage in the message.

Here’s our new method; add this to ComposerServices.svc.cs.

public string SubmitToQueue(string firstName, string lastName)
{
  //call to make sure the queue exists
  InitializeStorage();
  string errorMessage = string.Empty;
  string favoriteMovie = string.Empty;
  string favoriteLanguage = string.Empty;
  //get the favorites info for the name passed in
  errorMessage = GetFavorites(out favoriteMovie, out favoriteLanguage,
      firstName, lastName);     
  if (errorMessage.Length == 0)
  {
    //I'm passing the message as a comma-delimited string. Format the string.
    string msgString = String.Format("process,{0},{1},{2},{3}", 
      firstName, lastName, favoriteMovie, favoriteLanguage);
    //set the message
    CloudQueueMessage message = new CloudQueueMessage(msgString);
    Trace.TraceInformation("[SubmitToQueue] Message passed to queue = {0}", msgString);
    //add the message to the queue
    queue.AddMessage(message);
  }
  else
  {
    errorMessage = "Entry not submitted to queue. "
      + "Error when retrieving favorites = '" + errorMessage + "'.";
    Trace.TraceError("[SubmitToQueue] firstName = {0}, lastName = {1}, {2}",
        firstName, lastName, errorMessage);
  }
  return errorMessage;
}

Now let’s add the corresponding operation contract  to the interface. If you don’t do this, the method won’t be exposed to the client. Open ICustomerServices.cs and add the operation contract.

[OperationContract]
string SubmitToQueue(string firstName, string lastName);

Run the service locally. Now let’s change the client to add the entries to the queue. Open the TestService. Right-click on the service reference to our service and select Update Service Reference and click OK. This should expose the SubmitToQueue method. Add this to the DAC.cs class in the test client:

  internal static string AddToQueue(string firstName, string lastName)
  {
    string errorMessage = string.Empty;
    CustomerSvc.CustomerServicesClient prx = getClient();
    errorMessage = prx.SubmitToQueue(firstName, lastName);
    return errorMessage;
  }

Now open the code-behind for the form. Look for the btnAddToQueue_Click event handler. Change this:

string errorMessage = string.Empty; // DAC.AddToQueue(txtFirstName.Text, txtLastName.Text);

to this:

string errorMessage = DAC.AddToQueue(txtFirstName.Text, txtLastName.Text);

Run the client application. Click on GetCustomerList to see your list of customers. Now fill in the first and last name of one of your customers and cliick the AddToQueue button. Try a couple of them.

We haven’t written anything to process the messages, so they will sit on the queue for a week before they are automatically removed. If I open Cerebrata Cloud Storage Studio and look at the queue in my development storage, I can see the messages:

In my next post, I’ll show you how to set up the worker role and read the messages and write the information to blob storage.

Azure for Developers Tutorial Step 4: Calling the WCF service from the client

July 8, 2012

This is the fourth step of the Azure for Developers tutorial, in which we set up a WCF service running in Azure to provide CRUD operations to a client application. For more information, please check out the Introduction.

In the previous step, we finished the WCF service and ran it in the compute emulator. So if you haven’t got it running, you’ll want to run it now. Put a breakpoint in AddCustomer in CustomerServices.svc.cs. Let’s up the client application. If you haven’t already downloaded the client application we’ll be changing, please do so now; it is here.

The first thing you want to do is add a service reference to your service running in the compute emulator. This will retrieve the service definition. So right-click on Service References and select “Add Service Reference”. Find the open browser showing your service information and copy the link. It will be something like http://127.0.0.1:81/CustomerServices.svc.

Paste the link into the Add Service Reference dialog. Click Go to find the service. If it finds it, it will show it in the Services window. If you click on the service’s interface, it will show the exposed Operation Contracts on the right. When adding new methods to your service, you have to be sure to also add them to the interface, or they won’t be exposed to the client. (Please don’t ask how many times I’ve forgotten this!)

At the bottom, change the Namespace to CustomerSvc and click OK.

We need to set up the Data Access class in the client. Open DAC.cs. First, let’s add a method to get the proxy and set up the endpoint. This enables us to easily programmatically change the address of the service we’re connecting to. We’ll call this from the proxy methods that call the WCF service.

//put these here rather than relying on the app.config being right 
//so you can set up a service reference running the service locally,
//and then just change this to point to the instance in the cloud
private static string m_endpointAddress = @"http://127.0.0.1:81/CustomerServices.svc";
//private static string m_endpointAddress = 
//  http://yourservicename.cloudapp.net/CustomerServices.svc";

private static CustomerSvc.CustomerServicesClient getClient()
{
  CustomerSvc.CustomerServicesClient prx = new CustomerSvc.CustomerServicesClient();
  prx.Endpoint.Address = new System.ServiceModel.EndpointAddress(m_endpointAddress);
  //this sets the timeout of the service call, which should give you enough time
  //  to finish debugging your service call
  prx.InnerChannel.OperationTimeout = new TimeSpan(0, 5, 0);
  return prx;
}

Now let’s add the rest of the methods:

internal static string GetFavoritesForCustomer(out string favoriteMovie, 
  out string favoriteLanguage, string firstName, string lastName)
{
  favoriteMovie = string.Empty;
  favoriteLanguage = string.Empty;
  CustomerSvc.CustomerServicesClient prx = getClient();
  return prx.GetFavorites(out favoriteMovie, out favoriteLanguage, firstName, lastName);
}

internal static string UpdateFavoritesByName(string firstName, string lastName, 
  string favoriteMovie, string favoriteLanguage)
{
  CustomerSvc.CustomerServicesClient prx = getClient();
  return prx.UpdateFavoritesByName(firstName, lastName, favoriteMovie, favoriteLanguage);
}

internal static string GetCustomerList(out DataSet customers)
{
  customers = new DataSet();
  CustomerSvc.CustomerServicesClient prx = getClient();
  return prx.GetCustomerList(out customers);
}

internal static string AddACustomer(string firstName, string lastName, 
  string favoriteMovie, string favoriteLanguage)
{
  CustomerSvc.CustomerServicesClient prx = getClient();
  return prx.AddACustomer(firstName, lastName, favoriteMovie, favoriteLanguage);
}

These are already hooked up in the code-behind in the form. So you can just click F5 to run the application.

So now you should have the service running and the client application running, and you should have a breakpoint in AddCustomer in the service. Fill in the four fields and click on the Add button.

It should call into the service and stop at your breakpoint. Then you can step through your service code and debug it if you have any problems.

Add a few records, then click on GetCustomerList to retrieve them. Fill in a First Name and Last Name from the list and blank out the two Favorite fields, then click GetFavorites, and it should fill them in. Change the favorites and click UpdateFavorites, then retrieve the Customer list again and see if they have been changed.

If everything works, you’re golden. You now have a working WCF service and a client to test it with.

In the next post, we’ll add a queue to the service and add a service call to add a message to the queue. Then we’ll add a worker role to the service that will retrieve messages from the queue and write them to blob storage.


Follow

Get every new post delivered to your Inbox.

Join 55 other followers