ASP.NET + IIS + DNS Records = Sub Domain on the fly
Whenever any web portal provide hosted service, at that time publishing it through sub domain is the most convenient and preferable way. There are many such free hosted services are available on the internet. For e.g. blogger.com and wordpress.com for blog services. If you have noticed, in such portal, whenever user register for a service at that time it ask for a preferable sub domain, and upon choosing sub domain, service start instantly without waiting for DNS propagation compared to normal sub domain creation scenario.
Few days back one of my friend asked me how to achieve same with ASP.NET. So here we will see how we can achieve same with ASP.NET, IIS, and DNS tweaks. However this post is less about ASP.NET and more about IIS and DNS configuration except IIS Metabase entry automation through ASP.NET. Whole tutorial is divided in three parts: IIS setup, DNS Setup, and IIS Automation through ASP.NET
I have tested all below setup with IIS 7 on windows 7 and Windows Server 2008 Standard Edition.
IIS Setup
1) Create one website named domain.com in IIS and edit website binding to include domain.com and www.domain.com as host header.
This website will act as public site through which user can register and manage their account and services.
2) Create one more website named service.domain.com and host actual service application on this website. For e.g. if you are providing hosted blog service then host your blog application on this website.
DNS Setup
So far we are done with IIS setup. Now we need to configure DNS record. Open your domain control panel and go to DNS manager.
1) Add following A records for domain.com and www.domain.com which point to IIS server IP address.
domain.com IN A XXX.XXX.XXX.XXX www.domain.com IN A XXX.XXX.XXX.XXX service.domain.com IN A XXX.XXX.XXX.XXX
First two entries will ensure that whenever user browse domain.com or www.domain.com at that time it will be routed to IIS server and rest will be take care by IIS and it will serve public site.
Third entry in above is necessary for next step.
2) Now add following wildcard CNAME record.
*.domain.com IN CNAME service.domain.com
Above wildcard entry ensure that any sub domain request for domain.com will be routed to server which is pointed by service.domain.com.
So we have setup DNS records to route any sub domain request to IIS server. But still we have not done :) Now try to browse any sub domain let say user1.domain.com it will shows an error “The connection was reset” instead “Server not found”. It means our sub domain requests are routed to IIS server but IIS does not know about requested domain.
In step 1 of IIS setup, we have edited website binding to include domain.com and www.domain.com as host header, by this way we can tell IIS that any request from domain.com and www.domain.com will be handled by this particular website. So whenever user browse domain.com or www.domain.com it is serving public website without any problem. While in case of sub domain it is showing error that “The connection was reset” this is because IIS does not found host header entry for requested domain in any websites.
So we need to add host header entry in service.domain.com website created in step 2 of IIS setup because service.domain.com is actual website which is going to serve hosted service application when it is browse from sub domain.
Below is the code to add host header entry programmatically in IIS. I could not find right permission for application pool so I have set LocalSystem as an Application Pool identity. However in real time deployment, we can also create console application for below code which can be invoked from ASP.NET.
private string GetWebSiteId(string serverName, string websiteName)
{
string result = "-1";
DirectoryEntry w3svc = new DirectoryEntry(string.Format("IIS://{0}/w3svc", serverName));
foreach (DirectoryEntry site in w3svc.Children)
{
if (site.Properties["ServerComment"] != null)
{
if (site.Properties["ServerComment"].Value != null)
{
if (string.Compare(site.Properties["ServerComment"].Value.ToString(),
websiteName, true) == 0)
{
result = site.Name;
break;
}
}
}
}
return result;
}
private void AddHostHeader(string hostHeader, string websiteID)
{
DirectoryEntry site = new DirectoryEntry("IIS://localhost/w3svc/" + websiteID);
PropertyValueCollection serverBindings = site.Properties["ServerBindings"];
serverBindings.Add(hostHeader);
Object[] newList = new Object[serverBindings.Count];
serverBindings.CopyTo(newList, 0);
site.Properties["ServerBindings"].Value = newList;
site.CommitChanges();
}
AddHostHeader("127.0.0.1:80:user1.domain.com", GetWebSiteId("localhost", "service.domain.com"));
Put it all together
Let take quick review of how this all work together. So whenever any new user is registered at that time it will choose sub domain and application will add host header entry in IIS for chosen sub domain. Afterwards whenever that sub domain is requested, it will be routed to IIS with the help of DNS records and IIS will forward that request to service.domain.com website because there is a host header entry for requested sub domain. And service.domain.com website will identify user based on requested domain with the help of Request.Url.Host and it will load appropriate settings i.e. theme, module, etc.
Bad sub domain request and 404 handling
It may happen someone may try to access sub domain which is not registered with any user. In this case it will shows an error that "The connection was reset" because there is no host header entry for requested sub domain. We can tweak IIS to shows 404 not found page.
Create one more website and give any proper name to it. Now edit website binding to include empty host name as displayed in below image. This will instruct IIS that if IIS could not find host header entry in any websites then that request should be routed to this website.
Testing all above together on local machine
I am sure after reading this tutorial, one big question from many readers would be how to test this on single local machine without any live domain or DNS setup in LAN. Yes we can do! We can add entry in %systemroot%\System32\drivers\etc\hosts file however we cannot specify A record and CNAME records in this file. But still we can add following entry in hosts file so we can test this on local machine.
127.0.0.1 domain.com 127.0.0.1 www.domain.com # Add each sub domain entry manually below for testing purpose 127.0.0.1 user1.domain.com 127.0.0.1 user2.domain.com
For each sub domain we need to add manually in this file because there is no wildcard support in hosts file.
Hope this would be helpful.