Google analytics script

Latest jQuery CDN with code tiggling.

Friday 19 November 2010

Handling validation errors on Ajax calls in Asp.net MVC

If you're developing rich web clients using Asp.net MVC on the back-end, you've probably come across this functionality that can be described with these conditions:

  1. client side data (may be a form),
  2. ajax posting of this data (form),
  3. controller action has strong type parameters,
  4. controller action processes data and returns anything but a full view (since it was an Ajax call)
Familiar? Then you've come across this problem: »What should be done when validation fails?«
Seems fairly straight forward, right? Well not so fast my fellow developer friend...

The old fashioned way

Validation in Asp.net MVC is really simple and very transparent. If you had to think about it all the time when developing Asp.net Web forms applications, you don't have to do that with MVC anymore. Whenever your controller action takes strong type parameters and you define validation rules on the type itself, you can easily just lay back and watch the magic happen in front of your eyes.

When you don't use Ajax (how old fashioned of you), your form will be posted back the usual way by means of browser reloading the whole page. Because browser reloads the whole page there will be some flickering since it will clear the window at a certain point to render the new response. So your application will have a master view (in terms of master/detail process), where the user could click some Add new link or button that will redirect them to the new entity view (the details view). This view presents a form where they enter relevant data and a button to submit it to server. Your controller action could look very similar to this:

   1:  [HttpPost]
   2:  public ActionResult Add(Person instance)
   3:  {
   4:      if (!this.ModelState.IsValid)
   5:      {
   6:          // return the same view with validation errors
   7:          return View(instance);
   8:      }
   9:      
  10:      // save the new person instance and go back to master view
  11:      Person result = this.Service.Add(instance);
  12:      return RedirectToAction("Index");
  13:  }

The contemporary way

But since we're savvy web developers, we rather use asynchronous processing these days. Instead of asking the browser to post back data and reload the whole page, we rather issue an Ajax call. This way we avoid page flickering and even the nasty long page scroll position stays the same. Ajax FTW! All today's desktop browsers support this functionality as well. Great way of doing it then.

The whole client process probably changed according to our advanced functionality. We'd still have the master view with the list of all entities, but clicking on the Add new link will most probably present a modal dialog box with the details form instead of redirecting the user to a whole new page. In terms of usability and interface comprehension, this is a much better experience. Something similar is frequently used in Sharepoint 2010 these days. The process would work like this:

  1. User clicks Add new link on the master view.
  2. Your javascript dims the master view and displays a dialog box with the new entity form (details view).
  3. User enters relevant data and clicks the Save link.
  4. Your javascript issues an Ajax call that posts back data.
  5. When everything goes according to the plan, server responds either with a PartialViewResult that holds visual representation of the newly created entity that can be appended to master page entity list, or JsonResult data that can be consumed for the same purpose (but you'll have to manually transform JSON to visual HTML representation).
  6. Your javascript closes the dialog box.
  7. Master view is in full display again and your javascript adds the newly created entity to it's list (while also providing some highlight animation).
It's wiser to return a partial view since the same partial view can be used on the master view to display each entity item in the list, so they will be fully compatible and when you change your partial view, both functionalities still work as expected. Otherwise you'd have to change javascript code as well. Not DRY at all. This is our controller action now:
   1:  [HttpPost]
   2:  public ActionResult Add(Person instance)
   3:  {
   4:      if (!this.ModelState.IsValid)
   5:      {
   6:          // we'll see this in a bit
   7:      }
   8:      
   9:      // save the new person instance and go back to master view
  10:      Person result = this.Service.Add(instance);
  11:      return PartialView("Person", result); // or Json(result)
  12:  }

When the going gets tough...

So what do we do, when user enters incorrect data into the form that's not valid? You can't just return some other partial view, because javascript expects something else. You could of course put additional functionality on the client side that would detect different HTML being returned, but that's very prone to errors. Think of changing the partial view. Any of the two. DRY again. The best thing would actually be to return a 400 HTTP response and provide the right result in it. Why is this better?

  • Because it will keep working even when you completely redesign your views.
  • Because it's going to be much easier to distinguish between a successful results and an erroneous one on the client.
If you're an Asp.net MVC developer it's highly likely that you use jQuery on the client. Distinguishing success from an error cannot be easier:
   1:  $.ajax({
   2:      url: "Person/Add",
   3:      type: "POST",
   4:      data: $(this).serializeArray(), // provided this code executes in form.onsubmit event
   5:      success: function(data, status, xhr) {
   6:          // YAY! Process data
   7:      },
   8:      error: function(xhr, status, err) {
   9:          if (xhr.status == 400)
  10:          {
  11:              // this is our erroneous result
  12:          }
  13:          else
  14:          {
  15:              // some other server error must have happened (most probably HTTP 5xx)
  16:          }
  17:      }
  18:  });

...the tough get going

To make things reusable on the server side you have two possible (and most obvious) ways of doing it:

  1. Provide additional controller extension methods like PartialViewError, JsonError, etc. that will work very similar to their normal counterparts except they'll also set the response status code to 400 (don't forget to check whether this is an Ajax call, because if it's not, don't set status code).
  2. Provide a custom exception and an exception action filter that handles it.
