Category Archives: 2681

Internet Explorer 10 User Agent Strings On Windows 8 64bit

Internet Explorer 10 is the web browser Microsoft is delivering with Windows 8.

According to its different usages and modes, its user agent string is as follows:

Application Environment 32/64 bit User Agent String
Internet Explorer Metro - Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)
Javascript Application Metro - Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0;)
C#/VB Application Metro 32bit Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
C#/VB Application Metro 64bit Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)
Internet Explorer Desktop 32bit
Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Internet Explorer Desktop 64bit(1) Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)
WPF Application Desktop 32bit Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; Tablet PC 2.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Zune 4.7; InfoPath.3)
WPF Application Desktop 64bit Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C; Tablet PC 2.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Zune 4.7; InfoPath.3)
(1) Needs to be enabled for each security zone.

Analyzing the above table I conclude that:

  1. Metro Internet Explorer is always a 64bit application on 64bit Windows 8.
  2. Javascript Metro Style Applications don’t announce if they are 32bit or 64bit.
    1. They look like they are running on a 32bit Windows 8.
  3. Desktop Internet Explorer retains the same behavior introduced with Internet Explorer 8.
  4. Metro Style C#/VB Applications hosting the web browser (WebView control) exhibit the same behavior as Internet Explorer.
  5. Desktop WPF Applications hosting the web browser (WebBrowser control) retain the same behavior introduced with Internet Explorer 8.

Is Your ASP.NET Development Server Not Working?

Since Visual Studio 2005, Visual Studio comes with a development web server: the ASP.NET Development Server.

I’ve been using this web server for simple test projects since than with Visual Studio 2005 and Visual Studio 2008 in Windows XP Professional on my work laptop and Windows XP Professional, Windows Vista 64bit Ultimate and Windows 7 64bit Ultimate at my home desktop without any problems (apart the known custom identity problem, that is).

When I received my new work laptop, I installed Windows Vista 64bit Enterprise and Visual Studio 2008 and, for my surprise, the ASP.NET Development Server wasn’t working.

I started looking for differences between the laptop environment and the desktop environment and the most notorious differences were:

System

Laptop

Desktop

SKU

Windows Vista 64bit Enterprise

Windows Vista 64bit Ultimate

Joined to a Domain

Yes

No

Anti-Virus

McAffe

ESET

After asserting that no domain policies were being applied to my laptop and domain user and nothing was being logged by the ant-virus, my suspicions turned to the fact that the laptop was running an Enterprise SKU and the desktop was running an Ultimate SKU. After having problems with other applications I was sure that problem was the Enterprise SKU, but never found a solution to the problem. Because I wasn’t doing any web development at the time, I left it alone.

After upgrading to Windows 7, the problem persisted but, because I wasn’t doing any web development at the time, once again, I left it alone.

Now that I installed Visual Studio 2010 I had to solve this. After searching around forums and blogs that either didn’t offer an answer or offered very complicated workarounds that, sometimes, involved messing with the registry, I came to the conclusion that the solution is, in fact, very simple.

When Windows Vista is installed, hosts file, according to this contains this definition:

127.0.0.1       localhost
::1             localhost


This was not what I had on my laptop hosts file. What I had was this:



#127.0.0.1       localhost
#::1             localhost


I might have changed it myself, but from the amount of people that I found complaining about this problem on Windows Vista, this was probably the way it was.



The installation of Windows 7 leaves the hosts file like this:



#127.0.0.1       localhost
#::1             localhost


And although the ASP.NET Development Server works fine on Windows 7 64bit Ultimate, on Windows 7 64bit Enterprise it needs to be change to this:



127.0.0.1       localhost
::1             localhost


And I suspect it’s the same with Windows Vista 64bit Enterprise.

Detecting User Regional Settings In The Web Browser

Recently, a friend of mine asked me something like: “How do I get the user’s regional settings for a request to a web server?”

