URL Rewrite – Multiple domains under one site. Part II

I believe I have it …

I’ve been meaning to put together the ultimate outgoing rule for hosting multiple domains under one site.  I finally sat down this week and setup a few test cases, and created one rule to rule them all. 

In Part I of this two part series, I covered the incoming rule necessary to host a site in a subfolder of a website, while making it appear as if it’s in the root of the site.  Part II won’t work without applying Part I first, so if you haven’t read it, I encourage you to read it now.

However, the incoming rule by itself doesn’t address everything.  Here’s the problem …

Let’s say that we host www.site2.com in a subfolder called site2, off of masterdomain.com.  This is the same example I used in Part I.

 

Using an incoming rewrite rule, we are able to make a request to www.site2.com even though the site is really in the /site2 folder. 

The gotcha comes with any type of path that ASP.NET generates (I’m sure other scripting technologies could do the same too).  ASP.NET thinks that the path to the root of the site is /site2, but the URL is /.  See the issue?  If ASP.NET generates a path or a redirect for us, it will always add /site2 to the URL.  That results in a path that looks something like www.site2.com/site2

In Part I, I mentioned that you should add a condition where “{PATH_INFO} ‘does not match’ /site2”.  That allows www.site2.com/site2 and www.site2.com to both function the same.  This allows the site to always work, but if you want to hide /site2 in the URL, you need to take it one step further.

One way to address this is in your code.  Ultimately this is the best bet.  Ruslan Yakushev has a great article on a few considerations that you can address in code.  I recommend giving that serious consideration.  Additionally, if you have upgraded to ASP.NET 3.5 SP1 or greater, it takes care of some of the references automatically for you.

However, what if you inherit an existing application?  Or you can’t easily go through your existing site and make the code changes?  If this applies to you, read on.

That’s where URL Rewrite 2.0 comes in.  With URL Rewrite 2.0, you can create an outgoing rule that will remove the /site2 before the page is sent back to the user.  This means that you can take an existing application, host it in a subfolder of your site, and ensure that the URL never reveals that it’s in a subfolder.

Performance Considerations

Performance overhead is something to be mindful of.  These outbound rules aren’t simply changing the server variables.  The first rule I’ll cover below needs to parse the HTML body and pull out the path (i.e. /site2) on the way through.  This will add overhead, possibly significant if you have large pages and a busy site.  In other words, your mileage may vary and you may need to test to see the impact that these rules have.  Don’t worry too much though.  For many sites, the performance impact is negligible.

So, how do we do it?

Creating the Outgoing Rule

There are really two things to keep in mind. 

First, ASP.NET applications frequently generate a URL that adds the /site2 back into the URL.  In addition to URLs, they can be in form elements, img elements and the like.  The goal is to find all of those situations and rewrite it on the way out.  Let’s call this the ‘URL problem’.

Second, and similarly, ASP.NET can send a LOCATION redirect that causes a redirect back to another page.  Again, ASP.NET isn’t aware of the different URL and it will add the /site2 to the redirect.  Form Authentication is a good example on when this occurs.  Try to password protect a site running from a subfolder using forms auth and you’ll quickly find that the URL becomes www.site2.com/site2 again.  Let’s term this the ‘redirect problem’.

Solving the URL Problem – Outgoing Rule #1

Let’s create a rule that removes the /site2 from any URL.  We want to remove it from relative URLs like /site2/something, or absolute URLs like http://www.site2.com/site2/something.  Most URLs that ASP.NET creates will be relative URLs, but I figure that there may be some applications that piece together a full URL, so we might as well expect that situation.

Additionally, we don’t want this to apply to non-code pages like images, binaries, .axd files.

Let’s get started.  First, create a new outbound rule.  You can create the rule within the /site2 folder which will reduce the performance impact of the rule.  Just a reminder that incoming rules for this situation won’t work in a subfolder … but outgoing rules will.

image

Give it a name that makes sense to you, for example “Outgoing – URL paths”.

It’s important that you create a precondition so that this only applies to text based pages, like .aspx.  Running this on binary images will add needless server overhead and running this rule on some .axd files will cause errors when viewing your site.

Additionally, if you place the rule in the subfolder, it will only run for that site and folder, so there isn’t need for a HTTP_HOST precondition.  Run it for all requests.  If you place it in the root of the site, you may want to create a precondition for HTTP_HOST = ^(www\.)?site2\.com$.

To create the filter for just .aspx pages, select <Create New Precondition…> from the Precondition dropdown.  Give it a name like “.aspx pages only” and add a Condition with an input of {SCRIPT_NAME} and Pattern of “\.aspx$”.

image

Save the Add Condition and Add Precondition dialogue boxes until you’re at the Edit Outbound Rule form again.

For the Match section, there are a few things to consider.  For performance reasons, it’s best to match the least amount of elements that you need to accomplish the task.  For my test cases, I just needed to rewrite the <a /> tag, but you may need to rewrite any number of HTML elements.  Note that as long as you have the exclude /site2 rule in your incoming rule as I described in Part I, some elements that don’t show their URL—like your images—will work without removing the /site2 from them.  That reduces the processing needed for this rule.

Leave the “matching scope” at “Response” and choose the elements that you want to change.

image

Set the pattern to “^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)”.  Make sure to replace ‘site2’ with your subfolder name in both places.  Yes, I realize this is a pretty messy looking rule, but it handles a few situations.  This rule will handle the following situations correctly:

Original Rewritten using {R:1}{R:2}
http://www.site2.com/site2/default.aspx http://www.site2.com/default.aspx
http://www.site2.com/folder1/site2/default.aspx Won’t rewrite since it’s a sub-sub folder
/site2/default.aspx /default.aspx
site2/default.aspx /default.aspx
/folder1/site2/default.aspx Won’t rewrite since it’s a sub-sub folder.

For the conditions section, you can leave that be.

Finally, for the rule, set the Action Type to “Rewrite” and set the Value to “{R:1}{R:2}”.  The {R:1} and {R:2} are back references to the sections within parentheses.  In other words, in http://domain.com/site2/something, {R:1} will be http://domain.com and {R:2} will be /something.

image

If you view your rule from your web.config file (or applicationHost.config if it’s a global rule), it should look like this:

<rule name="Outgoing - URL paths" preCondition=".aspx pages only" enabled="true">
<match filterByTags="A" pattern="^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)" />
<action type="Rewrite" value="{R:1}{R:2}" />
</rule>
<preConditions>
<preCondition name=".aspx pages only">
<add input="{SCRIPT_NAME}" pattern="\.aspx$" />
</preCondition>
</preConditions>

Solving the Redirect Problem

Outgoing Rule #2

The second issue that we can run into is with a client-side redirect.  This is triggered by a LOCATION response header that is sent to the client.  Forms authentication is a common example.  To reproduce this, password protect your subfolder and watch how it redirects and adds the subfolder path back in.

Notice in my test case the extra paths:

http://site2.com/site2/login.aspx?ReturnUrl=%2fsite2%2fdefault.aspx

I want to remove /site2 from both the URL and the ReturnUrl querystring value.  For semi-readability, let’s do this in 2 separate rules, one for the URL and one for the querystring.

Create a second rule.  As with the previous rule, it can be created in the /site2 subfolder.  In the URL Rewrite wizard, select Outbound rules –> “Blank Rule”.

Fill in the following information:

Name response_location URL
Precondition Don’t set
Match: Matching Scope Server Variable
Match: Variable Name RESPONSE_LOCATION
Match: Pattern ^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)
Conditions Don’t set
Action Type Rewrite
Action Properties {R:1}{R:2}

It should end up like so:

<rule name="response_location URL">
    <match serverVariable="RESPONSE_LOCATION" pattern="^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)" />
    <action type="Rewrite" value="{R:1}{R:2}" />
</rule>

Outgoing Rule #3

Outgoing Rule #2 only takes care of the URL path, and not the querystring path.  Let’s create one final rule to take care of the path in the querystring to ensure that ReturnUrl=%2fsite2%2fdefault.aspx gets rewritten to ReturnUrl=%2fdefault.aspx.

The %2f is the HTML encoding for forward slash (/).

Create a rule like the previous one, but with the following settings:

Name response_location querystring
Precondition Don’t set
Match: Matching Scope Server Variable
Match: Variable Name RESPONSE_LOCATION
Match: Pattern (.*)%2fsite2(.*)
Conditions Don’t set
Action Type Rewrite
Action Properties {R:1}{R:2}

The config should look like this:

<rule name="response_location querystring">
    <match serverVariable="RESPONSE_LOCATION" pattern="(.*)%2fsite2(.*)" />
    <action type="Rewrite" value="{R:1}{R:2}" />
</rule>

It’s possible to squeeze the last two rules into one, but it gets kind of confusing so I felt that it’s better to show it as two separate rules.

Summary

With the rules covered in these two parts, we’re able to have a site in a subfolder and make it appear as if it’s in the root of the site.  Not only that, we can overcome automatic redirecting that is caused by ASP.NET, other scripting technologies, and especially existing applications.

Following is an example of the incoming and outgoing rules necessary for a site called www.site2.com hosted in a subfolder called /site2.  Remember that the outgoing rules can be placed in the /site2 folder instead of the in the root of the site.

<rewrite>
<rules>
<rule name="site2.com in a subfolder" enabled="true" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_HOST}" pattern="^(www\.)?site2\.com$" />
<add input="{PATH_INFO}" pattern="^/site2($|/)" negate="true" />
</conditions>
<action type="Rewrite" url="/site2/{R:0}" />
</rule>
</rules>
<outboundRules>
<rule name="Outgoing - URL paths" preCondition=".aspx pages only" enabled="true">
<match filterByTags="A" pattern="^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)" />
<action type="Rewrite" value="{R:1}{R:2}" />
</rule>
<rule name="response_location URL">
<match serverVariable="RESPONSE_LOCATION" pattern="^(?:site2|(.*//[_a-zA-Z0-9-\.]*)?/site2)(.*)" />
<action type="Rewrite" value="{R:1}{R:2}" />
</rule>
<rule name="response_location querystring">
<match serverVariable="RESPONSE_LOCATION" pattern="(.*)%2fsite2(.*)" />
<action type="Rewrite" value="{R:1}{R:2}" />
</rule>
<preConditions>
<preCondition name=".aspx pages only">
<add input="{SCRIPT_NAME}" pattern="\.aspx$" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>

If you run into any situations that aren’t caught by these rules, please let me know so I can update this to be as complete as possible.

Happy URL Rewriting!

