-[Danny Chen]- Blog of an ASP.NET QA tester

Tips and info about Site Navigation, ImageMap, Menu and other cool ASP.NET v2.0 features.

Using Device Filters (and making Menu work with Safari)

Well, as much as I would hate to admit it, there are a few bugs in the Menu control.  For the most part, I'm OK with that.  Some of the bugs are a matter of interpretation and those kind will always exist.  But one really sticks as a thorn in my side. 

  If you specify a DynamicMenuStyle (or LevelSubMenuStyles) on the Menu control, sub menus seem to disappear on "downlevel" browsers.  Well it's really there (if you look at the source) but it's styles make it hidden. 

There are very few workarounds for this bug.

1) Remove runat="server" from <head>
   Cons: HoverStyles are no longer allowed even in rich renderings

2) Remove the DynamicMenuStyle
   Cons: No Dynamic styles allowed even in rich renderings

3) Use device filters to selectively apply styles
   Cons: Still limited in what can be done but uplevel is not affected


note: This bug is being fixed and will eventually be availible through a patch, update, service pack, or etc in the future. Looks like I spoke too soon, this may not get fixed, what you should do instead is see if you can use the CSS Adaptors:  http://www.asp.net/cssadapters/


What are device filters?

Device filters are a somewhat lesser known feature of ASP.NET.  It isn't a feature I actively work with but since there seems to be little information about it, I'll give a brief description of how it works.

In the folder <SysDrive>\Windows\Framework\v2.0.50727\CONFIG\Browsers is a series of .browser files.  These contain some information about every type of web browsing device that ASP.NET knows about.  This information is used for a number of things, among which, choosing whether to render the 'downlevel' menu or the 'uplevel' menu based on which browser is being used. 

Device filters can use the browser information to selectively apply properties to a control depending on which browser is viewing the page.  Here's an example:

<asp:Label runat="server" id="Label1"
  ie:Text="Hello Internet Explorer browser
  Mozilla:Text="Hello Mozilla Browser"
  Text="Hello other browser" />

  This works as you might predict: the IE user gets a message about IE, a Mozilla user gets a message about Mozilla and other browsers, like Opera or Netscape, get the standard message.  One interesting observation is that IE is considered a Mozilla browser.  However, since the IE: filter is more specific, it wins over the Mozilla filter.

  Device filters can only be used declaratively and there is no designer support for them.  The syntax is:  browsername:Property=<value>.  browsername comes from the id field of each recognized browser in the .browsers files.  Any property (that isn't explicitly set as Filterable(false) through an attribute in the control code) can be filtered.  This includes collections and templates:

<asp:Menu runat="server" id="Menu1">
  <ie:Items>
   <asp:MenuItem Text="IE Item" />
  </ie:Items>

  <Items>
    <asp:MenuItem Text="Other Item" />
  </Items>

  <ie:StaticItemTemplate>
     This item is templated only in IE: <%# Eval("Text") %>
  </ie:StaticItemTemplate>

</asp:Menu>

So, can we work around this Menu bug with Device Filters? Kind of.

Ok, so we really can't, but what we can do is limit the damage that any of the workarounds I listed above would do to the uplevel rendering.  After all, they work fine.  The most basic improved workaruond is to recognize that IE 5 and Safari are the two browsers affected by this bug (for some reason Netscape is ok).  So, we could simply not apply a DynamicMenuStyle to those browsers:

<asp:Menu ... >
 <DynamicMenuStyle [my styles here] />
 <Safari:DynamicMenuStyle />
 <IE5:DynamicMenuStyle />
</asp:Menu>

But even better.  It's possible in many cases to produce a less desirable but still functional and styled menu using only DynamicMenuItemStyles. 

<asp:Menu ... >
 <DynamicMenuStyle [my styles here] />
 <Safari:DynamicMenuStyle />
 <IE5:DynamicMenuStyle />
 
 <Safari:DynamicMenuItemStyle ... />
 <IE5:DynamicMenuItemStyle ... />

</asp:Menu>

Comments

Kevin said:

So.. I went to look in CONFIG\Browsers, and there's no reference to the Safari or applekit browsers anywhere in there. What gives?

My boss uses safari. I have an asp:menu which doesn't show the dynamic menus on Safari, and then when I click on one, the whole menu bar disappears.
# November 28, 2005 3:53 PM

Danny Chen said:

Safari is considered a mozilla browser. It is in mozilla.browser and you can find it by looking for <browser id="Safari"
# November 29, 2005 2:05 PM

Leon Jollans said:

So I figured I'd fixed the problem with Safari Win (hopefully Mac too) by checking manually in the page like so:

if(Request.UserAgent.IndexOf("AppleWebKit") > 0 )

{

   Request.Browser.Adapters.Clear();

}

( yeah, it's a hack, but this is just while I'm testing and troubleshooting. )

but then I ran in debug and set a breakpoint on the line above. Imagine my surprise to see that Request.Browser.Adapters.Count was zero to begin with! The code works, but why?

# August 2, 2007 12:30 PM

Andrew Sauder said:

In my apsx code I already have both <DynamicMenuStyle .../> as well as <DynamicMenuItemStyle .../>.  Using the latter of the two examples would I need to specify Safari and IE5 specially for the <DynamicMenuItemStyle .../> tag to work correctly in the browsers?

# August 6, 2007 9:11 AM

Ashok said:

Hi Leon Jollans  its working man.  Thanks

# August 28, 2007 5:37 AM

Debarupa said:

Thanks a lot Leon Jollans!!

After struggling with this for a long time, it works finally!

# September 4, 2007 8:13 PM

Jon said:

How would you accomplish

<Safari:DynamicMenuStyle BackColor="#B8B2CC" />

in codebehind? I need to set this property dynamically.  I've tried <Safari:DynamicMenuStyle BackColor="<%=colourVar%>" /> whith colourVar being both a Colour type and string and neither works???

# September 21, 2007 9:59 AM

Jon said:

since <%= is a response.write which won't work for setting a property i tried using <%# for databinding and doing a call to Page.Databind but the menu control doesn't support the databind event so this goes out the window too... I just can't figure out how to make use of a browser device filter in code behind... any guidance will be appreciated.

# September 21, 2007 10:19 AM

AnHund said:

Thanks. Great - It works perfectly :-)

