.NET

No IE6 ActionFilter for ASP.NET MVC

*** Update ***: I’ve also created a cheap jQuery plugin for this as well. See the bottom of the post

There’s a lot of talk on Twitter today about IE6. I’m building a web application in my free time and I didn’t want to support IE6. Will I lose users? Yes, a few, but I don’t care. To try to help circumvent someone with IE6 hitting my site I wrote an ASP.NET MVC Action Filter to block anything less than IE7. It’s rather crude, but it works.

Please note, I’ve only tested this with IE Tester, so real IE6 users… well YMMV.

Here’s the code:


public class NoIe6Attribute : ActionFilterAttribute
 {

 public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
 var browser = filterContext.HttpContext.Request.Browser.Browser;
 var major = filterContext.HttpContext.Request.Browser.MajorVersion;

 if(browser.ToLowerInvariant() == "ie" && major <= 6)
 {
 filterContext.Result = new ContentResult {Content = Ie6NotSupportedHtml};
 }

 }

 public string Ie6NotSupportedHtml
 {
 get
 {
 return @"<html>
<head>
 <title>IE6 Not Supported</title>
 <style>

 body {
 background-color: #111;
 color: #FFF;
 font-family: Trebuchet MS, 'Helvetica Neue', Arial, sans-serif;
 font-weight: light;
 letter-spacing: 1px;
 line-height: 1.5;
 }

 h1 {
 font-size: 60px;
 font-weight: bold;
 line-height: 1;
 margin: 40px 10px 10px;
 text-align: center;
 }

 h2 {
 font-size: 20px;
 font-weight: normal;
 line-height: 1.2;
 margin-bottom: 20px;
 margin-right: 10px;
 margin-left: 10px;
 text-align: center;
 }

 a:link,
 a:visited,
 a:hover,
 a:focus,
 a:active {
 border: none;
 color: #5EA9FF;
 font-weight: bold;
 letter-spacing: 1.5px;
 text-decoration: none;
 }

 .container {
 margin: 0 auto;
 width: 540px;
 }

 </style>
 </head>
 <body>
 <divcontainer>
 <h1>Sorry, we do not support IE6.</h1>
 <h2>Please <a href=""http://www.microsoft.com/ie"" target=""_blank"" title=""Upgrade IE"">upgrade your browser</a> to a recent version of Internet Explorer</h2></div>
 </body>
</html>";
 }
 }

 }

Explaining It

It’s crude, but so it IE6, so we’re fighting fire with fire here. You don’t need to include any fancy HTML or whatever. If this filter finds that you’re running IE6, it will change the ViewResult to a ContentResult and stuff some hard-coded HTML into that result.

Result

This is what you’ll see if you visit an action/controller/etc that is decorated with this attribute. (click for bigger image)

How to Use It

Personally I like to block _all_ IE6 access (for my current project), so I throw it on my BaseController (the class all of my controllers inherit from). You can also slap it on an action, or you can slap it on a single controller if you’re doing some wonky stuff in an individual controller.


[NoIe6]
public class HomeController : Controller
{
 public ActionResult Index()
 {
 ViewData["Message"] = "Welcome to ASP.NET MVC!";

 return View();
 }

}

// or on an action

public class HomeController : Controller
{
 [NoIe6]
 public ActionResult Index()
 {
 ViewData["Message"] = "Welcome to ASP.NET MVC!";

 return View();
 }

}

Enjoy.

Let me know if you see any errors with it. I whipped it up a few weeks ago and never touched it since.

Update

jQuery Plug-In

Some people said they would rather have this as a jQuery plug-in. I understand that, but I still prefer to go on the server side. That way I don’t get the entire HTML of the page sent back to the user. They get what I give them when they use IE6. However, not everyone thinks like me. So here is a jQuery plugin for it.

Download the Plug-in: jquery.noie6.js

Code:


/**
 * jQuery noie6
 * A jQuery plugin to display text for IE6 Users.
 *
 * v0.0.1 - 31 March 2010
 *
 * Copyright (c) 2010 Donn Felker (http://twitter.com/donnfelker)
 * Dual licensed under the MIT and GPL licenses.
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.opensource.org/licenses/gpl-license.php
 *
 * Use $.noie6();
 *
 **/
;jQuery.noie6 = function (x) {
 // Stolen from thickbox
 if (typeof document.body.style.maxHeight === "undefined") {
 $("body").html("<style>        body {background-color: #111;color: #FFF;font-family: Trebuchet MS, 'Helvetica Neue', Arial, sans-serif;            font-weight: light;            letter-spacing: 1px;            line-height: 1.5;        }        h1 {    color:#FFF;        font-size: 60px;            font-weight: bold;            line-height: 1;            margin: 40px 10px 10px;            text-align: center;        }        h2 {    color: #FFF;        font-size: 20px;            font-weight: normal;            line-height: 1.2;            margin-bottom: 20px;            margin-right: 10px;            margin-left: 10px;            text-align: center;        }        a:link,        a:visited,        a:hover,        a:focus,        a:active {            border: none;            color: #5EA9FF;            font-weight: bold;            letter-spacing: 1.5px;            text-decoration: none;        }        .container {            margin: 0 auto;            width: 540px;        }    </style><div class=\"container\">        <h1>Sorry, we do not support IE6.</h1>        <h2>Please <a href=\"http://www.microsoft.com/ie\" target=\"_blank\" title=\"Upgrade IE\">upgrade your browser</a> to a recent version of Internet Explorer</h2>    </div>");
 }
};

Usage:


<script>