The first one is quite trivial so I've decided to do the latter. And since I don't use the usual pattern of displaying in-place form validation errors it also suits my needs.

Let's first look at the code of my custom exception. It's called ModelStateException because I throw it on invalid model state and it has a constructor that takes model state and gets the errors automatically from it. This is the code of the exception:

   1:  /// <summary>
   2:  /// This exception that is thrown when there are model state errors.
   3:  /// </summary>
   4:  [Serializable]
   5:  public class ModelStateException : Exception
   6:  {
   7:      /// <summary>
   8:      /// Gets the model errors.
   9:      /// </summary>
  10:      public Dictionary<string, string> Errors { get; private set; }
  11:   
  12:      /// <summary>
  13:      /// Gets a message that describes the current exception and is related to the first model state error.
  14:      /// </summary>
  15:      /// <value></value>
  16:      public override string Message
  17:      {
  18:          get
  19:          {
  20:              if (this.Errors.Count > 0)
  21:              {
  22:                  return this.Errors.First().Value;
  23:              }
  24:              return null;
  25:          }
  26:      }
  27:   
  28:      /// <summary>
  29:      /// Initializes a new instance of the <see cref="ModelStateException"/> class.
  30:      /// </summary>
  31:      public ModelStateException() : base()
  32:      {
  33:          this.Errors = new Dictionary<string, string>();
  34:      }
  35:   
  36:      /// <summary>
  37:      /// Initializes a new instance of the <see cref="ModelStateException"/> class.
  38:      /// </summary>
  39:      /// <param name="modelState">State of the model.</param>
  40:      public ModelStateException(ModelStateDictionary modelState)
  41:          : this()
  42:      {
  43:          if (modelState == null)
  44:          {
  45:              throw new ArgumentNullException("modelState");
  46:          }
  47:   
  48:          //this.ModelState = modelState;
  49:          if (!modelState.IsValid)
  50:          {
  51:              StringBuilder errors;
  52:              foreach (KeyValuePair<string, ModelState> state in modelState)
  53:              {
  54:                  if (state.Value.Errors.Count > 0)
  55:                  {
  56:                      errors = new StringBuilder();
  57:                      foreach (ModelError err in state.Value.Errors)
  58:                      {
  59:                          errors.AppendLine(err.ErrorMessage);
  60:                      }
  61:                      this.Errors.Add(state.Key, errors.ToString());
  62:                  }
  63:              }
  64:          }
  65:      }
  66:   
  67:      /// <summary>
  68:      /// Initializes a new instance of the <see cref="ModelStateException"/> class.
  69:      /// </summary>
  70:      /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
  71:      /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
  72:      /// <exception cref="T:System.ArgumentNullException">
  73:      /// The <paramref name="info"/> parameter is null.
  74:      /// </exception>
  75:      /// <exception cref="T:System.Runtime.Serialization.SerializationException">
  76:      /// The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0).
  77:      /// </exception>
  78:      protected ModelStateException(SerializationInfo info, StreamingContext context)
  79:          : base(info, context)
  80:      {
  81:          if (info == null)
  82:          {
  83:              throw new ArgumentNullException("info");
  84:          }
  85:   
  86:          // deserialize
  87:          this.Errors = info.GetValue("ModelStateException.Errors", typeof(Dictionary<string, string>)) as Dictionary<string, string>;
  88:      }
  89:   
  90:      /// <summary>
  91:      /// Initializes a new instance of the <see cref="ModelStateException"/> class.
  92:      /// </summary>
  93:      /// <param name="message">The message.</param>
  94:      public ModelStateException(string message)
  95:          : base(message)
  96:      {
  97:          this.Errors = new Dictionary<string, string>();
  98:          this.Errors.Add(string.Empty, message);
  99:      }
 100:   
 101:      /// <summary>
 102:      /// Initializes a new instance of the <see cref="ModelStateException"/> class.
 103:      /// </summary>
 104:      /// <param name="message">The message.</param>
 105:      /// <param name="innerException">The inner exception.</param>
 106:      public ModelStateException(string message, Exception innerException)
 107:          : base(message, innerException)
 108:      {
 109:          this.Errors = new Dictionary<string, string>();
 110:          this.Errors.Add(string.Empty, message);
 111:      }
 112:   
 113:      /// <summary>
 114:      /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception.
 115:      /// </summary>
 116:      /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
 117:      /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
 118:      /// <exception cref="T:System.ArgumentNullException">
 119:      /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic).
 120:      /// </exception>
 121:      /// <PermissionSet>
 122:      ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/>
 123:      ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/>
 124:      /// </PermissionSet>
 125:      [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
 126:      public override void GetObjectData(SerializationInfo info, StreamingContext context)
 127:      {
 128:          if (info == null)
 129:          {
 130:              throw new ArgumentNullException("info");
 131:          }
 132:   
 133:          // serialize errors
 134:          info.AddValue("ModelStateException.Errors", this.Errors, typeof(Dictionary<string, string>));
 135:          base.GetObjectData(info, context);
 136:      }
 137:  }

The exception action filter has to intercept ModelStateException exceptions and return the error in a predefined format, so the client's able to uniformly consume it. This is the code that I'm using:

   1:  /// <summary>
   2:  /// Represents errors that occur due to invalid application model state.
   3:  /// </summary>
   4:  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
   5:  public sealed class HandleModelStateExceptionAttribute : FilterAttribute, IExceptionFilter
   6:  {
   7:      /// <summary>
   8:      /// Called when an exception occurs and processes <see cref="ModelStateException"/> object.
   9:      /// </summary>
  10:      /// <param name="filterContext">Filter context.</param>
  11:      public void OnException(ExceptionContext filterContext)
  12:      {
  13:          if (filterContext == null)
  14:          {
  15:              throw new ArgumentNullException("filterContext");
  16:          }
  17:   
  18:          // handle modelStateException
  19:          if (filterContext.Exception != null && typeof(ModelStateException).IsInstanceOfType(filterContext.Exception) && !filterContext.ExceptionHandled)
  20:          {
  21:              filterContext.ExceptionHandled = true;
  22:              filterContext.HttpContext.Response.Clear();
  23:              filterContext.HttpContext.Response.ContentEncoding = Encoding.UTF8;
  24:              filterContext.HttpContext.Response.HeaderEncoding = Encoding.UTF8;
  25:              filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
  26:              filterContext.HttpContext.Response.StatusCode = 400;
  27:              filterContext.Result = new ContentResult {
  28:                  Content = (filterContext.Exception as ModelStateException).Message,
  29:                  ContentEncoding = Encoding.UTF8,
  30:              };
  31:          }
  32:      }
  33:  }

The reusable end result

All our controller actions can now take advantage of this reusable functionality. The previously shown controller action now looks like this:

   1:  [HttpPost]
   2:  [HandleModelStateException]
   3:  public ActionResult Add(Person instance)
   4:  {
   5:      if (!this.ModelState.IsValid)
   6:      {
   7:          throw new ModelStateException(this.ModelState);
   8:      }
   9:      
  10:      // save the new person instance and go back to master view
  11:      Person result = this.Service.Add(instance);
  12:      return PartialView("Person", result);
  13:  }

Our client side functionality can now be packed into a simple application specific jQuery plugin that does ajax error handling and can be loaded as part of the general scripts on your master (as in master template) view:

   1:  $.extend({
   2:      appAjax: function(url, type, datagetter, onsuccess) {
   3:          /// <summary>jQuery extension that executes Ajax calls and handles errors in application specific ways.</summary>
   4:          /// <param name="url" type="String">URL address where to issue this Ajax call.</param>
   5:          /// <param name="type" type="String">HTTP method type (GET, POST, DELETE, PUT, HEAD)</param>
   6:          /// <param name="datagetter" type="Function">This parameterless function will be called to return ajax data.</param>
   7:          /// <param name="onsuccess" type="Function">This optional function(data) will be called after a successful Ajax call.</param>
   8:          
   9:          var execSuccess = $.isFunction(onsuccess) ? onsuccess : $.noop;
  10:          var getData = $.isFunction(datagetter) ? datagetter : function() { return datagetter; };
  11:          
  12:          $.ajax({
  13:              url: url,
  14:              type: type,
  15:              data: getData(),
  16:              error: function(xhr, status, err) {
  17:                  if (xhr.status == 400)
  18:                  {
  19:                      alert(xhr.responseText);
  20:                  }
  21:                  else
  22:                  {
  23:                      alert("Server error has occured.");
  24:                  }
  25:              },
  26:              success: function(data, status, xhr) {
  27:                  window.setTimeout(function() {
  28:                      execSuccess(data);
  29:                  }, 10);
  30:              }
  31:          });
  32:      }
  33:  });