83 Comments

  • Excellent! ;-)

  • Hi G. That's similar but a bit different. If I understand, you want the domain to show www.example.com/client1 since you only have 1 domain name, but you want the references made from /client1 to be relative to the client1 folder, even if they post it as /css.

    What I suggest it to start a thread about that here: http://forums.iis.net/1152.aspx or start a thread directly with me using the Contact link above. URL Rewrite can definitely do that, but it's different than this blog post covered.

  • Wow... had no idea you could do this... very kewl!

  • Hi,

    Very nice article.

    Take for example a E-Com site in a sub folder ,where every second is a penny will this model be suitable ?

    Will that hit performance ?

    Thanks,
    Thanigianathan.S

  • I haven't been able to try any of these rewrite rules
    I keep getting this error
    system.webServer has invalid child element rewrite
    and a squigly under rewrite any idea how to fix

  • That error occurs when URL Rewrite isn't installed on the server, or it's install incorrectly. Try a fresh install or a repair. You can download it from www.iis.net and search for URL Rewrite.

    If the error is just in Visual Studio when editing, you can likely ignore that. Visual Studio just doesn't have the section declarations for intellisense.

  • Hey thanks for getting back to my comment it works in IIS7 I was using Cassini next you should write a post on how to get VS to recognize URL Rewrite with intellisense great job keep up the good work

  • Tried this part but /site2 is not hidden in the URL as you said: "but if you want to hide /site2 in the URL, you need to take it one step further"

    Do you know what is wrong with my environment?

  • Thanks Rickj1.

    nghianguyen, can you explain further what you've tried, what does work and if you run into any errors? Feel free to send me an email via the Contact link above.

  • I'm able to rewrite /site1 and hide "site1" from the URL, but when I enter /site1/ in the browswer, it does rewrite to the correct location, but it doesn't hide "site1/" from the URL. I can send details in email if you like.

  • jamescpk, what you can do to get ride of the /site1 in the rare occasion that it still comes up is to create a 'redirect' rule and move it to the top. Catch the /site1 in {PATH_INFO} with /site1(.*) and then redirect to http://www.site1.com/{R:1}. That should redirect and drop the /site1. It does a client-side redirect but that's to be expected if the URL already has the /site1.

  • I just moved to a new host that URL Rewrite and have implemented the rules as described here to setup the subsite access. It work great - until you hit F5 a number of times then I get a 500 error and the css no longer loads... the site resets after 5 minutes and it works again till the next set of F5's. disabling outbound rule 2 and 3 resolves the issue.

    Any ideas?

  • Hi EliteMike. Sorry for the delay in replying. Since the outbound rule just processes tags, the 'slt' must still be coming in through another tag. The trick is to find out where the slt shows up and then add that tag to the outbound filterByTag list. If you view source on the page and search for 'slt', you'll see all of the left overs that still need to be processed in the outbound rule.

  • Hi Mark. It sounds like a CPU issue and your host has the app pool set to turn off for 5 minutes after heavy CPU. First I would contact them and find out what app pool settings they use. That will help to know what limit you need to stay within. Then, see if you can reproduce the issue in your dev machine. See if you can see the heavy CPU with your rules for your site. It may be that something in the rule is bringing it over the top, in terms of CPU. Regex does take some processing resources so if you have a lot of traffic, it may be enough to work it too hard, and you'll need to make tweaks to your rules and processed tags so that the CPU doesn't complain too much.

  • Thanks, I did not see that in my original read through. That fixed some of the issues, but I still can't get ajax to work. Even when I get rid of all the ajax I am getting a jsdebug error, sys is undefined and object expected. I feel like I'm soo close.

    When i search my source code, I find slt in 2 spots now. One is in the css conditional statement. IS there a way to handle that?

    The other is with the ajax tool kit control.

  • Hi EliteMike. Let me see if I can get a repro later today or tomorrow. In the meantime, just to confirm, did you add the rule from part I of this article that allows /slt/ to still work in case a request for it comes in by mistake? That should allow requests to continue to work, and since the ajax call URL usually isn't seen by people, may be ok.

  • When I put the application into its own application pool, I recieve an error 403-18 forbidden. Is it possible to run the application in a seperate application pool from the main web site when using URLRewrite?

  • Hi James. You can do that with a redirect, but not with a rewrite. With a rewrite, it needs to be in the same application pool so that URL Rewrite has access to the full request lifecycle.

    You can mark it as an application using the same app pool, you just can't put it in a fresh app pool.

  • Hi Kevin,

    There are about 3 ways to do this. Just to make sure I understand, you want to map domain names to different accounts and content, allowing people to define their own domain name, that may or may not match their signup name. The content is database driven and served from a single site I assume.

    Option 1) Use URL Rewrite to add &domain=userdomainnameabcd.com to the querystring. That allows you to use the querystring to determine what content to serve.

    2) You can further extend URL Rewrite to have database driven rules if you want to start with a domain name and rewrite to an ID. You would need to write your own HTTP Module to do that. www.iis.net has some good walkthroughs on how to extend URL Rewrite 2.0.

    3) I think your easiest option will be to do it directly in code though. Request.ServerVariables("HTTP_HOST") is the domain name. From code, just call to HTTP_HOST to determine what the domain name is. Then in your code, pass in that parameter to all of your queries to determine what content to serve. (option #1 would need to do this anyway, so this avoids a step).

  • Thanks Scott. Much appreciated! Yes the content is database driven and is served from a single site. The sign-up name e.g. 'mysite1' is entirely different from any domain name they may have registered and want to use. I am thinking of Option 3 above.

    If I understand correctly this is what we do:

    1) In IIS 7 we add the user domain name (eg userdomainnameabcd.com) to the Site Bindings for the main portal site.

    2) We add the user domain name in a column in our database - so this domain name is associated with that user. Then we add this user domain name to the querystring in each of our links for a particular users's portal site? So, for example, in the main menu the coded link for the Contact page for 'mysite1' might now be:

    www.userdomainnameabcd.com/sites/default.aspx?site=mysite1&page=contact&domain=userdomainnameabcd.com


    3) Then we use Request.ServerVariables("HTTP_HOST")to determine the domain name in the URL. If this domain name matches the domain name in our database then we serve up that users' content.

    4) We use URL Rewrite to rewrite requests for: www.userdomainnameabcd.com > www.userdomainnameabcd.com/sites/default.aspx?site=mysite1&page=home&domain=userdomainnameabcd.com

    I'm sorry if this is off the mark but it's a new area for me! Thanks.


  • Hi Kevin,

    That sounds like a fun project.

    Using Option 3, you don't need to worry about your steps #2 and #4. They are addressed with #3. In other words, you have the domain name with HTTP_HOST already, before URL Rewrite does anything. So it's extra easy.

    For #1, you can make that easier by pointing everything to a single IP and using a wildcard binding. Set the IP and port, but leave the host header unset. That will catch everything for that IP. That way you don't need to make any IIS changes as new domains are added. Just have users point their DNS to your IP.

  • Thanks Scott for your patience :) I'm not quite sure how we can leave out all of step 2 though. I'm guessing that we would have to add the user domain name in a column in our database - so this domain name is associated with that user. Do you mean just leave out the adding the querystring part in step 2?

    Finally the portal site page, that serves all the different user portals, is hosted in a sub-directory (/sites/default.aspx). The main Home page ('default.aspx' in the root folder) tells users all about the service we provide, and does not display any portal content. It's just a marketing page.

    Presumably we have to add the HTTP_HOST code, to determine the domain name, in the main Home page in the root? Then do we do a response.redirect to the portal site page in this Home page code ? We also have to add the HTTP_HOST code in the portal page so that other portal links include the user domain name?

    Example:

    1) If the user domain name is userdomainnameabcd.com this will initially point to our main Home page in the root (default.aspx).

    2) In this Home page code we use HTTP_HOST to detect that it is userdomainnameabcd.com.

    3) In the Home page code we then do a look up in our table to see which sign-up name is associated with that domain name. In this example it is 'mysite1'

    4) We then do a response.redirect in the Home page code to: www.userdomainnameabcd.com/sites/default.aspx?site=mysite1&page=home

    5) We also need to add HTTP_HOST in the portal page itself (/sites/default.aspx) to detect that it is userdomainnameabcd.com - so that all other links for that user portal will include their domain name e.g. www.userdomainnameabcd.com/sites/default.aspx?site=mysite1&page=contact

    Is this correct? Does URL Rewrite fit anywhere at all in to this? Finally is this method search engine friendly?

    Sorry again if this is sounds a bit dumb but I really am very new to this area :)

  • Hi Kevin,

    You're right that the first part of your #2 is required ... adding a column to the database (likely in most tables). The part that you don't need is "Then we add this user domain name to the querystring in each of our links for a particular users's portal site?"

    I like where you're heading. I think you can make it even cleaner for the sites, and more search engine friendly. How about something like this:

    1) Have 2 separate sites. Portal+Homepage. Create 2 different sites in IIS if you can (i.e. it's not shared). The homepage should just have a host header for your homepage URL. Everything else will be caught by the portal site.

    This allows you to have the portal in the root of the site, providing easier URLs for people to hand out.

    Basically separate the code between the two.

    2) You don't need site= in the querystring. Pull that directly from HTTP_HOST. You can save it as a session variable in global.asax on session start, or it should be plenty fast enough (or maybe faster) to call it on every page. i.e. string sitename = Request.ServerVariables["HTTP_HOST"]; (Dim sitename As String = Request.ServerVariables("HTTP_HOST") in VB.NET)

    3) With separate code bases, you can do the check for site right on the default.aspx page. It sounds like you'll have the whole set of sites database driven and using a single page. I recommend using the entire domain name for the key, not just the 2nd level. i.e. mysite1.com.

    4) You shouldn't ever need to do a response.redirect with this method. URL Rewrite can do 'rewrites' instead.

    5) See #2. With separated sites, #2 and #5 are the same for the portal site. And you don't need the site in the querystring since you always have HTTP_HOST available to you.

    For search engines, use URL Rewrite to provide friendly URLs. So www.userdomainnameabcd.com/home/ would map to /default.aspx?page=home. This will utilize URL Rewrite to give friendly URLs for users and search engines. You'll need to exclude shared real folders like /images/.

    Visitors to the site will think that there are physical folders that exist, but really everything is being done through your default.aspx page. (btw, if you want, it can be under /sites/default.aspx. It doesn't matter since URL Rewrite can rewrite it however you want).

  • Thanks Scott - much appreciated again. I think I will have to go away and try it now - hopefully it will work, and I won't have to bother you again :) All the best.

  • Sure thing Kevin

  • Me again :) Actually thinking about it, I don't know if we can have 2 different sites in IIS for the user portal sites and the main marketing site (test.com).

    The reason being is that, as I mentioned in my first post above, some users will NOT have their own registered domain names so they will have their sign-up name appended to the main marketing site domain name as their URL i.e.

    www.test.com/mysite1
    www.test.com/mysite2

    etc....

    In reality these point to each user Home page such as:

    www.test.com/sites/default.aspx?site=mysite1&page=home
    www.test.com/sites/default.aspx?site=mysite2&page=home

    However other users may have their own registered domain names that they will want to use - as discussed above.

    So, if I am correct (?), I think that we will have to stick with one site in IIS and use the HTTP_HOST code in the main marketing site Home page to do the initial response.redirect to the portal site single page. This portal page then uses more HTTP_HOST, and other, code to detect if the user has their own registered domain name and then which user site to display, as I outlined in a previous post.

    I really like your '2 different sites in IIS' solution, but I can't see if it can work given this additional requirement for the 'www.test.com/mysite1 etc' type URLs.



  • Kevin,

    Good point. Since you are also supporting generic domain names under the same domain name as your primary site (good idea), then you'll need to keep the code base together.

    In that case, do what you originally planned and have your site as /default.aspx and the portal site as /sites/default.aspx. Then use URL rewrite to mask that.

    For example,
    1) www.yourdomain.com/site/theirsite/home could map to /sites/default.aspx?site=theirsite.com&page=home. (or use sites.yourdomain.com/theirsite/home or theirsite.yourdomain.com/home ... all can be accommodated)
    2) www.yourdomain.com/default.aspx would remain untouched
    3) www.theirsite.com/home would map to /sites/default.aspx?site=theirsite.com&page=home

    In /sites/default.aspx, just check for site=something and allow URL Rewrite to put the HTTP_HOST value into the querystring. That way it will be hidden from the outside, give you a simple query from code, and will work for dedicated domain names and your shared domain name.

  • Hi Kevin,

    You're right, the regex rules can be difficult to get a handle on, but since you got #1 and #2 working, it sounds like you are able to work through some fun regex rules already.

    For the part that isn't working, it sounds like you would need some mappings of domain names to site names. There are about 3 ways to handle that:
    1) with a manual rule for every mapping (ick), 2) extending URL Rewrite to do a database call or 3) doing it from code.

    For this, I would recommend handling it from code. How about this...add a 3rd parameter to your default.aspx page. Call it domain. i.e. site=mysite&domain=yoursite.com&page=home.

    In your code, if site "" then check domain.

    Therefore, if site is known do a database call with site in the filtering query. If site isn't known, then join to a mapping table of domains to sites, and query by domain instead.

    Would that do the trick? Then in URL Rewrite for your current rule, you would just need to add &domain={HTTP_HOST} in the action line.

  • Kevin, great! I'm pleased to hear that it worked for you.

    You're right, I hear that a lot that URL Rewrite (and ARR) are so impressive but that information is hard to find. I do need to get around to more blog posts and/or video walkthroughs. I do hope to, I just need to make the time.

  • Hey Scott, I've applied this to putting BlogEngine.NET 2.0 on my discountasp.net at /subsites/knowacode. Everything works great, except when "default.aspx" is the page explicitly targeted. Then IIS7 gives the error "The virtual path '/default.aspx' maps to another application, which is not allowed."
    http://knowacode.com/ works, http://knowacode.com/default.aspx fails
    All other .aspx pages work fine, just folders where default.aspx is specified on the URL have this problem. Any idea what's going on here?

  • Hi Noah,

    I heard someone else report a default doc issue on discountasp. I bet they are doing something in-front of the web server, possibly with their own URL Rewrite rules. I would start by asking them if they know. I would love to hear back so that I'm aware of their configuration in that regard.

    Possible workarounds would be the move your rule to the top of the list and set to not process any other rules. Likely the clashing rule is in-front of yours though, in which case you can consider a 'redirect' and then a 'rewrite'. For example, redirect from /default.aspx to /, and then the rewrite should work.

  • Hi Valerie,

    There are a few parts to making something like this successfully work:
    1. take the domain name for the new site and ‘rewrite’ to the subfolder (e.g. www.4OurHorses.com runs from {siteroot}/4ourhorses)
    2. try to change the URL in all links so that your html source never has /4ourhorses in a URL
    3. catch anything that didn’t get handled for step 2, and do a ‘redirect’

    This Part II with the outbound rules takes care of #2. You can do without the outbound rules, but it will mean that some of your links will still have the /4ourhorses in the URL. If the outbound rules don’t work, check to make sure that your host has URL Rewrite 2.0 installed. You can also clean up the URLs in your HTML other ways by updating your code. Some things can't be avoided, for example a login page redirect.

    For #3, you can create another rule that does a redirect. It will mean that a client-side redirect will be performed every time one of the wrong URLs exist so it has some performance overhead, but it will look better and address most of your concerns.

    Here’s what the rule should look like (place it above your bottom rule) (this is untested but it should be close):









  • Hi Peter,

    URL Rewrite lives above SSI so it should work the same. The only thing is that the relative URLs won't match the physical URL. For example /lib/x from code will be {siteroot}/lib/x while the same path in an HTML linnk really means {siteroot}/(subfolder)/lib/x. I don't believe that should affect your example though. Can you let me know what type of error or issue that you're getting?

  • Hi Peter. It sounds like the "virtual" keyword is meant to be based on the http path, which will change after the rewriting. ASP.NET has similar issues where the rewrite layer and the processing layer are separate.

    Check out this doc: http://www.w3schools.com/asp/asp_incfiles.asp

    For a solution, yyou can use "file" instead, or you can always use relative paths like ../.

  • Hi,

    I'm new to ARR and URL Rewriting and been pulling my hair over the weekend to determine the correct rules (input and output).

    Here's my setup: I have 3 server, one being my ARR Server wher I configure 2 server farm (News_SF and Blog_SF). The other 2 server is just plain web server hosting 2 different application (News and Blog). The IP Address of the 2 web server is binded to the website and added to the server farm in the ARR Server.

    I was able to test the request routing by just adding a single rule and route that to the News_SF and Blog_SF one at a time.

    Here's my problem:

    Assuming my DNS name is http://my.company.com:

    1. I wanted to route all request to the News Server farm to any URL except if the url contains "pages/blog"

    ex.

    1. Should route to News_SF
    - http://my.company.com
    - http://my.company.com/default.aspx
    - http://my.company.com/calendar


    2. Should route to Blog
    - http://my.company.com/pages/blog
    - http://my.company.com/pages/blog/1232

    I was succesful in routing using just a simple wildcard (still need to figure out the correct pattern) but the main issue is that, images on the pages/blog are broken.

    Hope you could help.

  • Hi Jay,

    ARR + URL Rewrite have a lot of flexibility so it's just a matter of getting a base understanding of URL Rewrite.

    In your case, the first thing I would do is to make sure to *not* have ARR manage the rules for you. Otherwise if you change them manually, and then make a rule related change in the ARR part, it will blow away your custom settings.

    The way to 'unbind' the URL Rewrite rule from ARR is to rename it to something different. How about VIP-News and VIP-Blog.

    First though, make sure that you have unique bindings on the web servers. Probably the easiest is to assign 2 IPs to each web server, and assign 1 IP to each site. So you'll have 4 IPs in the end. Then, on the ARR server, in your News_SF server farm, point to the corresponding IPs for News (1 on each web server), and for the Blog_SF server, point to the other 2 IPs. They can be internal IPs as long as the ARR server can communicate using that IP.

    Now, back to the rules on the ARR server.

    There are a couple ways to tackle this, but let's start with the easiest for now. To do so, the VIP-Blog rule should be first with VIP-News below it.

    Let's do it using Regular Expressions. So setup a VIP-Blog rule using regular expressions, and for the URL, enter "^pages/". You can add a condition of {HTTP_HOST} = "^my\.company\.com$" if you want, but if you're only handling the one domain name, then that part is optional. Set it to point to the blogs server farm.

    The other rule can be almost the same except that the url should be ".*" (without the quotes), and it should point to the other server farm.

  • Hi Mustafa,

    What you need in this case is to mark the root of the different sites as Application folders. You can do that by right-clicking on the folder in IIS 7 manager and converting to an application. That marks a new root for the various ASP.NET application folders.

    You may run into clashes with your web.config from the parent folder since web.config inherits all the way down the folder structure. If you do, you may need to use tricks like or or wrap sections in

  • Thanks!!! I was doing that already but didnt realize, that the code is doign something that make url of the site to have en-us. Anyway thanks again for all the post you have here. I learned alot.

  • I am unable to modify your site2 code because my website www.12anddone.com has a root folder of _12anddone and I am unable to tell inside your rewrite code which site2 is the domain and which site2 is the folder.

    Could you provide an example that uses www.site2.com with a folder called _site2 - Thank you.

    Michael Riley
    GySgt USMC Retired

  • Hi Cape Code Gunny,

    That's a good question. This is a fairly complex rule because it tries to be all things for all people.

    It's been a while since I wrote it but I'm pretty sure that if it doesn't have a .com, then it's a disk path. That's a good rule of thumb for changing it.

  • Could this same technique be used if I wanted to have www.masterdomain.com on my server while hosting a blog on another domain name and server? For instance blog.wordpress.com would look like it is being served up from www.masterdomain.com/blog.

  • blountdl, you can do that with a reverse proxy. ARR is a great solution for that. Check out my intro and further weeks here: http://dotnetslackers.com/projects/LearnIIS7/. Start with week 31.

    Another option if you don't want to setup a reverse proxy is to split the domain name. For example blog.masterdomain.com can be given a different IP and can point to a different server. So /blog/ in the URL requires a reverse proxy, but blog. as a cname can.

  • Hello, I am using all of the rules from this post and part one. It is all working okay except one problem. In IE the background images I have in my css will show up but the Firefox the don't. In Firefox, when I right click on the place where the image is supposed to be and view background image in a different window I get a IIS server error 404. In IE it is working just fine. In the same FF window where they don't show up on the page they do show up in the firebug Style Pane on the right.

    Also, is there a certain order that all of the rules should be in. When using the SEO tool in IIS I get errors that there are unnecessary redirects and that my link rank won't be passed through. I also have rules to remove default.aspx, to lowercase, and to add www. I used the pre-built rules for all of those and I am not sure if it has to do with the order I have the rules or if there is just too many.

    Thanks.

  • I am getting weird behavior with these rules and I am not sure how to trouble shoot it. When I have the outbound rule that checks all the "A" elements enabled I was not getting any background images.

    I found this out because I started turning off rules to see which one was the problem. When I noticed this was the the rule that had the issue I went back and re-read your post. I realized I had a typo and was using (R:1)(R:2) on accident instead of {R:1}{R:2}.

    When I enabled the rule everything worked as expected. A few days later it went back to not working. I uploaded another site using these same rules on the same server in a different app starting point is working properly. So I started comparing the two projects but so far they are similar. They are based off the same root project.

    It is enabled now and seems to be working. My question is is there anything that would cause this? Is there a way to use IIS to see what rules are getting used and in what order they are?

  • Hi Daniel,

    I'll answer the 'order' question first. The rules are processed from the top to the bottom and they can make a big difference. For example, if some rules depend on other rules then they need to be first. Or if some rule are redirects or are set to stop processing then they may need to be above or below other rules, depending on your situation. Also for performance reasons you can put the most used rules higher up and make a difference on very busy sites.

    As for the Firefox issue, that's an interesting one. It sounds like a browser issue ... although it sounds like it's triggered by a URL Rewrite rule. Possibly there is a caching issue where a redirect or an image was cached and now it won't recognize the new rule, or it's hanging on to the old rule.

    Can you determine the full URL for the background image and manually enter that into the address bar? That will help visualize what it should be and help find out why it doesn't work.

    The best way to narrow it down is to disable rules until you know for sure which rule is affecting it. You can also use Failed Request Tracing to see which rules are processed. Another trick that I use sometimes is create a breaking rule that I move up and down to find out which rules are in play. For example, create a rule that throws a 500 error and is filtered to just your IP. Then it won't affect anyone else while you're troubleshooting. Then move the rule to the very top and it should break. Move it down until it starts working again. If your rules aren't set to stop processing though, this trick may not work. Your mileage may vary.

  • Hi Krishna,

    Do you want it to look like 1 URL to the end user, or just the website? If it's to the end user, you should use URL Rewrite only, with a "redirect" rule. That causes a client-side redirect.

    If it's for the site but you want the user's web browser to keep the original URL then it depends on the config. If it's on the same server then you can use it with a URL Rewrite rule as a "rewrite". You don't need a reverse proxy (ARR) for that. However, if you are also load balancing then you should use ARR.

    So my question, before I can answer this properly, is which of these setups do you have?
    1) want the browser to just have 1 URL (client-side redirect)
    2) server-side change, 1 site on the same server
    3) server-side change, multi-server

    As a side, I have a video series here which you may find useful: http://dotnetslackers.com/projects/LearnIIS7/. Check out weeks 8-14 on URL Rewrite and weeks 31 and on for ARR.

  • Wooow! It seems this url rewriting functionality is a lot more powerfull then I've thought.

  • Hi Thomas,

    You should be able to achieve that with something like this:









    That watches for www.test.{something} and redirects to http://test.{something} while retaining the rest of the URL and querystring.

    If you want it more generic, you can do this:








    That doesn't care what the domain name is as long as it starts with www.

  • Hi OWScott, I was pointed to your blog by someone on forums.iis.net who thought you might be able to help me further.
    My question:
    What I want is that: http://johndoe.mydomain.nl and johndoe.mydomain.nl redirect to: http://www.mydomain.nl/mypage.aspx?title=johndoe But I get: Not Found HTTP Error 404. The requested resource is not found. btw: I placed the above code as my first rewrite rule in the section Do I need to configure anything extra in IIS?

    See the original post here:
    http://forums.iis.net/t/1155754.aspx#1895661

    Hope you can help me! :)

    Thanks!

  • Hi Peter,

    URL Rewrite can do this well for you. I just replied on the forum post that you linked to above.

  • Here is my scenario:
    I have a website www.mainsite.com under folder /root
    I want to be able to point subdomain.mainsite.com to a subdirectory under 'root', i.e. /root/subdirectory.

    Whenever the user visits the address subdomain.mainsite.com, they should be redirected to subdomain.mainsite.com/subdirectory, but without showing the '/subdirectory' part. The address on the bar should display, eg for the index page of the subdomain 'subdomain.mainsite.com/index.aspx' and NOT "subdomain.mainsite.com/subdirectory/index.aspx"

    Help please

  • Hi,

    I thought i could fix the issue of removing the subdomain folder from the path of my site to have it like subdomain.domain.com/default.aspx and not like subdomain.domain.com/subdomain/default.aspx by using the code in your article, I basically copied all the code replacing "site2" with the name of my subdomain but I'm getting a 404 error. In the trace the requested url looks like subdomain.domain.com/subdomain/subdomain.domain.com/subdomain/subdomain.domain.com/subdomain/subdomain.domain.com/subdomain/subdomain.domain.com/subdomain/
    I'd really like to know why something like that happens, could you please point me in the right direction?

  • Hi Terry,

    That usually happens when there is a redirect in the code that clashes with a rewrite in URL Rewrite. For example a login page can do that. Follow the link at the top of this page to part 1 and in the example there it shows how to exclude the current folder from the rewrite. So make sure that the condition excludes the folder path in case it's called.

  • Excellent post. Thank you very much.

    However, I need your help. I can’t figure out why I’m getting a HTTP Error 404.0 - Not Found error. In my case, the inbound rule works great, but the first outbound rule is causing the 404 error. See my code below.





















  • Hi Israel,

    The 404 is most likely from one of the redirect rules, otherwise it wouldn't cause a 404 when loading the first page. Can you disable all 3 rules and then turn them on one by one until you isolate which of the rules caused the 404?

    Then, a good test is to leverage google and in the action change it from "{R:1}{R:2}" to "http://google.com/?id={R:1}{R:2}"

    When it redirects the URL will show the end result, like this: https://www.google.com/?id=http://domain.com?def=abc

    That should give some clues on what is happening.

  • Hi BigTes,

    The reason it works is that your first rule probably ignores /site2/ so that with or without /site2/ will perform the same. That's what the rule in part I does, if that's what you used. So that explains that part.

    So, how to get rid of the /site2/ after logging in. The perl script isn't aware of the rewritten path so it's adding that when the redirect occurs.

    I assume you have the two outgoing rules applied from this blog post? If so, then the perl script must generate something that's enough different that the outbound rules aren't catching.

    The question is what, and how to fix it. How about using Fiddler2 and seeing if you can see what's in the response. It's probably a redirect, although it may be just a link in the page. I don't have enough information to tell. But Fiddler should let you know what needs to be changed. Let me know if you can track down the old reference to site2 and I can help brainstorm on how to update the rule to catch it.

  • Hi BigTex,

    I have just a minute to reply now so I'll be brief. For Fiddler if you browse to your Perl site with Fiddler running then it should show all of the web requests in the left-hand pane. Select your page which has the bad link, or the bad redirect, then in the right-hand side select the "Inspectors" tab and then select Raw for both the request and response (top and bottom section). That will let you see exactly what's happening with the request.

    Also, if you can send your outbound rules I'll look over it. It's been a while since I've reviewed this but if I start with what you have, hopefully something will stand out to me.

  • Hi BigTex,

    That's helpful, although I don't know if I see the issue yet. I think there are two issues, one I can see, but the other I don't see yet.

    To solve the first one, in your first outgoing rule, change filterByTags="A" to filterByTags="A, Form". That will get the reference in your form tag too.

    What I don't know is why the outgoing redirect rule didn't still fix the URL. After making the change to add "Form" to the rule, what happens if you disable the outbound rules and run it again? I'm curious what the URL structure that the .pl page generated is. What does the Fiddler data look like? The key information are any of the lines with paths in them.

    Note I deleted your latest post since your password was included in the action line in the querystring.

  • Hi Scott,

    Thanks for all of your help. I have determined (after some exaustive testing) that the issue that I described only manifests itself when the application that I am using is deployed using IIS. And it also manifests itself wether or not the URL rules are in place. It's "preferred" platform is Apache, but we have no experience using it so have opted for IIS instead. Your URL Rewrite rules are working flawlessly and we certainly thank you for your efforts. So many-many kudos to you!!!

  • Hi BigTex.

    Great, thanks for the update on that, and I'm pleased to hear that it's working for you.

    I wonder why it works well in Apache. My guess is that either it's in the root of the site rather than a subfolder, or that the application is Apache aware and handles that situation automatically. In any case, glad that you have it working well now!

  • hello, i also would like to add my thanks for a very usefule forum. Here is my issue. I have many rules, all of which work and i use the rewrite tag in my web.config and use an include file for my rules.
    as follows:


    The rules all work. I then added in one outbound rule (which also works as long as it is in the web.config file) as follows:
















    When i try to move the outbound rules to my include file i get a 500 internal server error and in failed request tracing i get either bad xml (which it is not, or unrecognized element 'outboundRules' or Unrecognized attribute 'preCondition'. I AM running iis7 so that is not an issue.

    my rewrite.config file is basically this:






















    Please help. I will have a LOT of outbound rules and would hate to have to put them all in the web.config file.

  • Hi Elena,

    You can pull out the outbound rules by setting the configSource directive on like so:








    And then in RewriteOutbound.config you can have something like this:

    -----






    -----

    The element doesn't support configSource so you need to set it for both the and .

  • Hi OWScott,

    Since I see this post is still active, maybe you can put me in the right direction on the following issue:

    I have a multi-language website, configured in one IIS-website using hostheaders for domain-names, e.g.:
    www.mysite.com
    www.mysite.de
    www.mysite.nl
    In my website I use the hostheader to determine what language to use on the site. From then on the 'language' parameter is also passed in links within the site, www.mysite.com/default.asp?pageid=123&lang=EN

    Now I need to rewrite the URL's. Of course I can pass the lang= to the rewrite-module and get www.mysite.com/EN/123 as a result or www.mysite.nl/NL/567

    But, is it possible to get the language using the hostheader?
    I would want: www.mysite.com/products to be interpreted as /default.asp?lang=en&pagid=123
    and www.mysite.nl/producten to be interpreted as /default.asp?lang=nl&pagid=627

    I hope you understand my problem. I googled several websites but sofar I haven't found this exact issue.

  • I have a website, say www.xyz.com I have another website hosted app.xyz.com

    I am looking at redirecting all the subdomain requests to app.xyz.com.

    e.g a.xyz.com - app.xyz.com

    b.xyz.com - app.xyz.com

    c.xyz.com - app.xyz.com

    The url in the browser needs to remain a.xyz.com, b.xyz.com or c.xyz.com

    Can you help me with the steps involved. What needs to be done in DNS and what rewrite rule needs to be written for IIS. ( For this I don't want to create any folders, sub-domains, binding rules, etc for every entry.)

    Will the above work with http and https ?

    TIA.

  • Hi Tushar,

    This will work with native IIS functionality without any URL Rewrite rules.

    For DNS point *.xyz.com to an available IP address on your web server.

    In IIS add a binding for just the IP address and with a blank host header. Then that site will catch everything for that IP address (except more specific bindings on other sites if you want to reuse the IP address elsewhere).

    That should do it. It will keep the original URL in the browser.

    For your www.xyz.com and xyz.com site you can create another site and add two specific bindings, one with www.xyz.com for the host header and one for xyz.com for the host header. Then your main website will be caught by one site, and everything else will be caught by the app site.

    For https it gets a bit trickier if you need to reuse the IP addresses. If you can devote an IP to your apps site and another IP to your main web site that's easiest then you can use blank host headers and relay on DNS to point to the correct site. Otherwise check out weeks 6 and 7 here for more details: http://dotnetslackers.com/projects/LearnIIS7/.

  • OWScott,

    Thank you very much for your reply.
    It turned out we didn't need the language parameter after all (because in the program-code we read the host name to set the language and because each language resides on a different domain) but this is a good one to remember in case we do only have one domain and multiple languages.

    Regards, and keep up the good work,

    Sieberen

  • Hi Sieberen,

    You're welcome, and thanks for the update. Glad to hear that you have a good solution.

  • Hi Naresh,

    Try out the following. It uses a rewrite map to map the key (en, ch, etc) to the TLD of the domain name (com, ch, de, dk, etc). Then it drops that from the URL and does a redirect. I believe this achieves what you're looking for.


















  • Hi Naresh,

    If you add a 3rd condition like so:



    then {C:4} will be the 2nd level domain (domain in www.domain.com). So the action can be:



    The default redirect is a 301 so this already performs a 301.

  • Hi Naresh,

    My mistake, I missed updating one part when creating the example. The 2nd condition should be this instead:



    The goal is to pull out the first part before the 2nd / from the REQUEST_URI and use that in the comparison for the second condition.

  • Hi Naresh,

    Give this a try:

    "^(?:www\.)?(.+?)\.([^\.]+)$"

    It's a bit messy but it will ignore a beginning www and then get everything in {C:4} except for the TLD.

  • Hi PW,

    The rule you have will work for the default doc, but not for the rest of the URLs. Your best bet would be to review part 1 (see the link at the top of this page) and use that example. That excludes /nopCommerce/ if you get a visit from the longer URL but will catch everything else.

    For the www part, you can do that with a second rule. Put it before the other rule. Here's how to do that: http://weblogs.asp.net/owscott/archive/2009/11/27/iis-url-rewrite-rewriting-non-www-to-www.aspx

    And for the 3rd point, that's the tricky part. If you can update all of your code references, that's the best way to handle it. Otherwise the details in the post above explain how to handle the old paths.

  • OWScott, thanks you so much.

    Everything works as expected. I don't even have to worry about URL shortening since nopCommerce is smart enough to adjust the URL.

    As far as I can remember, I had visited part 1 before...:-), and ruslany blog, and so many others. Still unsure which one of my saved web.config is working...:-) Please forgive my ignorance.

    Just saw similar unresolved problem in nopCommerce forum. Would you mind if I put link there (and probably another) and credit to you?


    Best regards

    PW

  • Hi PW,

    Excellent, nice to hear that you got it working. Sure, I'm pleased to help in any way possible so if this helps anyone with using nopCommerce, that sounds good to me. Feel free to reference what you want.

  • Scott,

    Apparently trackAllCaptures was causing the HTTP 500 error with the code snippet above. I remove it, although I'm not sure what effect that has - other than the site loads.

    So I added the rest of your example for the .... Now I'm getting HTTP 500 errors again. Even when I include only the opening and closing tag, like .

    Searching Google I've seen that OutboundRules will not work if IIS is using compression. I've found posts from users that indicate Godaddy has compression disabled by default.

    If trackAllCaptures is any clue, according to Godaddy they are running URL Rewrite 2.0. I'm in the blind. My site doesn't display any error information other than HTTP 500 and since I'm stuck with IIS Express 7, I can't test locally or use the GUI to create rules. :-S

    Thanks.

  • Hi,

    Any updates..

    With Regards,
    Naresh

  • Hi rwkiii,

    After 8 months on this, we definitely need to get this figured. I'm sure we will. From the sounds of your two comments, you must have URL Rewrite 1.0. The track all captures and outbound rules were introduced in URL Rewrite 2.0, so I bet that's the cause of the two different failures that you tracked down.

    Can you double check with godaddy to confirm that it's version 2.0 that they have installed?

    As another step, what happens when you get the first rule running, but without the trackAllCaptures? Does it load domain2 if you test www.domain2.com? We can work on the outbound rules later.

    Also, just to confirm, if you visit www.domain-root.com/www.domain2.com/ does that still load (even though the URL isn't right)?

  • Hi Naresh,

    Sorry for the delay. I was out of town last week and am just catching up now.

    There are two ways to approach this. One with a positive check for all domain names, and one with a negative check for the IP address(es). If you only have a limited number of IP addresses then it's probably easiest to check that way.

    How about just something like this:



    That will mean that the rule won't fire if the domain name starts with "213.". which will likely account for all public IPs on your server but not exclude any real domain names.

  • Btw, I can omit the trackAllCaptures from the root web.config and my subdomains/secondary sites will load. I don't really know that the root web.config rules are even necessary.

    The outboundRules seem crucial though..

  • @rwkiii. I would see if GoDaddy can upgrade you to a server that supports URL Rewrite 2.0 and outbound rules. That should give you support for the rules that you're doing. At this point you're at their mercy.

  • Hi rwkiii. Thanks for the update. My guess is that they just have some servers that aren't updated. I'm pretty sure lots of other people are using URL Rewrite 2.0 at GoDaddy. So hopefully they can schedule the server hosting your site to be upgraded.

  • Hello there Scott,

    I've been dealing with this for a day and while searching I've found your blog. I hope you can help me with my problem.

    I have a MVC Application running locally. The original url is http://localhost:7887/Login/Index?ReturnUrl=%2f , I want this to be http://asg/login is that possible?

    Looking forward to your response.

    Thanks and God Bless.

  • Hi Angelica,

    If you own the domain (asq in your example) then you can do that with a reverse proxy. Within your local network you can point computers to asq which will also work.

    To create the rule, the easiest way to do that is to make sure that ARR is installed, then at the site level in URL Rewrite, create a new reverse proxy rule. Fill out the fields and you should have a rule which is pretty close to what you need.

Comments have been disabled for this content.