$(function() { $.noie6(); }

</script>

This will give you the same screen I showed above (black background, white text, etc). However, this will not get run until document.ready _and_ you will have a whole goop of HTML that came down with the page. Note: The browser detection is from some Thickbox code.

ASP.NET MVC REST API WINDSOR CONTROLLER FACTORY

I’m using the ASP.NET MVC REST Toolkit for a REST API I’m building for a mobile infrastructure. Long story short, it will be responsible for service hundreds of thousands (if not millions) of users through their mobile phones and rich applications (Android, iPhone and eventually the WP7 Phones).

The one thing that the Toolkit did not provide out of the box was a way to use a container in the controller factory. Therefore I altered the ResourceControllerFactory to use Windsor Container as its default container. The code works great and I’m able to utilize dependency injection all throughout my app now.

The code for this is below:

//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------

using Castle.MicroKernel;

namespace System.Web.Mvc.Resources
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.Net;
    using System.Net.Mime;
    using System.Text;
    using System.Web.Routing;

    /// <summary>
    /// Specialized ControllerFactory that augments the base controller factory to make it RESTful - specifically, adding
    /// support for multiple formats, HTTP method based dispatch to controller methods and HTTP error handling
    /// </summary>
    public class ResourceControllerFactory : IControllerFactory
    {
        // Note: This has been changed from the regular controller factory provided
        // by the ASP.NET Team so that we can use Windsor Container to resolve
        // container dependencies.

        readonly IKernel _kernel;
        const string restActionToken = "$REST$";

        public ResourceControllerFactory(IKernel kernel)
        {
            _kernel = kernel;
        }

        public IController CreateController(RequestContext requestContext, string controllerName)
        {
            IController ic = _kernel.Resolve<IController>(controllerName.ToLowerInvariant() + "controller");
            Controller c = ic as Controller;
            if (c != null && WebApiEnabledAttribute.IsDefined(c))
            {
                IActionInvoker iai = c.ActionInvoker;
                ControllerActionInvoker cai = iai as ControllerActionInvoker;
                if (cai != null)
                {
                    c.ActionInvoker = new ResourceControllerActionInvoker();

                    string actionName = requestContext.RouteData.Values["action"] as string;
                    if (string.IsNullOrEmpty(actionName))
                    {
                        // set it to a well known dummy value to avoid not having an action as that would prevent the fixup
                        // code in ResourceControllerActionInvoker, which is based on ActionDescriptor, from running
                        requestContext.RouteData.Values["action"] = restActionToken;
                    }
                }
            }
            return ic;
        }

        public void ReleaseController(IController controller)
        {
            _kernel.ReleaseComponent(controller);
        }

        // This ActionInvoker allows us to dispatch to a controller when no action was provided by the routing
        // infrastructure, but the information is available in the request's HTTP verb (GET/PUT/POST/DELETE)
        class ResourceControllerActionInvoker : ControllerActionInvoker
        {
            public ResourceControllerActionInvoker()
            {
            }

            protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
            {
                if (actionName == restActionToken)
                {
                    // cleanup the restActionToken we set earlier
                    controllerContext.RequestContext.RouteData.Values["action"] = null;

                    List<ActionDescriptor> matches = new List<ActionDescriptor>();
                    foreach (ActionDescriptor ad in controllerDescriptor.GetCanonicalActions())
                    {
                        object[] acceptVerbs = ad.GetCustomAttributes(typeof(AcceptVerbsAttribute), false);
                        if (acceptVerbs.Length > 0)
                        {
                            foreach (object o in acceptVerbs)
                            {
                                AcceptVerbsAttribute ava = o as AcceptVerbsAttribute;
                                if (ava != null)
                                {
                                    if (ava.Verbs.Contains(controllerContext.RequestContext.GetHttpMethod().ToUpperInvariant()))
                                    {
                                        matches.Add(ad);
                                    }
                                }
                            }
                        }
                    }
                    switch (matches.Count)
                    {
                        case 0:
                            break;
                        case 1:
                            ActionDescriptor ad = matches[0];
                            actionName = ad.ActionName;
                            controllerContext.RequestContext.RouteData.Values["action"] = actionName;
                            return ad;
                        default:
                            StringBuilder matchesString = new StringBuilder(matches[0].ActionName);
                            for (int index = 1; index < matches.Count; index++)
                            {
                                matchesString.Append(", ");
                                matchesString.Append(matches[index].ActionName);
                            }
                            return new ResourceErrorActionDescriptor(controllerDescriptor, HttpStatusCode.Conflict, string.Format(CultureInfo.CurrentCulture, "Error dispatching on controller {0}, conflicting actions matched: (1)", controllerDescriptor.ControllerName, matchesString.ToString()));
                    }
                }
                return base.FindAction(controllerContext, controllerDescriptor, actionName) ??
                    new ResourceErrorActionDescriptor(controllerDescriptor, HttpStatusCode.NotFound, string.Format(CultureInfo.CurrentCulture, "Error dispatching on controller {0}, no actions matched", controllerDescriptor.ControllerName));
            }

            // This class is used when we don't find an ActionDescriptor or find multiple matches
            // in this case we want to return an error response but throwing an HttpException from
            // FindAction bypasses the InvokeExceptionFilters, so instead we throw in this custom ActionDescriptor
            class ResourceErrorActionDescriptor : ActionDescriptor
            {
                ControllerDescriptor controllerDescriptor;
                string message;
                HttpStatusCode statusCode;

                public ResourceErrorActionDescriptor(ControllerDescriptor controllerDescriptor, HttpStatusCode statusCode, string message)
                {
                    this.message = message;
                    this.statusCode = statusCode;
                    this.controllerDescriptor = controllerDescriptor;
                }

                public override string ActionName
                {
                    get { return restActionToken; }
                }

                public override ControllerDescriptor ControllerDescriptor
                {
                    get { return this.controllerDescriptor; }
                }

                public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
                {
                    HttpException he = new HttpException((int)this.statusCode, this.message);
                    ResourceErrorActionResult rear;
                    if (!WebApiEnabledAttribute.TryGetErrorResult2(controllerContext.RequestContext, he, out rear))
                    {
                        rear = new ResourceErrorActionResult(new HttpException((int)this.statusCode, this.message), new ContentType("text/plain"));
                    }
                    return rear;
                }

                public override ParameterDescriptor[] GetParameters()
                {
                    return new ParameterDescriptor[0];
                }
            }
        }
    }
}

You can easily replace the Windsor stuff with StructureMap, NInject, Autofac, etc.

