Utilizing .NET Services in Windows Azure
- This might help out some people on the forums. It is cross posted from my blog: http://blog.philiprichardson.org/2009/05/23/utilizing-the-service-bus-in-windows-azure/
I've split it out into three parts here (because of forum post size limits).
Philip Richardson [MSFT]
Lead Program Manager | .NET Services
-----------------------
Utilizing the Service Bus in Windows Azure
In this basic tutorial we’ll focus on two basic scenarios:
1. Service Bus Client in a Windows Azure Web or Worker Role
2. Service Bus Listener in a Windows Azure Worker Role
Pre-Requisites
You’ll need to do some basic setup before you start coding. This tutorial assumes you have accounts for Windows Azure and .NET Services, have completed ‘Hello World’ style tutorials and have downloaded and installed all the relevant SDKs.
Full Trust Code
.NET Services required Full Trust to run. You’ll need to set the ‘enableNativeCodeExecution’ attribute to be true in the ServiceDefinition.csdef file of your Windows Azure Project.
<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="AzureSample" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
<WebRole name="WebRole" enableNativeCodeExecution="true">
<InputEndpoints>
<!-- Must use port 80 for http and port 443 for https when running in the cloud -->
<InputEndpoint name="HttpIn" protocol="http" port="80" />
</InputEndpoints>
</WebRole>
<WorkerRole name="WorkerRole" enableNativeCodeExecution="true">
</WorkerRole>
</ServiceDefinition>
Service Bus Client
In this example we’ll create a normal Service Bus Listener and run that on-premise. This Listener will receive messages from the Service Bus and display them in a console window. We’ll also create a small client library which can be run from a Windows Azure Web or Worker role.
Our scenario is a simple one: Imagine you wanted to get real time logging information from your Windows Azure application. Instead of writing to the Windows Azure log API, manually transferring the logs to a blob and then download the data – we’ll write out directly to the Service Bus.
The Listener
This is a super simple Console Application consisting of 3 files: Program.cs, ILogContract and ILogService. This Listener differs slightly from a regular Service Bus application. It packages Microsoft.ServiceBus.dll directly in its bin (ie. It doesn’t care if you have the SDK or Redist installed) and it sets all the WCF/SB config in code. Becareful: By packaging Microsoft.ServiceBus.dll yourself you are now responsible for servicing (ie. Updating) this dll on any clients you distribute it to. You’ll also note that the User Name, Password and Service Bus URI have been stored in AppSettings.
ILogContract.cs
using System;
using System.ServiceModel;
using System.Collections.Generic;
namespace AzureSample
{
[ServiceContract(Name = "ILogContract", Namespace = "http://tempuri.org/")]
public interface ILogContract
{
[OperationContract]
void WriteToLog(DateTime eventDt, string source, string text);
}
public interface ILogChannel : ILogContract, IClientChannel { }
LogService.cs
using System;
using System.ServiceModel;
using System.Text;
namespace AzureSample
{
[ServiceBehavior(Name = "LogService", Namespace = "http://tempuri.org/")]
class LogService: ILogContract
{
public void WriteToLog(DateTime eventDt, string source, string text)
{
Console.WriteLine(string.Format("From {0} at {1}: {2}", eventDt.ToString(), source, text));
}
}
}
Program.cs
using System;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Description;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Description;
namespace AzureSample
{
class Program
{
static void Main(string[] args)
{
string endPoint = ConfigurationSettings.AppSettings["EndPoint"];
string userName = ConfigurationSettings.AppSettings["UserName"];
string password = ConfigurationSettings.AppSettings["Password"];
Uri uri = new Uri(endPoint);
TransportClientEndpointBehavior userNamePasswordServiceBusCredential = new TransportClientEndpointBehavior();
userNamePasswordServiceBusCredential.CredentialType = TransportClientCredentialType.UserNamePassword;
userNamePasswordServiceBusCredential.Credentials.UserName.UserName = userName;
userNamePasswordServiceBusCredential.Credentials.UserName.Password = password;
ServiceHost host = new ServiceHost(typeof(LogService), uri);
ContractDescription contractDescription = ContractDescription.GetContract(typeof(ILogContract), typeof(LogService));
ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(uri);
serviceEndPoint.Binding = new NetTcpRelayBinding();
serviceEndPoint.Behaviors.Add(userNamePasswordServiceBusCredential);
host.Description.Endpoints.Add(serviceEndPoint);
host.Open();
Console.WriteLine(String.Format("Listening at: {0}", endPoint));
Console.WriteLine("Press [Enter] to exit");
Console.ReadLine();
host.Close();
}
}
}
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="UserName" value="MY SOLUTION NAME"/>
<add key="Password" value="MY PASSWORD"/>
<add key="EndPoint" value="sb://mysolutionname.servicebus.windows.net/sample/log/"/>
</appSettings>
</configuration>
This posting is provided "AS IS" with no warranties, and confers no rights.- 已编辑Philip Richardson [MSFT]版主2009年5月23日 5:07
全部回复
- Part II: The Client in Windows Azure
The Client
Now we need to start sending some messages to the client. We’ll create a class which we can easily re-use in a Web or Worker project. In fact you could run this class on pretty much any remote hosting environment. You’ll also need to store some credentials and the URI in the web.config or app.config for a web or worker role respectively.
ServiceBusLoggerClass.cs
This class can be added as an external DLL or directly to the Worker/Web Role. Remember it needs Microsoft.ServiceBus.dll to work.
using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using Microsoft.ServiceBus;
namespace AzureSample
{
public class ServiceBusLogger
{
public ILogChannel Channel {get;set;}
public ServiceBusLogger(string endPoint, string userName, string password)
{
Uri uri = new Uri(endPoint);
TransportClientEndpointBehavior userNamePasswordServiceBusCredential = new TransportClientEndpointBehavior();
userNamePasswordServiceBusCredential.CredentialType = TransportClientCredentialType.UserNamePassword;
userNamePasswordServiceBusCredential.Credentials.UserName.UserName = userName;
userNamePasswordServiceBusCredential.Credentials.UserName.Password = password;
ChannelFactory<ILogChannel> channelFactory = new ChannelFactory<ILogChannel>();
channelFactory.Endpoint.Address = new EndpointAddress(uri);
channelFactory.Endpoint.Binding = new NetTcpRelayBinding();
channelFactory.Endpoint.Contract.ContractType = typeof(ILogChannel);
channelFactory.Endpoint.Behaviors.Add(userNamePasswordServiceBusCredential);
this.Channel = channelFactory.CreateChannel();
}
}
}
Calling the Logger – eg. A Worker Role
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Text;
using Microsoft.ServiceHosting.ServiceRuntime;
using System.Configuration;
using AzureSample;
namespace AzureSample_WorkerRole
{
public class WorkerRole : RoleEntryPoint
{
public override void Start()
{
string endPoint = ConfigurationSettings.AppSettings["EndPoint"];
string userName = ConfigurationSettings.AppSettings["UserName"];
string password = ConfigurationSettings.AppSettings["Password"];
ServiceBusLogger logger = new ServiceBusLogger(endPoint, userName, password);
logger.Channel.WriteToLog(DateTime.UtcNow, "Worker Role", "Role Started");
while (true)
{
Thread.Sleep(1000);
logger.Channel.WriteToLog(DateTime.UtcNow, "Worker Role", "Working...");
}
}
public override RoleStatus GetHealthStatus()
{
// This is a sample worker implementation. Replace with your logic.
return RoleStatus.Healthy;
}
}
}
SampleOutput
Here is some sample output from the console:
Listening at: sb://*******.servicebus.windows.net/sample/log/
Press [Enter] to exit
From 5/23/2009 4:30:44 AM at Worker Role: Role Started
From 5/23/2009 4:30:51 AM at Worker Role: Working...
From 5/23/2009 4:30:52 AM at Worker Role: Working...
From 5/23/2009 4:30:53 AM at Worker Role: Working...
From 5/23/2009 4:30:54 AM at Worker Role: Working...
From 5/23/2009 4:30:55 AM at Worker Role: Working...
This posting is provided "AS IS" with no warranties, and confers no rights. - Part III: Hosting a Listener in a Windows Azure Worker Role
Hosting a Listener in Worker Role
It’s relatively straight forward to host a Service Bus Listener in a Worker Role. Here is a version of the Calculator Service running inside Worker Role.
CalculatorService.c s
Here is the CalculatorService.cs from the .NET Services SDK to refresh your memory. I’ve added this class directly to my worker role. I’ve remove the Access Control helpers from this sample for simplicity’s sake.
namespace Microsoft.ServiceBus.Samples
{
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
// Define a service contract.
[ServiceContract(Name = "Calculator", Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
[OperationContract(Action = "Add", ReplyAction = "AddResponse")]
double Add(double n1, double n2);
[OperationContract(Action = "Subtract", ReplyAction = "SubtractResponse")]
double Subtract(double n1, double n2);
[OperationContract(Action = "Multiply", ReplyAction = "MultiplyResponse")]
double Multiply(double n1, double n2);
[OperationContract(Action = "Divide", ReplyAction = "DivideResponse")]
double Divide(double n1, double n2);
}
// Service class which implements the service contract.
public class CalculatorService : ICalculator
{
public double Add(double n1, double n2)
{
return n1 + n2;
}
public double Subtract(double n1, double n2)
{
return n1 - n2;
}
public double Multiply(double n1, double n2)
{
return n1 * n2;
}
public double Divide(double n1, double n2)
{
return n1 / n2;
}
}
}
WorkerRole.cs
Here is my WorkerRole.cs. I’ve configured my Service Bus listener with code only (with App Settings for the URI and credentials only). I’ve also physically included the Microsoft.ServiceBus.dll in my project. You’ll also note I’ve over-ridden the Stop() method of RoleEntryPoint to close the Host when the Worker Role shuts down.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Text;
using Microsoft.ServiceHosting.ServiceRuntime;
using System.Configuration;
using Microsoft.ServiceBus.Samples;
using System.ServiceModel;
using System.ServiceModel.Description;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Description;
namespace AzureSample_WorkerRole
{
public class WorkerRole : RoleEntryPoint
{
private ServiceHost host;
public override void Start()
{
string endPoint = ConfigurationSettings.AppSettings["EndPoint"];
string userName = ConfigurationSettings.AppSettings["UserName"];
string password = ConfigurationSettings.AppSettings["Password"];
Uri uri = new Uri(endPoint);
TransportClientEndpointBehavior userNamePasswordServiceBusCredential = new TransportClientEndpointBehavior();
userNamePasswordServiceBusCredential.CredentialType = TransportClientCredentialType.UserNamePassword;
userNamePasswordServiceBusCredential.Credentials.UserName.UserName = userName;
userNamePasswordServiceBusCredential.Credentials.UserName.Password = password;
host = new ServiceHost(typeof(CalculatorService), uri);
ContractDescription contractDescription = ContractDescription.GetContract(typeof(ICalculator), typeof(CalculatorService));
ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(uri);
serviceEndPoint.Binding = new NetTcpRelayBinding();
serviceEndPoint.Behaviors.Add(userNamePasswordServiceBusCredential);
host.Description.Endpoints.Add(serviceEndPoint);
host.Open();
while (true)
{
//Loop
}
}
public override void Stop()
{
host.Close();
base.Stop();
}
public override RoleStatus GetHealthStatus()
{
// This is a sample worker implementation. Replace with your logic.
return RoleStatus.Healthy;
}
}
}
Advanced Scenarios
In these examples we used a basic NetTcpRelay Binding. You could easily imagine expanding these very simple scenarios to take advantage of other bindings, queues and routes provided by the Service Bus.
This posting is provided "AS IS" with no warranties, and confers no rights. Great info Philip, thanks. It's been very helpful.
Unfortunately, I'm having a problem deploying a worker role to Azure. That is, the worker-role-hosted-WCF service exposed by the Service Bus works fine in the dev fabric, but fails to run when deployed to the cloud. Also note that the project I'm working on is for Microsoft, for the purposes of demonstrating how to port a .NET application (StockTrader, in this case) to Azure. Here are the details:
Specifically, I am hosting one of three WCF services in a worker role using a Service Bus endpoint. The other two WCF services are hosted in web roles, and they run fine both in the dev fabric and in the real cloud. The WCF service hosted in the worker role, however, runs just fine in the dev fabric but sits forever in the Initializing state when deploying to Azure. As a sanity check, I built a HelloWorld WCF app, and I'm experiencing exactly the same thing: Worker role runs and hosts the WCF service via the Service Bus just fine in dev fabric, but hangs forever in Initializing state when deploying to Azure.
Deployment ID: de51e019844e4bbea4b3a702c9bb44fb
Web Site URL: http://stocktrader2.cloudapp.net/Here is the HelloWorld worker role code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;
using System.Text;using Microsoft.ServiceHosting.ServiceRuntime;
using System.ServiceModel;
using System.ServiceModel.Description;
using Microsoft.ServiceBus;
using WcfServiceImplementation;namespace WcfWorkerRole
{
public class WorkerRole : RoleEntryPoint
{
private ServiceHost _host;public override void Start()
{
RoleManager.WriteToLog("Information", "Host is starting");
this._host = new ServiceHost(typeof(Service1));
this._host.Open();ServiceRegistrySettings settings = new ServiceRegistrySettings();
settings.DiscoveryMode = DiscoveryType.Public;
foreach (ServiceEndpoint se in this._host.Description.Endpoints)
{
se.Behaviors.Add(settings);
}RoleManager.WriteToLog("Information", "Host is running");
}public override void Stop()
{
RoleManager.WriteToLog("Information", "Host is stopping");
this._host.Close();
base.Stop();
RoleManager.WriteToLog("Information", "Host is stopped");
}public override RoleStatus GetHealthStatus()
{
// This is a sample worker implementation. Replace with your logic.
return RoleStatus.Healthy;
}
}
}And here is the worker role's App.Config (Service Bus password replaced with *******)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfServiceImplementation.Service1">
<endpoint
address="http://stocktraderservices.servicebus.windows.net/service1"
binding="basicHttpRelayBinding"
contract="WcfContracts.IService1"
behaviorConfiguration="ServiceBusBehavior"
/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="ServiceBusBehavior">
<transportClientEndpointBehavior credentialType="UserNamePassword">
<clientCredentials>
<userNamePassword
userName="StockTraderServices" password="********" />
</clientCredentials>
</transportClientEndpointBehavior>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>The service contract is simple; just does an echo to prove that client and server are communicating properly. No need to include that code in the post, unless you request it. Again, as I said, it runs fine in the dev fabric.
I need to finish this project for Microsoft in the next day or two, and if necessary will need to document this problem with the deliverables, but I'd much rather get a resolution and have it working properly for MS. I've also posted this to the Azure forum at http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/328f9dc7-b27b-4324-8981-7145717f1fd3, but haven't gotten a resolution there yet. Please help! Thanks in advance.
Hi Lenni,
Are you missing the while(true) loop in your worker role start() method as in Philip's example?
It's interesting you are using the worker role to host the relay endpoint. Why have you not used a web role WCF service for this particular endpoint?
Paul
- Hi Paul,
I actually believe the while(true) loop in Philip's example is incorrect. A worker role is similar to a Windows service in the sense that there are separate Start and Stop methods called by some service control manager. So when the Start method completes, the host remains running until the Stop method is invoked when the service is explicity stopped. But I did try it with the while(true) loop anyway, just to be sure, and as I expected, I got "Service is busy" errors trying to call it.
You're 100% correct that a web role is preferred over a worker role to host a WCF service in Azure. This project is a demonstration of how to do different things in Azure, one of which is how to host a WCF service in a worker role.
I did get a reply to my other forum post at http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/328f9dc7-b27b-4324-8981-7145717f1fd3, which I'm going to try now, and will post the results as soon as I get them.
Thanks!
~ Lenni I've been thinking of worker roles in terms of periodic background processes and was thinking of a while loop with a Thread.Sleep(n) rather than an empty while(true){}.
I was under the impression that the Start method is more like a main method than a Windows service. The docs state that the Role is restarted if the Start method returns.
Though this doesn't explain why you are not having problems in the dev fabric....
Cheers
Paul
Quite right Paul. The endless loop seems to be needed to keep the Start method from returning. Though like you say it doesn't explain why it doesn't seem to be needed when running in the dev fabric.
Apparently, the real problem was the fact that I was configuring WCF using <system.serviceModel> settings in app.config (which btw also works fine in dev fabric but not in the cloud) rather than the code-only approach in C#. I've since modified the HelloWorld worker role service and web role client to configure the WCF settings using code-only, rather than app.config/web.config. I also made sure to package Microsoft.ServiceBus.dll in the bin folder, as per Philip Richardson's post. With no more settings in app.config, I am also able to continue running successfully in the dev fabric.
Now the Worker Role does in fact start when deployed to Azure, rather than getting stuck in the Initializing state like it was before. So it is clear that switching to code-only WCF configuration corrected the problem I reported. Enter new problem!
New problem is, when the service method is invoked by the client on the channel, it fails with either "404 not found" (if I implement the infinite while-true loop) or "An existing connection was forcibly closed by the remote host" (if I don't). Philip Richardson's post uses the infinite while-true loop, but I'm desperately trying everything possible to get it working in the cloud. And again, it works fine in dev fabric -- both with and without the infinite while-true loop.
With the infinite loop version, the worker role starts normally, and seems to remain in a Started state indefinitely. But as soon as the first client request is issued, the worker role crashes and a 404 gets thrown on the client. After that, the worker restarts and then crashes continuously.
Without the infinite loop, the worker role never remains in a Started state indefinitely, even if a client request is never issued. So I'm pretty sure that the inifinite loop is correct and required, as shown in Philip Richardson's post.
[WebException: The remote server returned an error: (404) Not Found.]
System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult) +728
System.ServiceModel.Channels.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result) +133[EndpointNotFoundException: There was no endpoint listening at http://stocktraderservices.servicebus.windows.net/service1 that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.]
System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +10259418
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +539
WcfContracts.IService1.GetData(Int32 value) +0
WcfWebRoleClient._Default.btnTest_Click(Object sender, EventArgs e) in C:\Projects\WcfWorkerRolePOC\WcfWebRoleClient\Default.aspx.cs:38
System.Web.UI.WebControls.Button.OnClick(EventArgs e) +90
System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +138
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +32
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3087Why is this happening?!?
I'm at my wits end on this, and just can't get it to work. Here's the code (no more app.config)
public override void Start()
{
string endPoint = "http://stocktraderservices.servicebus.windows.net/service1";
string userName = "StockTraderServices";
string password = "paradox";Uri uri = new Uri(endPoint);
var creds = new TransportClientEndpointBehavior();
creds.CredentialType = TransportClientCredentialType.UserNamePassword;
creds.Credentials.UserName.UserName = userName;
creds.Credentials.UserName.Password = password;_host = new ServiceHost(typeof(Service1), uri);
var contractDescription = ContractDescription.GetContract(typeof(IService1), typeof(Service1));
var serviceEndPoint = new ServiceEndpoint(contractDescription);
serviceEndPoint.Address = new EndpointAddress(uri);
serviceEndPoint.Binding = new BasicHttpRelayBinding();
serviceEndPoint.Behaviors.Add(creds);_host.Description.Endpoints.Add(serviceEndPoint);
_host.Open();// Worker role starts and runs fine, but upon first client request, then crashes and client gets a 404 exception
// Only in the cloud... not in dev fabric!
while (true)
{
}
}And here's the client code (a button click event in a web role aspx page):
protected void btnTest_Click(object sender, EventArgs e)
{
string endPoint = "http://stocktraderservices.servicebus.windows.net/service1";
string userName = "StockTraderServices";
string password = "paradox";Uri uri = new Uri(endPoint);
var creds = new TransportClientEndpointBehavior();
creds.CredentialType = TransportClientCredentialType.UserNamePassword;
creds.Credentials.UserName.UserName = userName;
creds.Credentials.UserName.Password = password;var cf = new ChannelFactory<IService1Channel>();
cf.Endpoint.Address = new EndpointAddress(uri);
cf.Endpoint.Binding = new BasicHttpRelayBinding();
cf.Endpoint.Contract.ContractType = typeof(IService1Channel);
cf.Endpoint.Behaviors.Add(creds);var channel = cf.CreateChannel();
var result = channel.GetData(5);
Response.Write(result + "<br />");
}Runs like a charm in dev fabric; crashes miserably when deployed to the cloud. Please help!

