Thursday, October 9, 2008

Using the ASP.NET MVC Framework as a JSON Service Provider for Richer Web Applications with MS Ajax Templates and jQuery


Wow, that’s a really long title, but that is what this post is actually about, and why I think it is a great way to handle many web applications today.

I have recently been doing a lot more Javascript Client side applications and working a lot closer with JSON objects to build complex web pages. The problem I typically run into is getting my C# POCO objects to translate to a JSON Object on the client side so I can work with it and change it and then post it back to the server to handle it appropriately.

The basics of what I wanted was to seamlessly work with my Model data and it automatically handle converting to JSON or C# Objects at the correct layer.

I have used ADO.NET Data Services and Entity Framework (with the EF POCO Adapter project by Jaroslaw Kowalski @ here - which by the way is a preview of how POCO will work in Entity Framework 2.0, and has absolutely changed my mindset on Entity Framework).

First, Some Background

Using ADO.NET Data Services allowed me to utilize jQuery as the controller and presentation layer, and the ADO.NET Data Service and Entity Framework as the model. However, if I wanted to split up my Domain specific logic from my individual application business logic on the server side, I would have some work to do. Suppose that you already have a Service(Business) layer on the server side that setup your POCO objects and handled application specific logic before pushing it down to the Data-Layer (in my instance the EFPocoAdapter) or handled loading data in a very specific way, that would not be easy to do using ADO.NET Data Services.

I can push Domain specific rules by tying into the Adapter from EFPOCOAdapter using the following way:
clip_image002

Example of such a POCO Object tied into EFPocoAdapter:clip_image004

As you can see, my User model object represents a user in my system. I am tying that entity to IDomainEntity to specify that it requires Domain Validation, and when my Adapter in my Domain Layer saves this Model, it will run validation rules against it to verify it is correct.

However, if I had Business Logic I wanted to impose in my Service layer that does not belong in my Domain Data Layer, I cannot easily use ADO.NET Data Services without some additional work.

So in essence I want a to structure my Layers as such:
Model Layer => Domain Data-Layer (EFPocoAdapter) => Service Layer (Business)

The above would represent my Model for the MVC Framework. By doing this, I can cleanly separate out each concern in its respective layers and maintain reusability and simplicity.

However, if ADO.NET Data Services cannot get me there easily, what do I do? I could use WCF and that has worked fine for me in the past, however, since I fell in love with the MVC Framework, I wanted to see how I can utilize that.

Alright, so what is this post really about?

With that bit of history out of the way, what is my intent here? I essentially want to utilize ASP.NET MVC to make a Web Service Provider that returns to me my C# model objects in JSON Format, and allow me to post that Javascript object back to the server and it automatically convert it back to a C# object to process on the server.

So in essence, architecturally speaking, the Javascript logic becomes the Presenter that calls to the Server-Side Controller, that will call the Service Layer to get the Model Data from the Domain Layer.

Breakdown:

Client Request For Data
Html View with Template Binding
||
Javascript Presenter (Request JSON Data)
||
Server-Side Controller (gets C# Model Data and sends as JSON Data)
||
Service Layer (Business logic to call to the Domain Layer)
||
Domain Layer (Entity Framework POCO Adapter Preview)
||
Model POCO Objects (simple C# objects that represent Model Data)

Client Post Javascript Object Model Data
Html View with Template Binding
(2-way binding updates JSON object as changes made)
||
Javascript Presenter (Posts JSON Data)
||
Server-Side Controller (auto serializes JSON to C# Model Parameter)
||
Service Layer (Business logic update to the Domain Layer)
||
Domain Layer (Entity Framework POCO Adapter Preview)
||
Model POCO Objects (simple C# objects that represent Model Data)

In a way, I want to seamlessly convert between C# objects to Javascript objects without me doing any additional work than is necessary and utilize some new features such as Templating to get a two-way binding to that Javascript object so, again, I have little work to do.

So how did I do it? Well, with the help of Scott Hanselman in this post (here) and Omar AL Zabir in this post (here) I had enough tools to accomplish what I needed.

How did I do it?

First I needed a way to convert a C# model object to a Javascript object from MVC. I created the following ActionResult class to accomplish this:clip_image006
Notice that depending on the content type, it will return JSON or XML. The standard JsonResult type in ASP.NET MVC requires that I know I want JSON. Utilizing a known mechanism in HTTP to specify what you want: the content type. There is no need to re-invent the wheel here, content type is the requested type you are expecting.

Next I needed a way to convert a Javascript object back to a C# object in the parameter of a MVC Action. I created the following ActionFilterAttribute to accomplish this:
clip_image008

I then set-up a test Controller to test this setup:
clip_image010

Notice that I am returning on GetUserList Action a list of Users that is a C# Domain Object and depending on the content type from the client, will return XML or JSON.

Next notice that I am marking up the “POST” action UploadUser to convert the parameter User[] from a JSON Format to a C# Domain Object so that I can then use my Service layer to save it or whatever I want to do with it. I then return back a JSON response back to the client, along with a possibly changed Model object for the client to rebind to.

Now, I need a client side script to help me handle calling and tying everything together. For this, I included the following Javascript Libraries:

  • MicrosoftAjax.js
  • MicrosoftAjaxTemplates.js (you can find this at CodePlex under ASP.NET)
  • jQuery
  • JSON2 *the version changed by Rick Strahl* (you can use whatever you want, I just like this one)
  • A custom Helper jQuery Plugin to help make everything simpler :
    clip_image012clip_image014

This custom Javascript library will allow me to make my Javascript Client code less terse, and easier to maintain.

Next, let’s setup a View to test this scenario:
clip_image016
clip_image018
There is a lot going on here, but to put it simply, I am using MVC to route the user to this Test View that will load up the User data via a JSON Endpoint request and once that C# Model object is transformed to a Javascript object, I use Microsoft Templating to two-way bind that to my controls. As the user changes values, it is automatically updating the Javascript object, just as it would in WPF. Once the user is finished, they can click the save button, and the client side code will make a Javascript call passing that Javascript object back to the server MVC endpoint (Controller Action). That action will automatically convert that JSON object to its equivalent C# Model object, so that my controller can now save it or whatever it wants, and then respond back to that AJAX request with any errors or messages, as well as a possibly modified Model Javascript object for my View to rebind to.

Phew, that may seem complicated, but it’s really very simple, and requires very little to accomplish once all of your code is in place.

Enough about what it is doing behind the scenes, show us results!

Upon browsing to my http://localhost/home I am greeted with the following:
clip_image020

As I change the values in the text box, notice that the Javascript object on the client is changing also (I have not posted back yet!):
clip_image022

After I post back:

clip_image024

I am testing to see that if some of my Domain logic or application logic fails, that I can get that information back to the client to fix. To test this, the success of the AJAX post will be considered a failure (as you can see from the Test Controller code) if any value has the word “fail” in it. This will allow me to change my C# Model object slightly from the controller and push it back to the client as a Javascript object to rebind the controls to. This will allow the user to fix any issues they may have and repost if necessary.

Here is the Debug against the post so you can see how the Javascript object is transformed into a C# object:

clip_image026

As you can see, once setup with the AjaxResult, AjaxParameterAttribute, and the custom jQuery Plug-in Client-side helper, it is a snap to build out AJAX driven pages and not have to worry about going from server side objects to client side objects. This to me brings us much closer to a Silverlight/Flash type application, especially when considering the speed increases in Javascript on all major browsers coming.

Source Code

Questions, comments, ideas?


kick it on DotNetKicks.com

3 comments:

Anonymous said...

It is interesting too see you call your User class a POCO with all that datacontract attribute goo on it. Is this the way it will work with the EF 'pocos' as well?

Corey J Gaudin said...

Actually no. It requires no Attributes "goo" on it. In fact, I really didnt have to add that "goo" to it if I had used JSON.NET, which I do on several projects. But for some reason the Json Serializer in WCF needs this to work correctly. I thought I would use the Built in JSON Serializer in WCF for this example.

Corey J Gaudin said...

To be clear, what I meant to say is the Attribute Goo, was to correctly serialize out the User Object to JSON Representation on the Client side, and has no bearing on EF Adapter POCO project as it is a Straight POCO as you want it to be.

What I dont get is all the hate with Entity Framework in our community. Here we have Microsoft actually trying to hear our complaints and fix it in a usable manner, and most people dont even care to see where things are going and help them move the product in a usable way.