Enjoy.

My CODE Magazine Article Is Live

My first article with CODE Magazine is live. You can read it here. I cover ASP.NET MVC with the Spark View Engine. I cover a wide range of things and go in depth with examples with topics that include:

  • Inline Code
  • Element Constructs
  • Partials
  • Layouts
  • and more …

I hope you enjoy it! I enjoyed writing it.

A big thanks goes out to Chris G. Williams (twitter)for doing a stellar job on the tech editing.

Read it Here

LINQ: ToLookup vs ToDictionary

Some people are not sure of the difference between ToLookup vs ToDictionary LINQ methods.

To break it down real simple:

  • One is a look up (ToLookup)
  • One is a dictionary (ToDictionary)

Duh. That’s what you’re probably thinking. The real difference is understanding what each of these data structures do.

Lookups

A lookup is a key value pair in which multiple keys of the same value can exist, therefore resulting in a “lookup” type of scenario. The perfect example for this is to assume that we have a List of Author objects. The author object contains the following:

  • First Name
  • Last Name
  • Year their first book was published
  • Social Security Number

Assume that we’d like to give the user the option to select the authors based upon the year they published their first book in a WPF app. We could do that like this:

// Returns a list of Authors
var authors = authorService.FindAll();

// Turn the list into a lookup for later use
var authorLookup = authors.ToLookup(k => k.YearFirstPublished, v => v);

// Further down in the code
// Find all authors who published their first book in 1969
// Returns an enumerable of Authors that match that lookup value
var authorsPublishedInParticularYear = authorLookup[1969];

This will return all authors who published their first book in 1969. We could use this look up later in the application as well to look up other values.

The key thing to note here is that a Lookup can have multiple keys of the same value.

Dictionaries

A dictionary is a key value pair in which a key can only exist once within the dictionary. You cannot have two entries with the same key. This is the difference between the Dictionary and Lookup data structures.

Using the same Author object we described above we can find an author by their Social Security Number if we turned the list into a dictionary for fast lookups based upon a unique key (the social security number).

This will return a single Author object who’s social security number matches ‘555-55-5555’.

// Returns a list of Authors
var authors = authorService.FindAll();

// Turn the list into a lookup for later use
var authorDictionary = authors.ToDictionay(k => k.SocialSecurityNumber, v => v);

// Further down in the code
// Finds the author with 555-55-5555 as their Social Security Number
var author = authorDictionary["555-55-5555"];

Conclusion

While dictionaries and lookups seem to be nearly the same data structure, they provide completely different functionality based upon the value the key possesses.

Suspending Resharper 5

For those of you who don’t know, ReSharper is no longer a Visual Studio add-in. Previously if you needed to disable ReSharper for whatever reason you could do so in the Add-In Manager in Visual Studio. You no longer can. Disabling ReSharper is now termed “Suspending” it. You can suspend ReSharper from the Tools > Options > Resharper options window. Press Suspend. ReSharper will now be suspended.

I know, why would I disable ReSharper? Well, at my current client I’m working on a VB.NET ASP.NET Web Forms application which talks to Microsoft CRM through the CRM WebService and LinqToCrm. The web service proxy that gets generated is over 94,000 lines long. Thats 94,000 lines of VB.NET. When ReSharper starts to analyze that file (as well as the other VB.NET files in the app) all hell breaks loose. The IDE locks up and I can’t do a single thing. Therefore I had to suspend ReSharper while working in this project.

Here’s the crux … the only VB.NET project is the Website, all other libraries are C# (thankfully) and I want to use ReSharper. Going through the tools menu each time I need to Suspend/Resume Resharper is a royal PITA. Thankfully ReSharper gave us a key command: Resharper_ToggleSuspend to assist in toggling the suspension of ReSharper. This is not mapped to any key combo by default. What I have done is mapped it to CTRL + ALT + F6. Now when I need to turn ReSharper On/Off when I enter/leave the VB.NET project I hit the key combo and I’m back in action (or out of action in regards to turning it off). Super easy. Booyakasha.