# Friday, June 03, 2011

A colleague showed me an interesting problem, the ASP:LinkButton controls in our ASP.Net 4.0 website wouldn't work when it was viewed from a browser control hosted inside an iOS app (ie. The viewer that pops up in the Facebook app when you click on a weblink) What made it weirder was that depending on the order you hit the webpage, either through the Facebook App first, then through Safari. The link button might or might not work.

We ran some test on an ASP.Net 2.0/3.5 site, and the LinkButtons on those sites worked fine. Baffled, I enabled trace.axd on the server to see if there were any differences between requesting for a page through the Facebook App, and through Safari. Immediately I noticed that the User Agent string was different.

When viewed through Safari the User Agent string was something like this.

Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5

When the page was viewed through an app the User Agent looked like this.

Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8H7

Notice that both the Version and Safari values are missing when viewed from the app.

In order to figure out what the difference was when these 2 values are missing, I fed the 2 UserAgent strings through IE9's developer toolbar and viewed the page on the desktop so I had more debugging ability. And to my surprise, when I used the UserAgent string without the Safari moniker, the following code was missing from the output page.

function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

This happens to be the Javascript code which ASP.Net LinkButtons and various other auto postback controls will use to post the form through script on the page. So if the function was missing, then none of the controls would be able to post anything. So I begun my search on why the code was missing when the Safari moniker is missing. And I found the source of the problem, ASP.Net's feature for dynamically changing the way something is rendered based on the client's browser capability. Based on the sample shown on this page on how to see what browser capability was detected, I wrote a quick page which dumps the browser capabilities out with the code below.

        Dim ht As IDictionaryEnumerator = Request.Browser.Capabilities.GetEnumerator
        Response.Write(Request.Browser.Id)
        Response.Write("<br/>")
        Response.Write(Request.UserAgent)
        Response.Write("<br/>")
        Response.Write("----------------<br/>")
        While ht.MoveNext = True
           
            'If ht.MoveNext() = True Then
               
            Response.Write(String.Format("{0}:{1}", ht.Key, ht.Value))
            Response.Write("<br/>" & vbCrLf)
           ' End If
        End While

That's when I realized that WITHOUT the Safari moniker, the default browser capability DISABLES JAVASCRIPT that's why the clientside postback chunk was not delivered to the server.

Interestingly enough even without the Safari moniker, an ASP.Net 2.0 site will still flag the iPhone browser as Javascript capable.

Now that I knew what was missing, what was left was to make a new Browser Definition File which will enable Javascript for the ASP.Net 4.0 site even without the Safari moniker. I basically copied the definition for the Safari browser, but made it so it'll look for the AppleWebKit moniker in the UserAgent instead of the Safari moniker and flag it as Javascript capable.

<browsers>
  <browser id="safariiphone" parentID="mozilla">
    <identification>
      <userAgent match="AppleWebKit"/>   
    </identification>
    <capabilities>
            <capability name="version"                         value="${version}" />
            <capability name="majorversion"                    value="${major}" />
            <capability name="minorversion"                    value="${minor}" />
            <capability name="type"                            value="Safari${major}" />
            <capability name="ecmascriptversion"               value="3.0" />
            <capability name="javascript"                      value="true" />
            <capability name="javascriptversion"               value="1.6" />
            <capability name="w3cdomversion"                   value="1.0" />
            <capability name="tagwriter"                       value="System.Web.UI.HtmlTextWriter" />
            <capability name="cookies"                         value="true" />
            <capability name="frames"                          value="true" />
            <capability name="javaapplets"                     value="true" />
            <capability name="supportsAccesskeyAttribute"      value="true" />
            <capability name="supportsCallback"                value="true" />
            <capability name="supportsDivNoWrap"               value="false" />
            <capability name="supportsFileUpload"              value="true" />
            <capability name="supportsMaintainScrollPositionOnPostback" value="true" />
            <capability name="supportsMultilineTextBoxDisplay" value="true" />
            <capability name="supportsXmlHttp"                 value="true" />
            <capability name="tables"                          value="true" />
        </capabilities>
  </browser>
</browsers>

This file is saved as whateveryouwant.browser then placed under the App_Browsers folder of the website so it can be processed by ASP.Net. This fixed my problem but I was still still curious as to why it worked in ASP.Net 2.0 but NOT 4.0, so I dug around the Browser Definition Files for ASP.Net 2.0 and found this element it used to identify the Safari browser in ASP.Net 2.0

        <identification>
            <userAgent match="AppleWebKit/(?'webversion'\d+)" />
        </identification>

Which means that in ASP.Net 2.0 it's looking for the AppleWebKit moniker to identify Safari browsers, where as in ASP.Net 4.0 the identification element reads this.

        <identification>
            <userAgent match="Safari" />
            <userAgent nonMatch="Chrome" />
        </identification>

Seems like it was trying to avoid clashing with Google Chrome's Useragent which also happens to contain the AppleWebKit moniker but they ended causing problems in certain scenarios.


Note that you can Post As GUEST as well.
blog comments powered by Disqus