As far as I know, web browser only send an Accept-Language HTTP header and nothing more. You can take this and use the default regional settings for that language but, if your user is anything like me, you’ll be wrong.

So, what’s the problem of getting it wrong?

If you are just generating HTML and keep it consistent, nothing’s wrong. But what if your user wants to copy some numeric and/or date values to, say, Excel? Or if you want to export some data as a CSV file?

A solution

Going through the JScript Language Reference, I found that both Number and Date have locale related toString methods and I started playing with them.

Numeric format settings

To the numeric format settings, first we need a number that will behave in a predictable manner in any culture (“any culture” means “any culture I know”) and for all possible settings and convert it to a string using the toLocaleString method:

var number = 111111111.111111111;
var numberString = number.toLocaleString();


(With my regional settings, numberString becomes 111 111 111.11)



To get the decimal separator, all it takes is getting the first non 1 from the end:



var decimalSeparator;
var decimalDigits;
for (var i = numberString.length - 1; i >= 0; i--) {
    var char = numberString.charAt(i);
    if (char != "1") {
        decimalSeparator = char;
        decimalDigits = numberString.length - i - 1;
        break;
    }
}


And if you count how many 1s were skipped, we get the number of decimal digits.



In a similar way, the first non 1 will be the digit grouping separator:



var groupSeparator;
for (var i = 0; i < numberString.length; i++) {
    var char = numberString.charAt(i);
    if (char != "1") {
        groupSeparator = char;
        break;
    }
}


Now that we have the digit grouping separator, we can get the digit groups (these groups might not all have the same size):



var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
for (g in digitGrouping) {
    digitGrouping[g] = digitGrouping[g].length;
}


Date and time settings


Date and time values are more difficult to parse and you might not need all information. So, I’ll just get the value and let the parsing to you:



var dateTime = new Date(9999, 11, 31, 23, 30, 45);
dateTimeString = dateTime.toLocaleString();


List settings


The last setting is the list separator (very useful for those CSV files):



var list = ["a", "b"];
listSeparator = list.toLocaleString().substring(1, 2);


Test page



Here is a test page the get all these settings:



<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Test Page</title>
    <style type="text/css">
        label  { width: 8em; text-align: right; padding-right: 0.5em; white-space: nowrap; }
        span { border: 1px solid; white-space: nowrap; }
    </style>
    <script type="text/javascript">
        function init() {
            document.all.userLanguage.innerText = window.navigator.userLanguage;
            document.all.systemLanguage.innerText = window.navigator.systemLanguage;

            // Decimal separator and decimal digits
            var number = 111111111.111111111;
            var numberString = (111111111.111111111).toLocaleString();

            var decimalSeparator;
            var decimalDigits;
            for (var i = numberString.length - 1; i >= 0; i--) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    decimalSeparator = char;
                    decimalDigits = numberString.length - i - 1;
                    break;
                }
            }
            document.all.decimalSeparator.innerText = decimalSeparator;
            document.all.decimalDigits.innerText = decimalDigits;

            // Digit grouping separator and digit goups
            var groupSeparator;
            for (var i = 0; i < numberString.length; i++) {
                var char = numberString.charAt(i);
                if (char != "1") {
                    groupSeparator = char;
                    break;
                }
            }
            document.all.groupSeparator.innerText = groupSeparator;

            var digitGrouping = numberString.substring(0, numberString.length - decimalDigits - 1).split(groupSeparator);
            for (g in digitGrouping) {
                digitGrouping[g] = digitGrouping[g].length;
            }
            document.all.digitGrouping.innerText = digitGrouping.toString();

            // Date format
            var dateTime = new Date(9999, 11, 31, 23, 30, 45);
            var dateTimeString = dateTime.toLocaleString();
            document.all.dateTimeFormat.innerText = dateTimeString;

            // List separator
            var list = ["a", "b"];
            var listSeparator = list.toLocaleString().substring(1, 2);
            document.all.listSeparator.innerText = listSeparator;
        }
    </script>