# November 11, 2007 8:40 AM

ag said:

Thanks for sharing Leon

It woks like a charm!!

# December 7, 2007 1:37 PM

Perrin Mobley said:

The best bet would be use this type of logic on the page load.

protected void Page_Load(object sender, EventArgs e)

   {

       if (!Page.IsPostBack)

       {

           //This is to resolve a bug in Safari with the menu control

           if (Request.ServerVariables["http_user_agent"].Contains("Safari"))

           {

               MenuItemStyle style = Menu1.StaticMenuItemStyle;

               style.BackColor = System.Drawing.Color.FromName("#333333");

               style.HorizontalPadding = Unit.Pixel(15);

               style.ForeColor = System.Drawing.Color.FromName("#ffffff");

               style.Font.Bold = true;

               style.Height = Unit.Pixel(25);

           }

           else

           {

               /*

                    <DynamicHoverStyle Font-Underline="True" BackColor="#333333" ForeColor="#ffffff" Font-Bold="false"/>                              

                 */

               MenuItemStyle staticMenuItemStyle = Menu1.StaticMenuItemStyle;

               staticMenuItemStyle.BackColor = System.Drawing.Color.FromName("#333333");

               staticMenuItemStyle.HorizontalPadding = Unit.Pixel(15);

               staticMenuItemStyle.ForeColor = System.Drawing.Color.FromName("#ffffff");

               staticMenuItemStyle.Font.Bold = true;

               staticMenuItemStyle.Height = Unit.Pixel(25);

               /*

                  <DynamicMenuStyle BorderColor="#999" BorderWidth="1px" BackColor="#EAEAEA" HorizontalPadding="2px" VerticalPadding="2px"/>      

               * */

               SubMenuStyle subMenuStyle = Menu1.DynamicMenuStyle;

               subMenuStyle.BorderColor = System.Drawing.Color.FromName("#999");

               subMenuStyle.BorderWidth = Unit.Pixel(1);

               subMenuStyle.BackColor = System.Drawing.Color.FromName("#EAEAEA");

               subMenuStyle.HorizontalPadding = Unit.Pixel(2);

               subMenuStyle.VerticalPadding = Unit.Pixel(2);

               subMenuStyle.ForeColor = System.Drawing.Color.FromName("#ffffff");

               /*<DynamicMenuItemStyle Height="23px" HorizontalPadding="4px"/> */

               MenuItemStyle dynamicMenuItemStyle = Menu1.DynamicMenuItemStyle;

               dynamicMenuItemStyle = Menu1.DynamicMenuItemStyle;

               dynamicMenuItemStyle.HorizontalPadding = Unit.Pixel(4);

               dynamicMenuItemStyle.Height = Unit.Pixel(23);

           }

       }

   }

