Implementing a Multi-tenant Windows Azure Application
We are nearing the completion of our multi-tenant Azure incubation project. I blogged in March about our plans for the addressing scheme. Quite a bit has changed since then.
URL Rewriting Revisited
For many of our scenarios, URL Rewriting works very well. Since my last blog post, Azure has added the URL Rewriting module to the web role, so, for many scenarios, this is a great solution.
Where we ran into issues in our scenario is that we did not know the type or number of pages that could be created in advance. If a tenant creates a PageZZZ url, where do we route the request? In many scenarios, you know the set of pages in advance, so this isn’t an issue.
Enter the HTTP Handler
Our first attempt at handling this issue was to implement an HTTP Handler. We used the MVC pattern and the implementation was surprisingly straight forward and easy. The hardest part was getting the web.config exactly right.
Because of some other things we were doing, we ended up with quite a bit of code in global.asax. We also ran into a requirement to host URLs that didn’t have an extension. It became difficult to work around IIS’ “helpfulness” and we had a lot more code than it felt like we should.
HTTP Module to the Rescue
Having built a number of ISAPI extensions and filters, I was not looking forward to implementing an HTTP Module. Fortunately, IIS has come a long way and the module really wasn’t any harder than the handler.
The module cleaned up (ie removed) global.asax.cs. The module also gave us a very nice encapsulation and deployment story.
Later in the process, we decided that we wanted to instrument our code and log some results to Azure tables. The module made this easy to do as we didn’t have to modify any page level code. You could accomplish the same thing from global.asax, but it wouldn’t be as nicely encapsulated or as easy to reuse.
I have never been a huge MVC fan, but after implementing an HTTP Module using MVC, there are some problems that are much easier to solve and the code is much cleaner.
We are storing all of our data in Azure Tables (anxiously awaiting the next version of SDS). One of the key things our code does is break the URL into pieces (i.e. Tenant ID and Page ID). Once it does this, it checks an Azure table to determine if the page exists (remember, we don’t know the set of pages in advance) and if we have rights to the page. Most of us are used to case-insensitive URLs and Azure tables are case-sensitive. Another issue is that many pages have an extension (i.e. rss.xml). Azure doesn’t like special characters in the key or the search fields, so this needs to be handled. Obviously it’s not difficult, if you know the issues in advance.
We chose to store the Tenant ID and Page ID natively – in our case using proper case and allowing file extensions. The partition key is the escaped, lowercase of the Tenant ID and the row key is the same for the Page ID.
We embedded these rules in the SitePage class and created a constructor that takes Tenant ID and Page ID. We then use the partition key and row key as our parameters. The code looks something like this:
SitePage sp = new SitePage(“BartsWeb”, “rss.xml”) ValidatePage(sp.PartitionKey, sp.RowKey)
We decided on this approach as we use the entity classes in multiple layers of the app. This approach gives us good encapsulation and forces the user making the call to pass the right parameters. There may also be scenarios in the future where we don’t want to enforce these rules.
Mapping the Request
The HTTP Module also gives us the best of both worlds when handling a request. Depending on the type of request (retrieved from the Azure table), we can use URL Rewrite to send the request to an existing ASPX page or we can invoke one or the custom handlers to process the code.
Our original intent was to process all of the requests with custom handlers and this worked really well for read pages. When it came time to build forms for updating, the ASPX model really made development and debugging easier. Once debugged, if we choose, we can move the code into a custom handler and change the mapping in the table.
Performance of the HTTP Module
We had plans to implement caching for our June release, but decided to postpone the feature to a later release as our performance was more than adequate. I was amazed at how few lines of code it took to build a custom controller. The IIS team did a great job building a platform.
Packaging the Sample
I’m in the process of packaging a simple HTTP Module that handles a multi-tenant mapping strategy and implements custom asynchronous logging to Azure tables. We have a June 30 ship date, so it may take a couple of weeks for me to get the sample cleaned up and posted.