</head>
<body onload="init()">
    <p><label for="userLanguage">User language:</label><span id="userLanguage"></span></p>
    <p><label for="systemLanguage">System language:</label><span id="systemLanguage"></span></p>
    <p><label for="decimalSeparator">Decimal separator:</label><span id="decimalSeparator"></span></p>
    <p><label for="decimalDigits">Decimal digits:</label><span id="decimalDigits"></span></p>
    <p><label for="groupSeparator">Digit separator:</label><span id="groupSeparator"></span></p>
    <p><label for="digitGrouping">Digit grouping:</label><span id="digitGrouping"></span></p>
    <p><label for="dateTimeFormat">Date/Time format:</label><span id="dateTimeFormat"></span></p>
    <p><label for="listSeparator">List separator:</label><span id="listSeparator"></span></p>
</body>
</html>

Defining Document Compatibility In Internet Explorer 8

The procedures to define document compatibility in Internet Explorer 8 are well documented here, but I’ve seem many developers and system administrators that are not aware of this.

Although you can (and should) define the document compatibility your web pages were designed to, if you don’t, Internet Explorer 8 and the Web Browser Control will default to these compatibility modes:

Application

Intranet

Internet

Internet Explorer 8 IE7 mode IE8 mode
Application hosting the Web Browser Control IE7 mode IE7 mode

If you notice the table above, by default, only Internet Explore 8 will present itself to the as Internet Explorer 8 and only to Internet sites.

The way Internet Explorer (and any other browser) presents itself the web servers is using its user agent string:

Mode

User Agent String

IE7 Mozilla/4.0 (compatible; MSIE 7.0; …; Trident/4.0; …)
IE8 Mozilla/4.0 (compatible; MSIE 8.0; …; Trident/4.0; …)

(If you are curious about the history of the user-agent string, read the History of the user-agent string)

Microsoft did this to keep compatibility with legacy applications used by enterprises (large and small) but this brings a few issues to development and testing.

If you are building a public web site for Internet Explorer 8, you might see the right thing on your development machine, but the quality assurance team will see the site as if it were an Internet Explorer 7 if the version they are testing is on the intranet. If the the web site you are developing is going to be accessed from an application hosting the Web Browser Control and you don’t test on that application, you are not going to see the same thing.