# January 9, 2008 9:18 PM

Hiren Patel said:

 if (Request.UserAgent.IndexOf("AppleWebKit") > 0)

       {

           Request.Browser.Adapters.Clear();

       }

-This code is working like charm

# February 5, 2008 4:23 AM

Dan Satria said:

I have two websites with popup menu.  The one using ASP:Menu doesn't work with Safar (http://www.fbc-rockville.org).  The other uses AjaxToolkit:HoverMenuExtender and it works on Safari.  Moreover, the one using ASP.NET Ajax is easier to customize.  I added an ability to click on the background of the menu.  I tried to do that with ASP:Menu, but simply couldn't do that.

Well, in a few more weeks, I'll convert both sites to use ASP.NET Ajax HoverMenu.

# February 23, 2008 11:07 PM

Dan Satria said:

Sorry, I forgot to add the site that works with ASP.NET AJax HoverMenu: weecenter.fbc-rockville.org.

# February 23, 2008 11:16 PM

Jarrod said:

if (Request.UserAgent.IndexOf("AppleWebKit") > 0)

      {

          Request.Browser.Adapters.Clear();

      }

--Great fix!

# March 7, 2008 8:32 PM

Mary said:

I'm trying to fix the menu in Safari and Opera.  I've tried several suggestions; however, none completely fix my problem?

# April 17, 2008 12:21 PM

Chris said:

The browser adapter clear worked.  Great tip, thanks!!!

# May 7, 2008 3:16 PM

Jes said:

Leon Jollans

Thanks for sending the code.

It works fine....

Jes

# July 8, 2008 1:15 PM

Eric said:

I don't know why that works but than you Leon

# July 9, 2008 12:36 PM

Jonathon said:

Thanks for the post, Danny. FYI - I found that the damage caused by this bug can be further limited by created "jump pages" for each of your top level STATIC menu items, then using device filtrs to limit the dynamic display levels to "0" for the unsupported browsers.

<asp:Menu ID="MainMenu" runat="server"

  DataSourceID="XmlMenu"

  MaximumDynamicDisplayLevels="2"

  Safari:MaximumDynamicDisplayLevels="0"

  IE5:MaximumDynamicDisplayLevels="0"

  Orientation="Horizontal"

  StaticDisplayLevels="2"

  DynamicDisplayLevels="2"

  Safari:DynamicDisplayLevels="0"

  IE5:DynamicDisplayLevels="0"> . . . </asp:Menu>

# August 7, 2008 2:32 PM

Jeremy said:

I followed the advice I found here: http://www.big-o.org/?p=20.  Now my asp:menu looks just fine in Safari.  I couldn't get this solution to work correctly, but that could be because I am a n00b at .Net.

Is there a compelling reason not to do it by editing the .browser file?

# August 22, 2008 3:48 PM

John said:

Jeremy :

I too followed the advice there and amended the .browser file. This worked fine on my own machine but still presented the problem when accessing the Web page from Safari on any other machine which had not had the .browser file amended.

This is a very, very compelling reason not to use this as a fix, right ?

# August 27, 2008 6:12 AM

Anviti said:

if (Request.UserAgent.IndexOf("AppleWebKit") > 0)

     {

         Request.Browser.Adapters.Clear();

     }

This worked great! Thanks a lot.

# September 7, 2008 10:11 PM

Dhiman said:

Leon.

Thanks!! It works - with Safari and Google Chrome.

# September 16, 2008 3:46 PM

