Hosting multiple websites in a web role

Microsoft released Microsoft Azure as a production service in February 2010. A common complaint was that it was too expensive to develop small websites because a web role could support only a single website. The cause of this limitation was that a web role hosted a website using a hosted web core rather than the full IIS.

With the Microsoft Azure SDK v1.3 release, Microsoft Azure added support for full IIS for web roles. This means that a single web role can host multiple websites. However, all of these websites share the same Virtual IP address, and a CNAME record must be used to map the domain name of the website to the servicename.cloudapp.net URL for the web role. Each website is then distinguished inside IIS by its distinct host header.

The Providing a custom domain name for a Cloud Service recipe shows how to use a CNAME record to map a custom domain to a Cloud Service domain. Note that full IIS is also available on worker roles.

Tip

The approach described in this recipe, as it is still valid, is probably not the best you can do with Azure. If you need to host multiple websites in a single role (and in a single unit of scale), the Microsoft Azure Websites could be the best solution to accomplish this. We will talk about Websites in a dedicated chapter, and it is the newest and probably the most advanced PaaS on the market.

The Sites element in the ServiceDefinition.csdef service definition file is used to configure multiple websites. This element contains one child Site element for each website hosted by the web role. Each Site element has two attributes: name, which distinguishes the configuration, and physicalDirectory, which specifies the physical directory for the website. Note that multiple websites can reference the same physical directory. Each Site element has a Bindings child element that contains a set of Binding child elements, each of which identifies an endpoint used by the website and the host header used to distinguish the website. Each endpoint must correspond to an input endpoint specified in the EndPoints declaration for the web role. It is possible to define virtual applications and virtual directories for a website, using the VirtualApplication and VirtualDirectory elements, respectively. This configuration is a subset of the standard IIS configuration.

The following example shows a fragment of a service definition file for a web role that hosts two websites:

<WebRole name="MultipleWebsites">
  <Sites>
    <Site name="WebsiteOne" physicalDirectory="..\Web">
      <Bindings>
        <Binding name="HttpIn" endpointName="HttpIn"hostHeader="www.websiteone.com" />
      </Bindings>
    </Site>
    <Site name="WebsiteTwo" physicalDirectory="..\Web">
      <VirtualApplication name="Payment"physicalDirectory="..\..\Payment">
        <VirtualDirectory name="Scripts"physicalDirectory="..\Web\Scripts" />
      </VirtualApplication>
      <Bindings>
        <Binding name="HttpIn" endpointName="HttpIn"hostHeader="www.websitetwo.com" />
        <Binding name="HttpsIn" endpointName="HttpsIn"hostHeader="www.websitetwo.com" />
      </Bindings>
    </Site>
  </Sites>
  <Endpoints>
    <InputEndpoint name="HttpIn" protocol="http"port="80" />
    <InputEndpoint name="HttpsIn" protocol="https"port="443" />
  </Endpoints>
  <ConfigurationSettings />
</WebRole>

This configuration specifies that the web role hosts two websites: www.websiteone.com and www.websitetwo.com. They share the same physical directory, but www.websitetwo.com also uses a virtual application with its own virtual directory. Both websites are accessible using HTTP, but www.websitetwo.com also exposes an HTTPS endpoint.

In this recipe, we'll learn how to host multiple websites in a single Microsoft Azure web role.

How to do it...

We are going to see how to implement the two websites in a Cloud Service. We do this as follows:

  1. Use Visual Studio to create an empty cloud project.
  2. Add a web role to the project (accept the default name of WebRole1).

The changes from steps 3 to 8 affect the ServiceDefinition.csdef service definition file:

  1. Set the name attribute of the Site element to WebSiteOne.
  2. Add a physicalDirectory attribute, with the "..\..\..\WebRole1" value, to the Site element.
  3. Add a hostHeader attribute, with the www.websiteone.com value, to the Binding element for the Site element.
  4. Copy the entire Site element and paste it under itself.
  5. Change the name attribute of the new Site element to WebsiteTwo.
  6. Change the hostHeader attribute of the new Site element to www.websitetwo.com.
  7. Add the following entries to the hosts file present in the %SystemRoot%\system32\drivers\etc folder:
    127.0.0.1   www.websiteone.com 
    127.0.0.1   www.websitetwo.com
  8. Build and run the Cloud Service.
  9. Change the URL in the browser to www.websiteone.com, and refresh the browser.
  10. Change the URL in the browser to www.websitetwo.com, and refresh the browser.

How it works...

On completing the steps, the WebRole element in the ServiceDefinition.csdef file should be as follows:

<WebRole name="WebRole1">
  <Sites>
    <Site name="WebsiteOne" physicalDirectory="..\..\..\WebRole1">
      <Bindings>
        <Binding name="Endpoint1" endpointName="Endpoint1"hostHeader="www.websiteone.com"/>
      </Bindings>
    </Site>
    <Site name="WebsiteTwo" physicalDirectory="..\..\..\WebRole1">
      <Bindings>
        <Binding name="Endpoint1" endpointName="Endpoint1"hostHeader="www.websitetwo.com"/>
      </Bindings>
    </Site>
  </Sites>
  <Endpoints>
    <InputEndpoint name="Endpoint1"protocol="http" port="80" />
  </Endpoints>
  <Imports>
    <Import moduleName="Diagnostics" />
  </Imports>
</WebRole>

In steps 1 and 2, we created a Cloud project with a web role.

In steps 3 and 4, we configured the Site element for the first website. In step 3, we provide a distinct name for the element, and in step 4, we specified the physical directory for the website.

In step 5, we configured the Binding element for the Site element by specifying the host header we use to distinguish the website.

In step 6, we created the Site element for the second website. In steps 7 and 8, we completed the configuration of the second website by providing a name for its configuration and specifying the host header we use to distinguish the website. Note that in this example, we used the same physical directory for both websites.

In step 9, we modified the hosts file so that we can use the configured host headers as URLs.

We built and ran the Cloud Service in step 10. We will encounter an error in the browser as there is no default website at 127.0.0.1:81 (or whichever port the Microsoft Azure Compute Emulator has assigned to the Cloud Service). In steps 11 and 12, we confirmed this by replacing 127.0.0.1 in the browser URL with the URLs we configured as host headers for the two websites.

Tip

Note that although we only created two websites in this example, we could have configured additional websites.

There's more...

When we use this Cloud Service, we must use CNAME records to map the two domains to the ourservice.cloudapp.net URL of our Cloud Service. Just as we cannot access the Cloud Service locally at 127.0.0.1, we cannot access the Cloud Service at ourservice.cloudapp.net. We will see how to use CNAME to do this mapping in the Providing a custom domain name for a Cloud Service recipe.

See also

Have a look at the following MSDN links and blog posts to get additional information: