We recently implemented the .Net Service bus to expose some in-house WCF services to the world wide world. It may be useful for me and others if I describe how to do this :) This setup allows you to switch between tcp and http relay binding with configuration.
The Host
For the in-house systems you need to create a host – you cant use IIS as the service bus requires a connection to be initiated by both ends of the communication. We created a Windows Service application that also runs as a console app. This is much simpler when developing and debugging. To create this service:
1) create a new windows service using the standard VS template and add a reference to Microsoft.ServiceBus (in addition to your service and data contracts which I hope are in separate assemblies!)
2) change the Application Output Type to Console Applications.
3) add some code to Program.cs to flick to console mode:
if (args.Length > 0 && args[0].ToLower() == "/console")
{
myService.RunConsole(args);
}
else
{
ServiceBase.Run(ServicesToRun);
}
4) in your service code add the following method:
internal void RunConsole(string[] args)
{
OnStart(args);
Console.WriteLine("My ServiceHost is running... Press Enter to stop the service");
Console.ReadLine();
OnStop();
}
Now for the service bus stuff.
5) in the service class, add a member for the service host:
ServiceHost _host = null;
6) in the OnStart method of your service add the following :
// create a behavior for the service bus – it need creds of some type
TransportClientEndpointBehavior behavior = new TransportClientEndpointBehavior();
behavior.CredentialType = TransportClientCredentialType.UserNamePassword;
behavior.Credentials.UserName.UserName = Properties.Settings.Default.ServiceBusSolutionName;
behavior.Credentials.UserName.Password = Properties.Settings.Default.ServiceBusPassword;
// create an address for the service bus. config allows a switch between tcp and http
Uri address;
if(Properties.Settings.Default.ServiceBusBinding == "nettcp")
{
address = ServiceBusEnvironment.CreateServiceUri("sb", Properties.Settings.Default.ServiceBusSolutionName, Properties.Settings.Default.ServiceBusServiceName);
}
else
if(Properties.Settings.Default.ServiceBusBinding == "basichttp")
{
address = ServiceBusEnvironment.CreateServiceUri("http", Properties.Settings.Default.ServiceBusSolutionName, Properties.Settings.Default.ServiceBusServiceName);
}
else
throw new ArgumentException("Invalid binding for Service Bus");
// create the host
_host = new ServiceHost(typeof(MyService), address);
// update all the end points with the new behavior – you may need more than one – or not – up to you
foreach (ServiceEndpoint endpoint in _host.Description.Endpoints)
{
endpoint.Behaviors.Add(behavior);
}
// open the portal to another dimension – a dimension of sight and sound
_host.Open();
Now for some config. For me this was the hardest part. Hard coding WCF config is much simpler!
7) create some service model bindings for netTcpRelayBinding & basicHttpRelayBinding
<bindings>
<netTcpRelayBinding>
<binding name="default" />
</netTcpRelayBinding>
<basicHttpRelayBinding>
<binding name="default">
<security mode="None" />
</binding>
</basicHttpRelayBinding>
</bindings>
8) add an endpoint for your service:
<endpoint name="MyServiceEndpoint"
address=""
binding="netTcpRelayBinding"
contract="MyApp.ServiceContracts.IMyService"
bindingConfiguration="default" />
9) if you want to use basic http then add this instead:
<endpoint name="MyServiceEndpoint"
address=http://mysolution.servicebus.windows.net/MyService
binding="basicHttpRelayBinding"
contract="MyApp.ServiceContracts.IMyService"
bindingConfiguration="default" />
And that’s about it for the service host. If you need to have metadata support (WSDL) then that’s a whole other story that I will try to blog about seperately.
The Client
Azure worker roles do not have a web or app .config you can use for WCF configuration settings. Web roles do have a web.config but this can only be changed by a redeploy of package so it’s not the best idea to put anything but service configuration in there. In my case, I needed to access the in-house services from both the web and worker roles so I created a class lib project with a single helper class.
1) create a Windows Class Library and add references for Microsoft.ServiceBus plus your service and data contracts.
2) Create a static class with a single static method – call it what you like but something like GetClientChannel() will do. Add the following code:
IMyChannel channel; // this is a simple combo interface of IChannel and IMyService
Uri address;
// create the behavior for SB creds
TransportClientEndpointBehavior behavior = new TransportClientEndpointBehavior();
behavior.CredentialType = TransportClientCredentialType.UserNamePassword;
// get the creds from the cloud config
behavior.Credentials.UserName.UserName = RoleManager.GetConfigurationSetting("ServiceBusSolutionName");
behavior.Credentials.UserName.Password = RoleManager.GetConfigurationSetting("ServiceBusPassword");
// create a channel factory
ChannelFactory<IMyChannel> channelFactory = new ChannelFactory<IMyChannel>();
// create the binding – config allows us to select http or tcp
if (RoleManager.GetConfigurationSetting("ServiceBusBinding").ToLower() == "basichttp")
{
channelFactory.Endpoint.Binding = new BasicHttpRelayBinding(EndToEndBasicHttpSecurityMode.None, RelayClientAuthenticationType.None);
address = ServiceBusEnvironment.CreateServiceUri("http", RoleManager.GetConfigurationSetting("ServiceBusSolutionName"), RoleManager.GetConfigurationSetting("ServiceBusServiceName"));
}
else if (RoleManager.GetConfigurationSetting("ServiceBusBinding").ToLower() == "nettcp")
{
channelFactory.Endpoint.Binding = new NetTcpRelayBinding(EndToEndSecurityMode.Transport, RelayClientAuthenticationType.None);
address = ServiceBusEnvironment.CreateServiceUri("sb", RoleManager.GetConfigurationSetting("ServiceBusSolutionName"), RoleManager.GetConfigurationSetting("ServiceBusServiceName"));
}
else
{
throw new ArgumentException("Invalid service bus binding configuration option: " + RoleManager.GetConfigurationSetting("ServiceBusBinding"));
}
// update the factory for A B & C
channelFactory.Endpoint.Address = new EndpointAddress(address);
channelFactory.Endpoint.Behaviors.Add(behavior);
channelFactory.Endpoint.Contract.ContractType = typeof(ICRMChannel);
channel = channelFactory.CreateChannel(new EndpointAddress(address));
channel.Open();
return channel;
Tada! All done. Provided I haven’t introduced any bugs then this should give you a connection to you in-house services, by passing any firewall crap that those pesky IT people seem to insist on.
Enjoy :)