Wednesday, June 13, 2012

ASP.Net MVC Error Handling at Method, Controller and Global Levels

Traditional Asp.Net supports
  • Custom error redirect in web.config 
  • Application_Error in Global.asax
On top of that MVC3 introduced 
How do you put these pieces together to build a more robust MVC site?

1. Handle controller/method level MVC errors with HandleErrorAttribute
You can decorate your controllers or action methods with HandleErrorAttribute.

    public class MyController : Controller
        public ActionResult Index()
            return View();
    public class MyController : Controller
        public ActionResult Index()
            return View();

When an action method in the controller with HandleError attribute decoration throws an exception and it is not handled by your code, MVC displays the Error view that is located in the ~/Views/Shared folder. It's worth to note that HandleErrorAttribute renders the view without passing through any error handling controller you may have.  Yes this is somewhat confusing.  I first learned this from Tom Dupont's blog and later confirmed with simulating an exception and stepping through the code.

You may override HandleErrorAttribute's default behavior by specifying the exception types that should be handled, the name of the view to render, the order in which the filters are applied if you have more than one HandleErrorAttribute for your controller/method.  See MSDN for more details.

If you want to enable error handling for all of your controllers yet don't like the tedious work of decorating your controllers one-by-one, you can register global HandleErrorAttribute in Global.asax.  This is enabled by default in MVC3.

        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            filters.Add(new HandleErrorAttribute());
        protected void Application_Start()


2. Log errors by overriding HandleErrorAttribute.OnException or Controller.OnException
By now you may wonder how/where I can log errors.  HandleErrorAttribute renders an error view to users, but doesn't log errors for you automatically.  This is where OnException method comes into play.  Both HandleErrorAttribute and Controller classes have OnException method that can be overridden for error logging.  

In Controller's case, first you override the OnException method and add your own logic for error logging or notification.

    public class BaseController : Controller
        protected override void OnException(ExceptionContext filterContext)
            // Do additional things like logging or emailing here.


Then you inherit your controllers from the new BaseController instead of the default Controller class.

    public class HomeController : BaseController
        public ActionResult Index()
            // Do something here

            return View();

From that point on, your OnException will be called whenever an unhandled exception is thrown in your controller methods.
3. Handle non-MVC errors at global level with Application_Error
HandleErrorAttribute and Controller.OnException deal with application or server errors, but not errors outside of MVC pipeline such as 404 Not Found error.  Those non-MVC errors should be handled/logged in your Application_Error method in Global.asax.

If you neither register global HandleErrorAttribute nor decorate your controller/method with HandleErrorAttribute, exceptions will bubble up to Application_Error.

4. Display custom errors to the end users.
If you already registered global HandleErrorAttribute or decorated your methods/controllers with the same attribute but still saw the Yellow Screen of Death (YOSD) with potentially security-compromising error details like stack trace, you should check customErrors element in your web.config to make sure the mode attribute is set to either RemoteOnly (by default) or On.

    <customErrors mode="RemoteOnly" defaultRedirect="/Error/HttpError">
      <error statusCode="404" redirect="/Error/Http404"/>

Other Resources

Friday, June 1, 2012

Twitter Stream APIs Comparison

Twitter REST API is polling-based.  On contrast, Twitter Stream API pushes the tweet data to client as they become available thereby eliminating the polling overhead and reducing the latency.

There're three types of Stream APIs: public streams, User streams and Site streams.  Our company has been using public stream for the past year or so and now are moving to UserStreams/SiteStreams. Following is a side-by-side comparison of these three APIs.

Streaming Endpoint
Use case
Public streams

Streams of the public data flowing through Twitter.
One user app for data analysis.
.         Each Twitter account may create only one standing connection.
         No support for Direct Message.

User streams
Single-user streams, containing roughly all of the data corresponding with a single user's view of Twitter.
Single-user, client-side desktop app/mobile app that provides equivalent view of their home timeline.
         Each Twitter account is limited to only a few simultaneous connections per OAuth application, regardless of IP.
.       The number of simultaneous connections per IP address is still limited, regardless of application.  
Direct message is supported in the context of the authenticated user under which the connection is established.
Site streams

Multi-user version of user streams.
Multi-tenant websites or mobile push services that provide home timeline on behalf of many users at a time.
          Max 100 users per stream while connecting; can add up to 1,000 users per connection through API.
         Max 25 new connections per second.
Deliver both public and private data in a single stream.  Must be very meticulous about what data they display to whom.

Workaround for SELECT INTO in SQL Azure

If you try to do select insert in SQL Azure like the statement below

INSERT INTO dbo.MyTarget

You will get error message:

Statement 'SELECT INTO' is not supported in this version of SQL Server.

There's a quick workaround for that -- use INSERT INTO SELECT statement instead.  The caveat is that you need to create the table beforehand.


INSERT INTO dbo.MyTarget (ID)