Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.
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.
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 typeTransportClientEndpointBehavior 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 httpUri 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 youforeach (ServiceEndpoint endpoint in _host.Description.Endpoints){ endpoint.Behaviors.Add(behavior);}// open the portal to another dimension – a dimension of sight and sound_host.Open();
// create a behavior for the service bus – it need creds of some typeTransportClientEndpointBehavior 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 httpUri 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 youforeach (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.
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 IMyServiceUri address; // create the behavior for SB credsTransportClientEndpointBehavior behavior = new TransportClientEndpointBehavior();behavior.CredentialType = TransportClientCredentialType.UserNamePassword; // get the creds from the cloud configbehavior.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;
IMyChannel channel; // this is a simple combo interface of IChannel and IMyServiceUri address;
// create the behavior for SB credsTransportClientEndpointBehavior behavior = new TransportClientEndpointBehavior();behavior.CredentialType = TransportClientCredentialType.UserNamePassword;
// get the creds from the cloud configbehavior.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);
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 :)