In your page, you can simply call it this way:

   1:  $.appAjax(
   2:      "Person/Add",
   3:      "POST",
   4:      function() { return $(this).serializeArray(); },
   5:      function(data) { // consume data }
   6:  );

This is the reusable model validation in Asp.net MVC applications using Ajax calls and jQuery on the client side. If you have any further questions or suggestions please leave a comment. And don't forget to vote on this content.

21 comments:

  1. Thanks!! Exactly what I needed.

    One question... While debugging, how do I throw ModelStateException to the browser only? Right now when the exception is thrown in the controller Visual Studio breaks execution to notify me and I have to resume for my controller to send the response to the browser.

    ReplyDelete
  2. You can control whether Visual Studio debugger breaks on certain unhandled exceptions. Either go to menu Debug > Expceptions... or press Ctrl+Alt+E. In the dialog that opens you will have to add your custom exception (include the whole namespace tree with the exception class name - same as other exceptions that are already defined there) and UN-tick "User-unhandled" checkbox of this particular exception. You can read about this functionality on MSDN: http://msdn.microsoft.com/en-us/library/038tzxdw.aspx

    ReplyDelete
  3. This is awesome code...I implemented it almost verbatim.

    One change I would make is to the Message property of ModelStateException:


    public override string Message
    {
    get
    {
    StringBuilder sb = new StringBuilder();
    foreach (string error in this.Errors.Keys)
    sb.Append(this.Errors[error]).Append("
    ");
    return sb.ToString();
    }
    }

    That way you can see all the errors at once...I display xhr.responseText at the top of my form as a summary of all the errors that need to be fixed.

    ReplyDelete
  4. @Anonymous: If you need to display all the errors than that is the right thing to do of course.

    But as I see you're joining those errors with spaces. You could as well just use string.Join method instead and accomplish the same task. Internally it doesn't use StringBuilder class but character buffer which is likely even faster than your code. So...


    return string.Join(" ", this.Errors.Select(e => e.Value).ToArray());


    This is a one liner and also uses a bit more contemporary LINQ query. You could do a similar thing by using Aggregate which is rather seldom used. I know I haven't used it yet.

    ReplyDelete
  5. Excellent article. Do you by chance have a working sample?

    ReplyDelete
  6. that you are willing to share of course. :)

    ReplyDelete
  7. @Steven: application that's currently in development that integrates Asp.net MVC into Sharepoint uses exactly these classes. So yes I do have a working example. Unfortunately I can't show it to you...

    What are your concerns? Maybe I can address them directly?

    ReplyDelete
  8. Only that I can't easily copy the code from your samples in the post above. :D I think your solution here is great.

    ReplyDelete
  9. @Steven: Can't copy my code? I've actually added the "toggle code line numbers" to make it easier for you to copy code.

    By clicking on the link (above every code block), all those line numbers disappear and you can select and copy just the code without any unwanted junk.

    If those links don't work for you, you can still copy the code (with line numbers included) and paste it into a text editor (Visual Studio or Notepad++) and use the multi-line editor capabilities to remove those line numbers all at once.

    Refer to one of my previous comments about multi-line editor capabilities of modern text editors.

    ReplyDelete
  10. Hmm, dude, unless I'm mistaken, HTTP 400 errors are generated by web servers, which, in your case, is most likely IIS. Don't you consider it a violation of generally-accepted principles and practices, if it isn't your web server, but your program that fakes 400 errors on a whim, left and right and center? Please, enlighten me.

    ReplyDelete
    Replies
    1. This is a very interesting question. Let me try to explain reasoning behind my code.

      Every web application is running on a web server. One or another. And applications are run by the web server so they run in its context so to speak. Within. And if my application returns an HTTP error code this still means that server will send it. So they're still coming from the server. It is also impossible to expect of the server to understand the business processes of our applications so we may instruct it what to report back to the client. Server is what the word says... A humble and prompt servant.

      However you look at it, these codes are HTTP standard. I don't see a particular reason why would I need to avoid them, when I have to tell the client exactly the same thing that 4xx codes are about: The 4xx class of status code is intended for cases in which the client seems to have erred. And when data validation fails this means that the client has erred. Client provided invalid values. So it is their fault and they should correct it.

      I'll stick with these codes and not add an additional abstraction layer over my applications making them harder to maintain in the future. It's usually better to be practical and not overcomplicate most obvious things.

      Just a question for you: What is the difference that Microsoft guys put into Asp.net MVC's routing code that also returns a 404 when route can't be resolved for whatever reason (even because of two matching controller actions). In the end it's still Asp.net MVC that reports the error and not IIS itself.

      Feeling enlightened now? ;)

      Delete
    2. One more thing... Did you know that Twitter application returns a 420 error code that reads as: 420 Enhance Your Calm (Twitter). It's application that returns the code not the server itself. Servers only serve traffic. They can only orchestrate the very trivial and simplistic processes. Applications are almost always much more complicated in terms of processes.

      Delete
  11. Excellent article. One suggested improvement, though...

    Wouldn't it make life easier to have the HandleModelStateExceptionAttribute inherit ActionFilterAttribute instead of Filter Attribute? Then you could override OnActionExecuting like so (please excuse that it's VB, not C#):

    Public Overrides Sub OnActionExecuting(filterContext As ActionExecutingContext)
    MyBase.OnActionExecuting(filterContext)
    If Not filterContext.Controller.ViewData.ModelState.IsValid Then
    Throw New ModelStateException(filterContext.Controller.ViewData.ModelState)
    End If
    End Sub

    This way, it's not even necessary to add the logic checking ModelState.IsValid to every controller. You would only have to add the attribute.

    ReplyDelete
    Replies
    1. That would be of course possible and a nice addition to my filter. The simplified example within this blog post directly suggests this improvement. That's true. But application where I used this approach has a bit more complex scenario to this simple if statement checking validity. So in my case it wouldn't do the trick. That's the only reason why I left it out of the filter.

      But you can easily add that to your code if it makes your life simpler. I would suggest you do add that and follow DRY principle.

      Delete
  12. Ever since .Net first came out, have we not been told over and over it is very bad practise to use Exceptions for application error handling?

    This is due to the extensive processing they cause underneath, and it will choke your site if they are being used throughout.

    They should only ever be used to handle unexpected application or system errors, usually where the app is not going to be able to continue.

    This seems ilke a lot of uneccessary rocket science to avoid an occassional miniscule flicker. KISS.

    ReplyDelete
    Replies
    1. To some extent you're right, because I've seen several cases in applications where developers put number parsing inside try-catch block just to make sure string is a valid number (in terms of format and length). And I'd say that's bad practice.

      In this case there is actually an error happening. Validation error. It's true that nothing exceptional happened that prevented correct program execution so exception shouldn't thrown.

      But. The only reason why I used exceptions is that I didn't have to add additional abstractions. Otherwise I'd have to communicate invalid information to filter by other means. Either by custom controller class and casting in filter or some other shared resources. Both are prone to errors and other issues, while exceptions are already working out of the box without any additional customizations.

      But you're welcome to replace exceptions with other means if you think that these exceptions became a bottleneck of your application's performance which I seriously doubt they do. The problem of exceptions becomes apparent when people start using them instead of code branching and putting everything single thing inside try-catch blocks. But that's my view... You do as you think fits better.

      Delete
  13. Awesome, well written, and very useful - thanks for taking the time to write this article. Two things I am curious about:

    - Why the 10ms pause prior to calling the executeSuccess javascript function?
    - Why did you opt to manually check the ModelState and manually throw an error when you could have bundled that behavior in the action filter also?

    ReplyDelete
    Replies
    1. The timeout trick is simply used to defer that function execution outside of ajax call stack so it gets executed completely independently of it. Why I used 10ms in my example? I really don't know. I usually use 1 or 0. Especially the latter.

      I kept model state checking in my controller because in my real application there were other implications to model validation than inplicit MVC model validation on my application entities. If in your case this covers 99% of all cases, you can easily add this validation inside your filter and also populate your results however fits your requirements.

      The main idea of this article is to show how to handle validation errors with Ajax requests. Because I've seen many cases where people returned normal results and then parsed that on the client side (within success function of course) and decide whether processing was successful or not. My code makes it possible to use built-in jQuery Ajax call functionality to avoid such additional processing while also simplifying your code with reusable action filter.

      Delete
  14. I know this is an old post, but I just stumbled upon your validation handling and it is awesome! I am having one issue though that maybe you could help me with, when my validation is incorrect the responseText is correct but the status is always 500. It may be because I altered the function to accept contentType so I can pass in "application/json; charset=utf-8", also my data has been changed to data: JSON.stringify({ ticket: ticketData }). I think one of these two might be causing incorrect return data. If it is something obvious any help would be greatly appreciated. Other than I just want to say thanks for providing such a good solution.

    ReplyDelete
  15. Thanks a lot, your article gave me a direction to implement, awesome article

    ReplyDelete

This is a fully moderated comments section. All spam comments are marked as spam without exception. Don't even try.

Note: only a member of this blog may post a comment.