ASP.NET Normalizing Backslashes to Forward Slashes

Short answer

You cannot prevent this behavior as it is hard-coded into IIS.

Investigation

I wanted to investigate this issue by decompiling the runtime and following the code. It's always nice to do that: you learn how the runtime works and sometimes you find the problem. Let's start the journey...

As a starting point, I'm decompiling System.Web with ILSpy, starting at the HttpRuntime class. Navigating through public static void ProcessRequest(HttpWorkerRequest wr), ProcessRequestNoDemand, ProcessRequestNow, ProcessRequestInternal...

Here I want to investigate the following lines:
httpContext = new HttpContext(wr, false);,
httpContext.Response.InitResponseWriter();,
httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);.

In HttpContext.HttpContext(HttpWorkerRequest wr, bool initResponseWriter) many things may cause this:
this.Init(request, response),
new HttpRequest(wr, this).

More precisely HttpContext.GetEurl() (looks suspicious),
Request.InternalRewritePath(VirtualPath.Create(virtualPath), null, true) (safe),
VirtualPath.Create(virtualPath) (looks very suspicious),
virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath); (the infamous!).

Let's write the stack trace that gets us here:

  • HttpRuntime.ProcessRequest... (multiple methods)
  • new HttpContext(wr, false)
  • this.Init(new HttpRequest(wr, this), new HttpResponse(wr, this));
  • if (!string.IsNullOrEmpty(eurl)) (can we prevent entering the if?)
  • this.Request.InternalRewritePath(VirtualPath.Create(virtualPath), null, true);
  • VirtualPath Create(string virtualPath)
  • unsafe static VirtualPath Create(string virtualPath, VirtualPathOptions options)

This last (unsafe) method is doing something to the path. First there is a loop over each character. If a char is below '.', and is different from '/' and is equal to '\', then flag = true. After the loop, if (flag) (src), then an exception may be thrown, and virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath); (src).

Is seems for now that nothing will help us avoid going there (maybe the eurl thing?).

The string FixVirtualPathSlashes(string virtualPath) (src) replaces the backslashes into slashes and if removes the duplicate slashes. Shame.

What about the GetEurl method? When you read src, you find that is will not help you.

Conclusion

The http runtime is killing your backslashes for no documented reason. There is no way you can disable this behavior.

Workaround #1

Now, there must be a way. This guy referencing this page has a workaround. It seems that using the rewrite module, you can put the original URL back into the pipeline. I don't really like this solution because I don't know what's going on exactly. I have another idea...

I did not yet test this thing. Can you?

Searching for workaround #2 (none found)

What if there was a place where the original request path was stored?

Searching HttpRequest, none of the Url.OriginalString, RawUrl, Path, ServerVariables contains the desired value. Not even the _filePath, _path, _queryStringText, _rawUrl, _rewrittenUrl, _url private fields.

Search into the IIS7WorkerRequest, the value is already changed at runtime. I suspect IIS is doing the thing before pushing the request to the ASP.NET runtime. It looks like there is no hope.