The Ajax response object pattern
Posted on Sunday, 21st February 2016
For some time now I’ve been using what I like to call the Ajax response object pattern to great success across a variety of projects, enough so that I thought it merited its own blog post.
The Ajax response object pattern is an incredibly simple pattern to implement, but goes a long way to help promote a consistent API to handling most Ajax responses, and hopefully by the end of this post you’ll be able to see the value in implementing such an approach.
Before we dig into what the Ajax response object pattern is and how to implement it, let’s take a moment to look at the problem it aims to solve.
The common way to handle Ajax responses
Typically you’ll see a variation of the following approach to handling an asynchronous javascript response (the important part here is the handling of the response rather than how we created it), and whilst the code may vary slightly (the example used is a trivial implementation) hopefully it will give you an idea as to what we’re trying to do.
// The back end
public class ProfileController : Controller
{
...
[HttpGet]
public ActionResult GetProfile(int profileId)
{
var userProfile = this.profileService.GetUserProfile(profileId);
return View(new ProfileViewModel(userProfile));
}
}
// The Javascript / JQuery
$.ajax({
type: "GET",
url: "/Profile/LoadProfile/" + $('#userProfileId').val(),
dataType: "html",
success: function (data, textStatus, jqXHR) {
$('#profileContainer').html(data);
},
error: function (jqXHR, textStatus, errorThrown) {
// Correctly handle error
}
});
As you can see from the above code all we’re doing is creating our asynchronous javascript request to our back end to retrieve a profile based on the profile id provided. The DOM is then updated and the response loaded via the call to JQuery’s html() method.
What’s wrong with this approach?
Nothing. There’s nothing wrong with this approach and it’s in fact, a perfectly acceptable way to perform and handle ajax requests/responses - however, there is room for improvement. To see what can be improved ask yourself the following questions:
What does our code tell us about the state of the ajax response?
Judging by our code, we can tell that our HTTP request to our _LoadProfile_ controller action was successful as our success method is being invoked - however what does this REALLY tell us about the state of our response? How to do we know that our payload is in fact a user profile. After all, our success method (or our status code of 200 Ok) simply tells us that the server successfully responded to our HTTP request - it doesn’t tell us that our domain validation conditions within our service were met.Is it easy to reason with?
When programming we should always aim to write code succinct and clear. Code that leaves no room for ambiguity and removes any guesswork. Does the above solution do this?How could we pass additional context to our Javascript?
As we’re returning an HTML response to be rendered to the user, what happens if we wanted to pass additional context to the Javascript such as a notification of an error, or some data we wish to display to the user via a modal dialog?Are we promoting any kind of consistency?
What happens if our next Ajax response returns a Json object? That will need to be handled in a completely different way. If we have multiple developers working on a project then they’ll each probably implement ajax responses in various ways.
A better approach - let’s model our Ajax response
Object-orientated programming is all about modelling. Wikipedia says it best:
Object-oriented programming (OOP) is a programming language model organized around objects rather than “actions” and data rather than logic.
To us mere mortals (as opposed to computers), modelling behaviour/data makes it easier for us to grasp and reason with - this premise is what helped make the Object-orientated programming paradigm popular in the early to mid-1990s. Looking back over our previous example, are we really modelling our response?
When we start passing values around (such as HTML in the previous example), we’re missing out on the benefits gained from creating a model around our expected behaviour. So, instead of simply returning a plain HTML response or a JSON object containing just our data, let’s try and model our HTTP response and see what benefits it will bring us. This is where the Ajax response object pattern can help.
The Ajax response object pattern
Implementation of the Ajax response object pattern simple. To resolve the concerns and questions raised above, we simply need to model our Ajax response, allowing to us to add additional context to our asynchronous HTTP response which we can then reason with within our Javascript.
The following example is the object I tend to favour when applying the Ajax response object pattern - but your implementation may vary depending on what you’re doing.
public class AjaxResponse
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public string RedirectUrl { get; set; }
public object ResponseData { get; set; }
public static AjaxResponse CreateSuccessfulResult(object responseData)
{
return new AjaxResponse
{
Success = true,
ResponseData = responseData
};
}
}
We can then use the AjaxResponse object to model our HTTP response to something like the following:
[HttpGet]
public JsonResult GetProfile(int profileId)
{
var response = new AjaxResponse();
try
{
var userProfile = this.profileService.GetUserProfile(profileId);
response.Success = true;
response.ResponseData = RenderPartialViewToString("Profile", new ProfileViewModel(userProfile));
}
catch (RecordNotFoundException exception)
{
response.Success = false;
response.ErrorMessage = exception.Message;
}
return Json(response, JsonRequestBehavior.AllowGet);
}
Earlier we were rendering the view and returning just the HTML payload in the response, now we need to render the view to a string and pass to the ResponseData property. This way we can make use of the additional properties such as whether the response the user is expecting was successful, if not then we can supply and error message. Because our ResponseData property is an object base type, we can use it to store any type, including Json.
Below is an implementation of the _RenderPartialViewToString_ method I often create in a base controller when writing ASP.NET MVC applications.
public class BaseController : Controller
{
protected string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
{
viewName = this.ControllerContext.RouteData.GetRequiredString("action");
}
this.ViewData.Model = model;
using (var stringWriter = new StringWriter())
{
ViewEngineResult partialView = ViewEngines.Engines.FindPartialView(this.ControllerContext, viewName);
ViewContext viewContext = new ViewContext(this.ControllerContext, partialView.View, this.ViewData, this.TempData, (TextWriter)stringWriter);
partialView.View.Render(viewContext, (TextWriter)stringWriter);
return stringWriter.GetStringBuilder().ToString();
}
}
}
Now that we’ve modelled our response we have the ability to provide the response consumer far more context, this context enables us to better reason with our server response. In the cases where we may need to perform any kind of front end action based on the outcome of the response, we can easily do.
// The Javascript / JQuery
$.ajax({
type: "GET",
url: "/Profile/LoadProfile/" + $('#userProfileId').val(),
dataType: "json"
success: function (data, textStatus, jqXHR) {
if (ajaxResponse.Success === true) {
$('#profileContainer').html(ajaxResponse.ResponseData);
} else {
Dialog.showDialog(ajaxResponse.ErrorMessage);
}
},
error: function (jqXHR, textStatus, errorThrown) {
...
}
});
Additionally, if the approach is used throughout a team then you’re promoting a consistent API that you can build around. This makes the development of general error handlers far easier. What’s more, if you’re using TypeScript in your codebase then you can continue to leverage the benefits by casting your response to a TypeScript implementation of the ajaxResponse class and gain all the intelli-sense and tooling support goodness that comes with TypeScript.
// TypeScript implementation
export interface IAjaxResponse {
Success: boolean;
ErrorMessage: string;
RedirectUrl: string;
ResponseData: any;
}
That’s all for now. Thoughts and comments welcome!
Enjoy this post? Don't be a stranger!
Follow me on Twitter at @_josephwoodward and say Hi! I love to learn in the open, meet others in the community and talk Go, software engineering and distributed systems related topics.