Ajay said:

The clearing of Adapters works... but the question remains why?

# September 24, 2008 2:55 AM

Ryan said:

Leon, code worked like a charm! saved me a lot of headache!   Please note (for any newbies) that  it helps to wrap Leon's code in <%  [code here] %>

The same bit of code works with Google Chrome (which seems to render pages the same way as Safari... why? I don't know... maybe someone will post an answer.)

# September 29, 2008 6:40 PM

Ryan said:

Sorry Dhiman, did not see your post with regards to Google Chrome... I stopped reading right before I got to your post.

Also, to answer Ajay's question as to WHY it works if you clear the adapters, well .NET will attempt to export truly W3C compliant code for "downlevel" and other older browsers, meaning browsers that can't render XHTML.   And for some reason .NET thinks Safari is a "downlevel" browser (as well as Google Chrome, which for some reason behaves exactly like Safari when rendering web pages).  

It's a very Microsoft move (if you ask me), meaning that a lot of the javascript heavy and XHTML code generated by .NET is meant to work with Internet Explorer (such as the dynamic menus and client-side validation).  And when .NET first came out, all other browsers were kind of up the creek in terms of the way .NET rendered code.  Fortunately, most other browsers have come to support the same sort of things as IE over the past couple years (such as the Microsoft Document Object Model)... a sort of "getting on the knees" move by other browsers... but necessary to survive.

Check this link for more information:

msdn.microsoft.com/.../x3k2ssx2.aspx

# September 29, 2008 7:16 PM

Zé said:

Thanks Leon. Your solutions works with just 3 lines of code.

# November 6, 2008 5:16 AM

Sam said:

The following code work fine. The menu displayed correctly in Safari browser; however I got error "Error Message:Object reference not set to an instance of an object."  Anyone know what problem is this please let know. Thank  you

if (Request.UserAgent.IndexOf("AppleWebKit") > 0)

    {

        Request.Browser.Adapters.Clear();

    }

# November 18, 2008 4:08 PM

Naveen said:

Great man.........

Request.Browser.Adapters.Clear();

it works like any thing.........

thanks for sharing it

# December 17, 2008 1:12 AM

tom said:

Request.Browser.Adapters.Clear();

did the trick for me.  Thanks for sharing.

# January 23, 2009 3:42 PM

cfcf said:

no code required to make menu items work in safari:

If you put ClientTarget="uplevel" in the page directive <%@ Page ClientTarget="uplevel" ......%> Safari works totally!!!!!

# May 21, 2009 3:09 PM

Joshua Johnson said:

UPDATE Since I initially wrote this post, I found another way of solving the Safari / Chrome ASP.NET

# June 22, 2009 1:09 PM

Brijesh Bosamiya said:

Thanks to all of you, It is working in both the browser

Safari and Chrome.

If you put ClientTarget="uplevel" in the page directive <%@ Page ClientTarget="uplevel" ......%> Safari works totally!!!!!

# July 10, 2009 7:44 AM

Dave Kilroy said:

Thanks Leon - great fix

# July 19, 2009 6:27 PM

Mike D said:

The "ClientTarget" directive is an elegant solution to an annoying bug.  Kudos. ( Now if I just understood WHY it works . . . )

# August 17, 2009 2:09 PM

ASP.NET menu on Safari not linking, grrrr « John Jakubowski said:

Pingback from  ASP.NET menu on Safari not linking, grrrr &laquo; John Jakubowski

# October 21, 2009 12:26 AM

Cell blocker said:

By the way, the best chance to secure yourself from annoying phone calls and spy gadgets is to use <a href="www.jammer-store.com/">Cell jammer</a>. Block cell phones in your surrounding.

# October 26, 2009 10:32 AM

Monica Bellucci Video said:

"You do your best work if you do a job that makes you happy." - Bob Ross  n <a href="boxesandarrows.com/.../90757-monica_bellucci">Monica Bellucci</a>

# November 8, 2009 2:15 AM
Thanks for sharing your feedback! If your feedback doesn't appear right away, please be patient as it may take a few minutes to publish - or longer if the blogger is moderating comments.
Leave a Comment

(required) 

(required) 

(optional)

(required)