hand plugging an ethernet cord into a cloud

Hosting WCF Services on Azure 101

Leveraging Web and Worker Roles for your WCF Services

Historically in this column we’ve focused on designing, building, and running WCF and Workflow Services on premises. This time, let's examine how you can leverage your existing experience in WCF by building services that run on Windows Azure. We will approach this by walking through the development lifecycle of a simple WCF service that runs within Windows Azure. The purpose is twofold: encouraging you to get your feet wet, but also pointing out some of the hidden gotchas that can waste hours of your time even in the simplest scenarios.

Windows Azure from Space

If you’ve had any exposure to Windows Azure, you’re probably already familiar with its two major components: Windows Azure Storage (which provides table, queue, and blob storage) and Windows Azure Compute (which is where your code runs within Azure). In this article we will focus exclusively on Windows Azure Compute, particularly as it applies to web services.

Window Azure Compute consists of two components that are cloud equivalents of what you are familiar with in the on-premises world. Windows Azure Web Roles are akin to ASP.NET websites hosted in IIS. Windows Azure Worker Roles are the equivalent of a Windows Service.

Which Role for Services?

If these are your two options for hosting, where should you put web services? Are Web Roles, as the name implies, simply for websites and Worker Roles the place for services? You can think of Worker Roles as a process that will self-host your WCF ServiceHost, in much the same way that a Windows Service or console application might. Also, with IIS 7 you’re able to host ASP.NET web pages alongside WCF Services (with SVC endpoints) or Workflow Services (with XAMLX endpoints).

In practice, the answer to which role to use really boils down to the protocols you require for communication between Internet clients and your WCF service.  For services which will rely on HTTP or HTTPS, you will want to host your service within a Web Role. TCP services, on the other hand, should be hosted in a Worker Process. This gotcha can be quite hard to debug as there is no obvious exception that occurs, for example, when you attempt to host an HTTP-based service within a Worker Role. Moreover, the lack of documentation on this and a plethora of conflicting blogs increase this confusion. Also, note that currently there is no support for XAMLX services (or WF 4.0 in general) as of this writing in either role.

Both roles support the notion of endpoints that are available for communicating with Internet clients (known as input endpoints) as well as for communication between services and services in different roles (known as internal endpoints). Odds are your first services will need to communicate with Internet clients, so we focus on leveraging input endpoints in this article.

Let’s start with what our service looks like, as we might have defined for use on-premises. The ListService, as Figure 1 shows, consists of a single operation GetItems that takes a quantity of items desired, generates a list of strings representing those items, and returns the List.

//IListService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace CloudListService
{
    [ServiceContract]
    public interface IListService
    {
    [OperationContract]
    List GetItems(int quantity);
    }
}

//ListService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace CloudListService
{
    public class ListService : IListService
    {
    public List GetItems(int quantity)
    {
        List items = new List();

    for (int i = 0; i < quantity; i++)
        {
        items.Add(string.Format("Item Number {0}", i));
        }

    return items;
    }
    }
}

We built this by creating a WCF Service Library project, defining both the interfaces (IListService) and the service itself (ListService). In the on-premises case we could reference this type from an IIS hosted SVC file’s ServiceHost tag, or within the code which instantiates a ServiceHost instance within a Windows Service. In moving the service to Windows Azure Web Role, the process is similar to the former and the latter for a Worker Role.

Hosting the ListService with a Web Role

For simplicity, let’s assume we want to expose this service using a basicHttpBinding across the HTTP protocol. As we know, we will need a Web Role for this. To begin, we will add a new project within our existing solution that contains the ListingService project. Within the New Project dialog’s Installed Templates list, select the Cloud category and then Windows Azure Cloud Service. I'll name the project CloudListService and click OK, as Figure 2 shows.

Figure 2: Creating a new Cloud service project
Figure 2: Creating a new Cloud service project

Next, a dialog will appear asking what roles to add to our Cloud Service project. Each role selected in this dialog will appear as a separate project in the same solution. I've selected WCF Service Web Role from the list of available roles on the left, and then clicked the “>” button to add it to our solution. Hovering over the entry with the mouse cursor displays a pencil icon, which lets use rename the service to ListServiceWeb, as Figure 3 shows.

