Archive for March, 2010
No IE6 ActionFilter for ASP.NET MVC
Mar 31st
*** 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.
TekPub: Introduction to Android Series
Mar 31st
Want to learn Android? Don’t have time to read a book?
TekPub.com has released a new series, produced by yours truly:
In this series I will be building the TekPub Android application. During the course of the series you’ll be introduced to Android, the Eclipse IDE, developing with the Android Framework, Java and I’ll also show you how to publish your app to the Android Marketplace (some call it the “App Store”).
Check out the preview here
WP7 Emulator Screen Size Management
Mar 16th
I’ve been developing Android apps for awhile now and at times the emulators are SOOO big that they litterally consume the entire screen real estate and its very difficult to navigate through the enumlator. I was very surprised when I started up the WP7 emulator and started finding out how simple it was to scale the emulator to fit my screen.
All I had to do was select the settings:
Then, select the screen size I wanted:
The screen could scale accordingly now.
Examples
100% (Very Large) – Its so big that it falls off the screen.
50% (Smaller), now it fits nicely, however the UI is hard to read.
This makes life much easier when developing for a high resolution system on a non-high resoution system.
WP7 Boot to VHD
Mar 15th
Tonigt I downloaded the WP7 Developer Tools on a new VHD (Boot to VHD). I’d I’d have to say … its super easy.
File –> New
As soon as the tools are installed you can select File –> New Windows Phone Application, as seen below:
Selecting a new Phone app, I was able to get some XAML on the screen, edit the XAML and get my first Hello WP7 app up in running in under 5 minutes. This very impressive. The emulator started right up, I waited for about 30 seconds while it booted and then my app started. As shown below:
All in all, it was a super simple process.
Boot to VHD
It is important to note that working from a virtualized environment is not supported with the Dev Tools for WP7. However, you can boot to vhd and it will work great (which is 10x better than a VPC in my opinion for regular dev anyway).
So if you havent already, start utilizing Boot to VHD, set a a new one and get started with the new WP7 tools. Its cool/fun stuff.
ASP.NET MVC REST API WINDSOR CONTROLLER FACTORY
Mar 11th
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.