To know how is your browser presenting itself to the web server in the internet, there are several web sites that will show information about the user-agent string (like http://www.useragents.org/) and it helps to have the same thing in your intranet. If you want to build such a web application using ASP.NET, you can use the UserAgent property of the HttpRequest class (or the Browser property for more detailed information).

This type of information is also available in Internet Explorer in the navigator object.

Giorgio Sardo has a few functions to detect Internet Explorer 8 but you might also want to develop a diagnostics page (or part) to show the web browser features, something like this:

<fieldset id="webBrowserInfo">
    <legend>Web Browser</legend>
    <table border="1">
        <tr>
            <td class="label" style="width: 100px"><label for="webBrowser$userAgent">userAgent</label> </td>
            <td class="value" colspan="3"><span id="webBrowser$userAgent"></span></td>
        </tr>
        <tr>
            <td class="label" style="width: 100px"><label for="webBrowser$appVersion">appVersion</label></td>
            <td class="value"><span id="webBrowser$appVersion"></span></td>
            <td class="label"><label for="webBrowser$appMinorVersion">appMinorVersion</label></td>
            <td class="value"><span id="webBrowser$appMinorVersion"></span></td>
        </tr>
        <tr>
            <td class="label" style="width: 100px"><label for="webBrowser$appCodeName" style="width: 600px">appCodeName</label></td>
            <td class="value" colspan="3"><span id="webBrowser$appCodeName"></span></td>
        </tr>
        <tr>
            <td class="label"><label for="webBrowser$appName">appName</label></td>
            <td class="value" colspan="3"><span id="webBrowser$appName"></span></td>
        </tr>
        <tr>
            <td class="label" style="width: 100px"><label for="webBrowser$userLanguage">userLanguage</label></td>
            <td class="value"><span id="webBrowser$userLanguage"></span></td>
            <td class="label" style="width: 100px"><label for="webBrowser$cpuClass">cpuClass</label></td>
            <td class="value"><span id="webBrowser$cpuClass"></span></td>
        </tr>
        <tr>
            <td class="label"><label for="webBrowser$systemLanguage">systemLanguage</label></td>
            <td class="value"><span id="webBrowser$systemLanguage"></span></td>
            <td class="label"><label for="webBrowser$platform">platform</label></td>
            <td class="value"><span id="webBrowser$platform"></span></td>
        </tr>
        <tr>
            <td class="label"><label for="webBrowser$browserLanguage">browserLanguage</label></td>
            <td class="value"><span id="webBrowser$browserLanguage"></span></td>
            <td class="label" style="width: 100px"><label for="webBrowser$cookieEnabled">cookieEnabled</label></td>
            <td class="value" colspan="5"><span id="webBrowser$cookieEnabled"></span></td>
        </tr>
    </table>
</fieldset>

<script type="text/javascript">
    document.getElementById("webBrowser$userAgent").innerHTML = window.navigator.userAgent;
    document.getElementById("webBrowser$appCodeName").innerHTML = window.navigator.appCodeName;
    document.getElementById("webBrowser$appMinorVersion").innerHTML = window.navigator.appMinorVersion;
    document.getElementById("webBrowser$appName").innerHTML = window.navigator.appName;
    document.getElementById("webBrowser$appVersion").innerHTML = window.navigator.appVersion;
    document.getElementById("webBrowser$browserLanguage").innerHTML = window.navigator.browserLanguage;
    document.getElementById("webBrowser$cookieEnabled").innerHTML = window.navigator.cookieEnabled;
    document.getElementById("webBrowser$cpuClass").innerHTML = window.navigator.cpuClass;
    document.getElementById("webBrowser$platform").innerHTML = window.navigator.platform;
    document.getElementById("webBrowser$systemLanguage").innerHTML = window.navigator.systemLanguage;
    document.getElementById("webBrowser$userLanguage").innerHTML = window.navigator.userLanguage;
</script>

Web Site Globalization With ASP.NET Routing

For those who don’t know, I have this web site http://PauloMorgado.NET/ that I use both as a web presence besides my blogs and a playfield.

Because I write both in English and Portuguese, I wanted the web site to have both English and Portuguese versions. This is easily accomplished by using ASP.NET Globalization and Localization.

But I wanted to do more than guessing the user’s language form her/his web browser languages. I wanted something like the MSDN and TechNet web sites have where the culture is embedded in the URL which makes it easy for the user to choose in which language she/he wants to see the web site.

With the release of the ASP.NET Routing, this is as easy as writing a custom route handler that sets the culture for the request and returns the requested page handler.

Something like this:

public class GlobalizationRouteHandler : global::System.Web.Routing.IRouteHandler
{
    System.Globalization.CultureInfo culture;
    System.Globalization.CultureInfo uiCulture;

    public GlobalizationRouteHandler(System.Globalization.CultureInfo culture)
        : this(culture, culture)
    {
    }

    public GlobalizationRouteHandler(CultureInfo culture, CultureInfo uiCulture)
    {
        if (culture == null)
        {
            throw new ArgumentNullException("cultureInfo", "cultureInfo is null.");
        }

        if (uiCulture == null)
        {
            throw new ArgumentNullException("uiCulture", "uiCulture is null.");
        }

        this.culture = culture;
        this.uiCulture = uiCulture;
    }

    private GlobalizationRouteHandler()
    {
    }

    #region IRouteHandler Members

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        Thread.CurrentThread.CurrentCulture = this.culture;
        Thread.CurrentThread.CurrentUICulture = this.uiCulture;

        string path = "~/" + (requestContext.RouteData.Values["path"] as string);

        var physicalPath = requestContext.HttpContext.Server.MapPath(path);
        if (System.IO.Directory.Exists(physicalPath))
        {
            path = VirtualPathUtility.Combine(path, "Default.aspx");
        }

        var httpHandler = BuildManager.CreateInstanceFromVirtualPath(path, typeof(IHttpHandler)) as IHttpHandler;

        return httpHandler;
    }

    #endregion
}