Figure 3: Adding a WCF Service Web Role to the new Cloud Service Project
Figure 3: Adding a WCF Service Web Role to the new Cloud Service Project

This adds two projects to our solution, one called CloudService (which represents the complete Windows Azure service) and another called ListServiceWeb which will contain the Web Role implementation of the ListService, in addition to our ListService project which contains the service library itself.

Update the AddressFilterMode. Before we proceed, we need to make one modification to our ListService definition to account for the fact that once deployed to Azure, all instances of it will be running behind Azure’s Network Load Balancer. Naturally each of these instances will have their own unique addresses, so we need to relax WCF’s enforcement of the address field to allow an incoming message to be received with any address. I accomplish this by adding a ServiceBehavior attribute that sets the AddressFilterMode to Any as follows:

    [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
    public class ListService : IListService
    {
	… class implementation unchanged …
    }

Modify the SVC. Now let's turn our attention to the Web Role implementation. The ListServiceWeb project, upon creation, will have a Service1.svc, Service1.svc.cs, and an IService1.cs. Since our service is already defined in a library, we need to add a reference to the ListService project and then need to keep Service1.svc, deleting the other two files. I'll then rename Service1.svc and the markup will be updated so that it references the ListingService type shown below:

<%@ ServiceHost Language="C#" Debug="true" Service="CloudListService.ListService"  %>

Verify input endpoints. We don’t need to make any other changes to the Web.config or the WebRole.cs, but we do need to verify the externally available, HTTP input endpoint for the service.  To do this, I'll look at the CloudListService project, expand the Roles folder, right-click the ListServiceWeb item, and select Properties. I then select the Endpoints tab to arrive at the dialog shown in Figure 4. By default, HTTP input endpoints are checked and assigned to port 80 so we are good to go there. The other item to verify is the .NET Trust Level, which is accessed by the Configuration tab. By default this is set to Full trust, and for most WCF services this is what you will want to keep it at.

Figure 4: Verifying the Input endpoints for the Web Role
Figure 4: Verifying the Input endpoints for the Web Role

Test the service locally. At this point I can simply press F5 to start our service. This will launch the Development Fabric—which is observed using the Windows Azure Simulation Monitor that appears in the system tray—and attach the Visual Studio debugger so we can step through the code. However, this will only let us run the Web Role—we still need to call the service in order to test it so we need to build a client. For our purposes, we can add a simple Console Application project to the solution and define the code within Program.cs, as Figure 5 shows. I chose to manually build our proxy to the service in code using the ChannelFactory, because we want to easily switch between calling the Web Role via HTTP or the Worker Role (described later) via TCP, and because we could easily add a reference to the ListService library itself. A typical alternative to this approach (once you have an instance of the Web Role running, either on the Development Fabric or in Azure itself) is to use the Add Service Reference feature in Visual Studio or svcutil to create the proxy type used by your client application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace ConsoleClient
{
    class Program
    {
    static void Main(string[] args)
    {
        Console.WriteLine("What is the uri to the service? ");
        string serviceUri = Console.ReadLine();

    Binding binding = null;

    if (serviceUri.StartsWith("http:"))
        {
        binding = new BasicHttpBinding(BasicHttpSecurityMode.None) { HostNameComparisonMode = HostNameComparisonMode.Exact }; 
        }
        else
        {
        binding = new NetTcpBinding(SecurityMode.None);
        }

    CloudListService.IListService client =
        ChannelFactory.CreateChannel(
            binding,
            new EndpointAddress(serviceUri)
            );

    Console.WriteLine("Getting items...");

    try
        {
        List result = client.GetItems(5);
        foreach (var s in result)
        {
            Console.WriteLine(s);
        }
        }
        catch (Exception ex)
        {
        Console.WriteLine(ex);
        }
            
    Console.ReadLine();
    }
    }
}

Observe that when calling the HTTP endpoint, we configure the HostNameComparisonMode to Exact—this is required for clients of Web Roles. Forgetting to set this is another not-so-obvious gotcha. When run, the test client’s output is:

What is the uri to the service?
http://localhost:81/ListService.svc
Getting items...
Item Number 0
Item Number 1
Item Number 2
Item Number 3
Item Number 4

Deploying Your First Service

Now that we’ve tested the service locally, it’s time to deploy the service to Windows Azure. One thing to note is that there are subtle differences between the Development Environment and the real Azure fabric, so you should never assume that because it works well locally it will work once deployed. A great example of this is using a Worker Role to host the service using an HTTP endpoint. It will test out fine while running locally, but the client will fail to communicate with it once deployed to Windows Azure because it is currently not supported.

Create a Hosted Service for the roles. Assuming you’ve already got your Windows Azure account set up, you may still need to create a Hosted Service.  You do this through the Windows Azure Portal, which you can reach from Visual Studio by right-clicking the CloudListService project and selecting Browse to Portal. Once in the portal, use the New Service wizard to create a Hosted Service for your deployment. The wizard will guide you through setting a display name, description, public hosted URL, and hosting affinity. Once that’s created, you should see your new service listed, as represented by the label Demo in Figure 6. You’ll want to leave this browser window open, as we’ll return to it shortly to configure the credentials Visual Studio will use to deploy your projects.

Figure 6: Windows Azure Portal with a single hosted service
Figure 6: Windows Azure Portal with a single hosted service

Configure the API Certificate. Returning to Visual Studio, right-click the CloudListService project and select Publish to kick off the deployment. The first time you do this, Figure 7 will appear with nothing selected under Credentials—you will need to configure the credentials Visual Studio presents in order to work with your Windows Azure Hosted Service via the API.

Figure 7: Preparing to deploy to Windows Azure
Figure 7: Preparing to deploy to Windows Azure

The credentials are certificates. Selecting the option will bring up the dialog shown in Figure 8 that allows you to create and name the certificate that Visual Studio will present to the Windows Azure API.

Figure 8: Creating the Windows Azure API certificate for use by Visual Studio
Figure 8: Creating the Windows Azure API certificate for use by Visual Studio

Within the Cloud Service Management Authentication dialog, click on the drop-down list below the label, “1. Create or select an existing certificate for authentication.” This will provide you with a option. This presents a subsequent dialog that only lets you set the Friendly Name of the certificate.

This will create the certificate, but Windows Azure still needs to be informed about it. As Figure 8 shows, you can click the link to copy the path to the .CER file. Then you should return to the Windows Azure portal. Here you need to click on the Account button, and then the Manage My API Certificates link. This will present the dialog shown in Figure 9. You’ll want to click the Browse… button and in the file chooser window, paste the path copied, click OK, and click Upload.

Figure 9: Configuring Windows Azure API Certificates
Figure 9: Configuring Windows Azure API Certificates

Next, you want to return to the Account page and from the bottom of the center panel, copy the Subscription ID value. Paste this value back into the text box below step 3 on the Cloud Service Management Authentication dialog open in Visual Studio.  Finally, give these credentials a friendly name and click OK.  Your copy of Visual Studio is now configured to properly authenticate with your Azure account, and future Cloud Service Projects (even in separate solutions) will not need to repeat these steps.

Complete the deployment. After completing the Configure Service Management Authentication dialog, you will be returned to the Publish Cloud Service dialog, Figure 7, with your newly created credentials selected and the Hosted Service Slot filled in. You will want to select your hosted service and slot (Production or Staging). Select Production if you want to access your service using the user friendly URL you entered when you originally created your Hosted Service. Finally, click the Enable IntelliTrace for .NET 4 roles (we’ll return to what this does shortly) and click OK to begin the deployment. This will bring up the Windows Azure Activity Log (see Figure 10), which you can use to monitor in process deployments as well as the results of completed deployments. As these deployments currently take a while (on my machine these took about 15 minutes), you’ll be frequently keeping tabs on the progress using this window.

Figure 10: Monitoring deployment status
Figure 10: Monitoring deployment status

Test the Azure hosted service. Now that you’ve deployed your WCF Web Role to Windows Azure, you can test with the same client we used locally, simply substituting in the URL for the service on Windows Azure. In my example above, the address was "http://tejadanet.cloudapp.net/ListService."

Hosting the ListService with a Worker Role

What if we want to expose a TCP endpoint for the ListService? In this case, we need to use a Worker Role, which is a similar process to what we followed for a WCF Web Role. The Worker Role is effectively a process for hosting an instance of your ServiceHost. In the Solution Explorer viewing our existing solution, we can right-click the CloudListService project and select New Worker Role Project. We name this role ListServiceWorker. This will create a new project that contains an app.config and WorkerRole.cs. This project is also associated with the CloudListService project, as shown in Roles folder displayed in Figure 4.

We rename the class to ListServiceWorkerRole, and update the body of the Run method so that it creates and opens an instance of our ServiceHost prior to entering its wait loop. Figure 11 shows the changes we made to Run and the definition of our StartListService method–the rest of the file is unchanged from the template’s default. The key point to observe in the StartListService is how we construct the endpoint address of the service from the ListService endpoint of the CurrentRoleInstance. This provides the internal IP address and port of the service instance. This address is the one that the Network Load Balancer will talk to, but that is not used directly by clients.

public override void Run()
{
    Trace.WriteLine("ListServiceWorker entry point called", "Information");

StartListService();

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

private void StartListService()
{
    serviceHost = new ServiceHost(typeof(ListService));

this.serviceHost.Faulted += (sender, e) =>
    {
    Trace.TraceError("Host fault occurred. Aborting the host.");
    this.serviceHost.Abort();
    };

NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);

string serviceAddress = string.Format("net.tcp://{0}/ListService", RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["ListService"].IPEndpoint);   
   serviceHost.AddServiceEndpoint(
    typeof(IListService),
    binding,
    serviceAddress
    );

    
    try
    {
    serviceHost.Open();
    Trace.TraceInformation("ListService host started successfully.");
    foreach (var ep in serviceHost.Description.Endpoints)
    {
        Trace.TraceInformation("Listening on: " + ep.ListenUri.ToString());
    }

}
    catch (TimeoutException timeoutException)
    {
    Trace.TraceError("The service operation timed out. {0}", timeoutException.Message);

}
    catch (CommunicationException communicationException)
    {
    Trace.TraceError("Could not start the ListService host. {0}", communicationException.Message);
    }
}

To configure this endpoint, as we did for the Web Role, we return to the CloudListService project. This time, however, we right-click and choose properties on the ListServiceWorker entry under the Roles folder as Figure 4 shows. When you select the Endpoints tab, we see a slightly different view than we did for the Web Role. I clicked Add Endpoint and changed the Name to ListService, left the Type as Input, the Protocol as TCP, and changed the port to 4040.

With those changes in place, we can press F5 to run it locally and point our test client to the local address (e.g., "http://localhost:81/ListService.svc"), or we can deploy it and use the address exposed by Windows Azure (e.g., "http://tejadanet.cloudapp.net/ListService.svc").

Debugging the Deployed Service with IntelliTrace

Once you have deployed your service, be it Web or Worker Role, you forego the ability to debug using Visual Studio’s debugger. You can, however, collect IntelliTrace logs, which let you navigate threads, call stacks, trace statements, and certain variable values from a static log file with an experience similar to debugging an actual process. When we configured our deployment (see Figure 7), we enabled this feature. IntelliTrace logs are collected on the local storage of the instance of the VM running your Web or Worker role. IntelliTrace logs do not utilize Windows Azure Storage, so you don’t have to set that up prior to enabling IntelliTrace logging. They can be written in a circular buffer format, where you can choose whether to start overwriting old entries when the file exceeds a limit you configure. In order to view the IntelliTrace logs for an instance, bring up Server Explorer in Visual Studio, and navigate to the instance under the Windows Azure Compute node, right-click it, and select View IntelliTrace logs. This will download the logs to your local machine and open them in Visual Studio.

This article covered the gamut of steps taken when building a WCF Service to host in Windows Azure, from development to deployment to debugging, and hopefully provides a heads up of a few of the things that go bump in the night and how to avoid them. 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish