Hosting a public web site and an internal services site in one Windows Azure web role

Imagine you want to host some (Web) services in an IIS web site inside your Windows Azure compute web role. In addition, you also want to host your web application/portal in another web site in Full IIS in the *same* web role.

So, basically, the scenario looks like the following – where the endpoint of the web portal is an input endpoint which is available through the firewalls and the load balancer, but the endpoint of the services web site is an internal endpoint which is (and which should) only be reachable within our Azure application system.

image

Let’s take a look at a sample solution.
We have an Azure project, two web applications (for the portal and for the services) and a Contracts project which contains the WCF service and data contracts:

image


What we need to achieve is having two web sites configured in one web role our Azure project – one with the portal, one with the services. And we need two endpoints: one input (external) and one internal endpoint:

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="WCFinWebRoleInternalEndpoints" 
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebPortal">
    <Sites>
      <Site name="Web">
        <Bindings>
          <Binding name="WebPortal" endpointName="WebPortal" />          
        </Bindings>
      </Site>
      <Site name="Services" physicalDirectory="..\BackendServices">
        <Bindings>
          <Binding name="Services" endpointName="Services" />
        </Bindings>
      </Site>
    </Sites>
    
    <Endpoints>
      <InputEndpoint name="WebPortal" protocol="http" port="80" />
      <InternalEndpoint name="Services" protocol="http" />
    </Endpoints>
    
    <Imports>
      <Import moduleName="Diagnostics" />
    </Imports>
  </WebRole>
</ServiceDefinition>


OK. Now our services web site runs inside our web role VM on a port that is not publicly available.
If you look at the internal endpoint definition you can see there is no port definition. This is because Windows Azure will assign a port dynamically.

Well, turns out this is a bit of a problem if our web portal wants to talk to the WCF services in that other (internal) web site. Which address, which port to use to talk to on the client/consumer side?

Of course there is help. The RoleEnvironment API from Windows Azure is here to heal our wounds.
What we need is the address and the port that Azure has assigned to our abstract internal endpoint (which we named ‘Services’ in the above .csdef). With this information we simply create a new endpoint URL and assign it to a WCF ChannelFactory: Here we have some generic helper method which can look at the endpoint of a ChannelFactory and adjust the address for an internal Windows Azure endpoint:

using System;
using System.ServiceModel;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace Thinktecture.WindowsAzure
{
    public static class ChannelFactoryInternalEndpointExtensions
    {
        public static void AdjustEndpointAddress(this ChannelFactory channelFactory, 
string internalEndpointName) { var serviceEndpoint = RoleEnvironment.CurrentRoleInstance
.InstanceEndpoints [internalEndpointName].IPEndpoint; if (serviceEndpoint != null) { var serviceAddress = channelFactory.Endpoint.Address.Uri; var serviceUrl = new Uri(String.Format("{0}://{1}:{2}{3}", serviceAddress.Scheme, serviceEndpoint.Address, serviceEndpoint.Port, serviceAddress.AbsolutePath)); channelFactory.Endpoint.Address =
new EndpointAddress(serviceUrl); } } } }


With this tiny extension method in place the actual code for calling the WCF services hosted in the internal web site looks like the following
(illustrated for both, ‘Add Service Reference…’ and a pure shared contracts with ChannelFactory scenario::

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {  
        // ASR... approach:
        var client = new EchoServiceClient();
        client.ChannelFactory.AdjustEndpointAddress("Services");

        var result = client.Echo("Hello Cloud");
        labelResult.Text = result;


        // ChannelFactory and shared contracts approach:
        var channelFactory = new ChannelFactory<IEchoService>("*");
        channelFactory.AdjustEndpointAddress("Services");
        var clientChannel = channelFactory.CreateChannel();

        result = clientChannel.Echo("Hello Cloud");
        labelResult.Text = result;
    }        
}


Voila. Nice architectural cloud approach made real.

Hope this helps.