Angular routing supports multiple views through a feature named router outlets.
This feature allows for parts of the same page to react independently to change in the url. Imagine a web page that displays sports teams on its left half and the live scores on the right. You can click on the on a team on the left and see details about it, and click on a score on the right and see statistics about that game.
Named router outlets enable having all this be “deep-linkable”, i.e., every time you navigate on either the teams and/or the scores the url updates in a way that describes both states independently. If you send this url to someone else they will see exactly what you see.
Here’s an example of a possible URL for the details of FC Porto and the live score of Real Madrid vs Juventus:
https://www.imaginaryfootbalwebsite.com/(teams:fc-porto//live:real-juventus)
Notice something odd about that URL? It’s not the parenthesis… it’s the colon.
In the RFC for URLs there’s this:
…only alphanumerics, the special characters “$-_.+!‘(),”, and reserved characters used for their *reserved purposes may be used unencoded within a URL
(emphasis mine)
“:” is a reserved character, it’s used after the protocol (http, https) and after the domain to specify a port.
If you host your Angular application in an IIS server you’ll quickly realize that these URLs cause problems. IIS rejects them with 400 Bad Request.
If you look for “IIS using colon in url” in Google there’s quite a few results that describe how you can modify IIS to allow for all sorts of invalid characters. Here’s an example: Experiments in Wackiness: Allowing percents, angle-brackets, and other naughty things in the ASP.NET/IIS Request URL.
If you do read that post you’ll notice that it comes with a warning: “I’m loading the gun and showing you where to point it. If you point it at your foot, that’s your business. Safety mechanisms exist for a reason…”
Going the IIS route feels like fighting a battle that you are likely to lose (especially if you work in an environment where security is taken seriously).
What then?
You can easily change how Angular produces the URLs and how it interprets them.
When the user navigates inside an Angular application a service named UrlSerializer is used to create a url from a UrlTree. A UrlTree is a data structure that Angular uses internally to represent a parsed URL.
All we need to do is provide an alternative UrlSerilizer
that produces urls without “:”s. We’ll need to replace “:” with something else, for example “,” which is valid.
The easiest way to achieve this is to reuse the original UrlSerializer
but replace “:” with “,” on serialization and “,” back to “:” when parsing urls.
Let’s do that. Create a service named IisCompatibleUrlSerializer
like this:
import { Injectable } from '@angular/core';
import { UrlSerializer, UrlTree } from '@angular/router';
@Injectable()
export class IisCompatibleUrlSerializer extends UrlSerializer
{
constructor(){
super();
}
serialize(tree: UrlTree): string {
return super.serialize(tree).replace(/:/g, ','); //replace ":" with "," in the url
}
parse(url: string): UrlTree {
return super.parse(url.replace(/,/g, ':')); //replace "," back to ":" and let the original UrlSerializer do it's work
}
}
We just need to configure dependency injection in the app module so that our UrlSerializer
is used instead of the original:
@NgModule({
...
providers: [
...
{ provide: UrlSerializer, useClass: IisCompatibleUrlSerializer}
]
})
That url in the example should now look like this:
https://www.imaginaryfootbalwebsite.com/(teams,fc-porto//live,real-juventus)
And it should not be an issue on IIS anymore.