And now it’s only a matter of registering the handled cultures:



routes.Add("en", new Route("en/{*path}", new GlobalizationRouteHandler(CultureInfo.GetCultureInfo("en-US"))));
routes.Add("pt", new Route("pt/{*path}", new GlobalizationRouteHandler(CultureInfo.GetCultureInfo("pt-PT"))));

Coupling ASP.NET Session State With Forms Authentication

Today I was talking with João about a way to couple the lifetime of the ASP.NET session state with the lifetime of Forms Authentication ticket.

My idea was to store the session ID in the UserData property of the forms authentication ticket upon logon and retrieve it with a custom session ID manager.

The login code would be something like this:

protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
    bool isPersistent = this.Login1.RememberMeSet;
    string username = this.Login1.UserName;
    var ticket = new FormsAuthenticationTicket(
        0,
        username,
        DateTime.Now,
        DateTime.Now.AddMinutes(2),
        isPersistent,
        Guid.NewGuid().ToString("N"));

    // Encrypt the ticket.
    var encryptedTicket = FormsAuthentication.Encrypt(ticket);

    // Create the cookie.
    this.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));

    // Redirect back to original URL.
    this.Response.Redirect(FormsAuthentication.GetRedirectUrl(username, isPersistent));
}



For the purpose of this test I am using a Guid as the session ID.




The session ID manager will return this session ID when queried by the session state HTTP module:




public class SessionIdManager : global::System.Web.SessionState.ISessionIDManager
{
    #region ISessionIDManager Members

    public string CreateSessionID(HttpContext context)
    {
        return GetDummySessionIdOrRedirectToLoginPage(context);
    }

    public string GetSessionID(HttpContext context)
    {
        return GetSessionIdFromFormsIdentity(context);
    }

    public void Initialize()
    {
    }

    public bool InitializeRequest(HttpContext context, bool suppressAutoDetectRedirect, out bool supportSessionIDReissue)
    {
        supportSessionIDReissue = false;
        return GetSessionIdFromFormsIdentity(context) == null;
    }

    public void RemoveSessionID(HttpContext context)
    {
    }

    public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
    {
        redirected = false;
        cookieAdded = false;
    }

    public bool Validate(string id)
    {
        return true;
    }

    #endregion

    private static string GetSessionIdFromFormsIdentity(HttpContext context)
    {
        var identity = context.User != null ? context.User.Identity as FormsIdentity : null;

        if ((identity == null) || (identity.Ticket == null) || string.IsNullOrEmpty(identity.Ticket.UserData))
        {
            return GetDummySessionIdOrRedirectToLoginPage(context);
        }
        else
        {
            return identity.Ticket.UserData;
        }
    }

    private static string GetDummySessionIdOrRedirectToLoginPage(HttpContext context)
    {
        if (context.Request.CurrentExecutionFilePath.Equals(FormsAuthentication.DefaultUrl, StringComparison.OrdinalIgnoreCase)
                        || context.Request.CurrentExecutionFilePath.Equals(FormsAuthentication.LoginUrl, StringComparison.OrdinalIgnoreCase))
        {
            return Guid.NewGuid().ToString("N");
        }
        else
        {
            FormsAuthentication.RedirectToLoginPage();
            return null;
        }
    }
}



NOTE: Although this might work, it’s just an intellectual exercise and wasn’t fully tested.