Introduction
If you have developed or maintained an ASP.NET 2.0 web site, you may have discovered that ASP.NET 2.0 does not properly recognize and identify the Google Chrome browser. The purpose of this
article is to explain how you can update your ASP.NET 2.0 site to properly recognize and identify Chrome.
Background
ASP.NET developers usually rely on the properties of the .NET HttpBrowserCapabilities
object referenced by the HttpRequest
object's
Browser
property. For example, to obtain the name and version of the browser, you would use code like this:
BrowserName.Text = Request.Browser.Browser;
BrowserVersion.Text = Request.Browser.Version;
or
BrowserName.Text = this.Context.Request.Browser.Browser;
BrowserVersion.Text = this.Context.Request.Browser.Version;
The Problem
The problem is that ASP.NET 2.0 does not properly recognize Chrome. In a standard (unfixed) ASP.NET 2.0 web application, Chrome gets identified
as an "AppleMAC-Safari" version 5.0. This is no surprise since ASP.NET 2.0 was released before Chrome was developed.
Many developers work around this deficiency by rolling their own code to parse the HttpRequest
object's UserAgent
property
which is where the browser and browser version information ultimately comes from anyway. For the version of Chrome I am currently using, the User-Agent string is:
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7"
Seeing the User-Agent string, it is easy to understand why ASP.NET would recognize the Chrome browser as Safari since the code was released prior to the existence of Chrome.
Now you could hack your own solution to this problem but as it turns out, Microsoft developers created a fairly sophisticated system to handle browser recognition
and I decided that for me, the best approach was to fix the already existing system. It turned out to be fairly simple.
The Solution
It's only a simple five step process to get ASP.NET 2.0 to properly recognize Chrome and best of all, it will not even require a code change.
Step 1: Identify your version of the .NET Framework and its directory
In my case, I am using the 64-bit framework, so my framework directory is:
C:\Windows\Microsoft.NET\Framework64\v2.0.50727. If I were using the 32-bit framework,
it would be: C:\Windows\Microsoft.NET\Framework\v2.0.50727. I will refer to this directory as the "framework directory" in the rest of this article.
Step 2: Copy or obtain a chrome.browser file and place it in your framework directory's CONFIG\Browsers directory
When you look in the framework directory's CONFIG\Browsers directory, you may be shocked to see all the .browser files. These .browser files are part
of the sophistiated system I referred to earlier. If you examine a file, you will see it is nothing more than an XML configuration file that uses Regular Expressions
to recognize the browser from the User-Agent string and to set browser capabilities. If you look carefully, you will see that it cleverly uses Regular Expressions
to not only determine the browser, but to obtain the version right from the User-Agent string.
If you have .NET 4.0 installed on the web site's serving machine, you can simply copy the 4.0 framework's chrome.browser file from the 4.0 Config\Browsers directory
to your framework directory's CONFIG\Browsers directory.
If you don't have .NET 4.0 installed, I have a slightly modified version of the chrome.browser file available as a download file for this article. Download and unzip it,
and place it in your framework directory's CONFIG\Browsers directory.
Step 3: Decide if you want Chrome's version reported only as the major and minor version (like 16.0) or the entire version (like 16.0.912.63)
The .NET 4.0 chrome.browser file (unmodified) will only contain the major and minor version in the version number. So for the version of Chrome I am using, 16.0.912.63,
it reports the version as "16.0". This isn't satisfactory for me because I want the full version number. If you use the chrome.browser file attached to this article,
I have modified it to report the full version number.
Below are the relevant lines from my attached chrome.browser file. You can see that the first userAgent
element has been commented out. That is the original
line from the standard .NET 4.0 chrome.browser file and causes the version to be reported as only the major and minor version, "16.0". The second userAgent
element is the one I have added and causes the entire version number to be reported as the version, "16.0.912.63".
<browser id="Chrome" parentID="Mozilla">
<identification>
-->
<userAgent match="Chrome/(?'version'(?'major'\d+)(?'minor'\.[.\d]*))" />
</identification>
If you use my attached chrome.browser file and want the full version, simply leave the file as it is. If you want only the major and minor version reported for the version,
uncomment the commented out userAgent
element and comment out the uncommented userAgent
element.
If you are using your own .NET 4.0 chrome.browser file and want only the major and minor version reported for the version, leave the file as it is. If you want the entire
version number reported, comment out the existing userAgent
element and add the one from the example above.
Don't worry about the comment lines negatively affecting performance because the approach I am taking here won't affect the runtime performance of your system.
Step 4: Edit the mozilla.browser file
While we now have a chrome.browser configuration file to help ASP.NET recognize the Chrome browser, unfortunately, the existing
mozilla.browser file will interfere. We will need to make two modifications to it.
4.1. Make a backup of your existing mozilla.browser file
I have simply made a copy of the file in the same directory and named it mozilla.browser.bak and that doesn't seem to cause any ill effects.
4.2. Update the Gecko section
Find the browser
element whose id
attribute is set to "Gecko". This will begin the configuration section for recognizing
the Gecko browser. Inside the browser
element will be an identification
element and inside it will be a userAgent
element
whose match
attribute is set to "Gecko". Technically, this is saying if you find the string "Gecko" in the User-Agent string,
this is the Gecko browser. If you examine the User-Agent string provided by Chrome and documented near the top of this article, you can see that it does indeed
include the string Gecko and this will cause it to be (mis)recognized as the Gecko browser. The simple solution is to add another userAgent
element
below the existing one and set a nonMatch
attribute to "Chrome". This will tell the system to be Gecko, the User-Agent string must include
"Gecko" but not "Chrome". Here is what that section of the mozilla.browser file should look like after you add your new userAgent
element:
<browser id="Gecko" parentID="Mozilla">
<identification>
<userAgent match="Gecko" />
<userAgent nonMatch="Chrome" />
</identification>
4.3. Update the Safari section
Find the browser
element whose id
attribute is set to "Safari". This will begin the configuration section for recognizing the Safari browser,
which is the one that Chrome is currently getting (incorrectly) recognized as. Notice that farther down in that section is a capability named browser that is set
to "AppleMAC-Safari". This is why Chrome is getting identified as "AppleMAC-Safari". It's all starting to make sense now, don't you think?
Inside the Safari browser
element will be an identification
element and inside it will be a userAgent
element whose match
attribute is set to "AppleWebKit/(?'webversion'\d+)". Technically, this is saying if you find the string "AppleWebKit/" in the User-Agent string,
this is the Safari browser. If you examine the User-Agent string provided by Chrome and documented near the top of this article, you can see that it does indeed include
the string "AppleWebKit/" and this will cause it to be (mis)recognized as the Safari browser. The simple solution is to add another userAgent
element
below the existing one and set a nonMatch
attribute to "Chrome". This will tell the system to be Safari, the User-Agent string must
include "AppleWebKit/" but not "Chrome". Here is what that section of the mozilla.browser file should look like after you add your new userAgent
element:
<browser id="Safari" parentID="Gecko">
<identification>
<userAgent match="AppleWebKit/(?'webversion'\d+)" />
<userAgent nonMatch="Chrome" />
</identification>
Without any modifications to the mozilla.browser file, you will find that initially, Chrome will get recognized as Safari, which is the initial problem. If you only updated
the Safari section to exclude it if the User-Agent string contains "Chrome", the browser would then get (mis)recognized as Gecko. This is why we had to update
both sections of the mozilla.browser file.
Step 5: Run the aspnet_regbrowsers.exe program
Earlier I said that leaving comments in .browser files will not affect runtime performance. The reason is that these .browser configuration files are not read every
time a browser makes an HTTP request. For them to become effective, we must run the aspnet_regbrowsers.exe program which will actually parse them and build
an assembly that then gets linked in at runtime.
So, open a command prompt and cd to your framework directory. In it, you will find the aspnet_regbrowsers.exe program. Simply run the program
and append the -i (install) parameter. This will create and install the ASP.BrowserCapsFactory.dll assembly which is the one that populates
the HttpBrowserCapabilities
object for you when you receive an HTTP request.
You should be aware that running the aspnet_regbrowsers.exe program will cause IIS to restart.
C:\Windows\Microsoft.NET\Framework64\v2.0.50727>aspnet_regbrowsers.exe -i
Utility to compile ASP.Net browser files.
Copyright (C) Microsoft Corporation. All rights reserved.
The browser capabilities assembly ASP.BrowserCapsFactory.dll has been successfully installed.
The results
Once you have run the aspnet_regbrowsers.exe program, you should find that Chrome gets recognized as Chrome and as the appropriate version based
on your decision in Step 3. In my case, the browser is now recognized as Chrome 16.0.912.63, which is exactly what I wanted.
The risks
There are some risks to this approach. If you have an existing application that already supports Chrome, you could find this causes you trouble. Existing code,
third party server controls for example, may expect Chrome to get recognized as "AppleMAC-Safari" and may examine the User-Agent string themselves to determine that
the browser is Chrome. By the system now recognizing Chrome as Chrome, their code may malfunction. In my case, my application didn't already support Chrome so I didn't have this risk.
Also, when you update your system this way, it will affect all web applications using the same version of the .NET Framework (on the same server). So you might be derailing
another web application while fixing yours.
The limitations and an alternative
Obviously this approach requires you to have full control of the server. In a shared server environment, you may not have this level of control. There is another approach
you can take though that should solve this problem. You can add the .browser files to the App_Browsers directory in your project.
I haven't tried this approach so I don't know the details but I wanted to share this option in case you don't have control of the server, or you don't want
to risk breaking other web applications on the server. If I were interested in this approach, I would want answers to the following questions:
Will the .browser files get parsed every time an HTTP request is made? How do they get used? In other words, running the aspnet_regbrowsers.exe
program causes an assembly to get built once. What does just sticking the .browser files in your App_Browsers directory do instead?
Depending on the answer to the previous question, will having comments in the .browser files affect performance? If they get parsed every time a request comes in,
they would. I might want to remove those comments if it would affect performance on a repeated basis, like for every HTTP request.
How will the system react to having the framework mozilla.browser file compiled into the ASP.BrowserCapsFactory.dll assembly while at the same time having
a modified mozilla.browser file in your App_Browsers directory? Will your configuration properly override the one in the ASP.BrowserCapsFactory.dll assembly?
The Conclusion
There are lots of alternative approaches and hacks for determining if the browser is Chrome but since Microsoft created such an elaborate system to do so,
why not use it? This approach is cleaner than most and doesn't require any code changes. Just make sure that you update all your necessary servers
including those in your development, staging, testing, and production environments.