7820 Views // 0 Comments // Not Rated

Using ASP.NET AJAX 3.5 JSON Web Services Part 2 - A Client MVC Architecture For Your Pages

JSON Web Services really are the jumping off point between server-centric and client-centric web development. You can add all of the pretty, JavaScript-y, AJAX-y sugar you want to your UI, but if your page or control posts back, (weather or not it is asynchronously via an Update Panel) then it is still server-centric in terms of logic.

But by off-loading your logic to JSON Web Services, (as I introduced in Part 1 here) you are making your pages themselves each act like a nice client-centric MVC application. The HTML is obviously the view, the web services act as the model, since they will be processing business objects for persistence or returning them to the UI for consumption. Finally, a JavaScript file sits in between, acting as a controller.

Let's break it down with some code, then some discussion:

Model - Web Service

Code Listing 1

  1. using System;
  2. using System.Web.Services;
  3. using System.Web.Script.Services;
  4. namespace WebApplication1
  5. {
  6. [WebService(Namespace = "http://tempuri.org/")]
  7. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  8. [ScriptService]
  9. public class JSONTestService : WebService
  10. {
  11. [WebMethod]
  12. public void SetTest(Guid id, string text)
  13. {
  14. this.Application.Add(id.ToString(), text);
  15. }
  16. [WebMethod]
  17. public string GetTest(Guid id)
  18. {
  19. return this.Application[id.ToString()].ToString();
  20. }
  21. [WebMethod]
  22. public Guid CreateNew()
  23. {
  24. return Guid.NewGuid();
  25. }
  26. }
  27. }

Granted, this is only an example, but normally, these web services will be processing full business objects. What I do is actually keep a variable on the client that stores my object, manipulate its properties via client-side events, (both of which are in the JavaScript file), and then pass the whole object to a web service for server-side manipulation.

So for this example, I'll use a guid as by business object.

View - Page / Control Markup

Code Listing 2

  1. <script type="text/javascript" language="javascript">
  2. var txtGetTestID = '<% =this.txtGetTest.ClientID %>';
  3. var txtSetTestID = '<% =this.txtSetTest.ClientID %>';
  4. </script>
  5. <table>
  6. <tr>
  7. <td>
  8. &nbsp;
  9. </td>
  10. <td>
  11. <asp:Button id="btnNew" runat="server" Text="New" OnClientClick="GetNew(); return false;" />
  12. </td>
  13. </tr>
  14. <tr>
  15. <td>
  16. <asp:TextBox ID="txtSetTest" runat="server" />
  17. </td>
  18. <td>
  19. <asp:Button ID="btnSetTest" runat="server" Text="Set" OnClientClick="SetTest(); return false;" />
  20. </td>
  21. </tr>
  22. <tr>
  23. <td>
  24. <asp:TextBox ID="txtGetTest" runat="server" />
  25. </td>
  26. <td>
  27. <asp:Button ID="btnGet" runat="server" Text="Get" OnClientClick="GetTest(); return false;" />
  28. </td>
  29. </tr>
  30. </table>

The only embedded JavaScript here is for variable declarations. Loose JavaScript files can't use the <% ... %> construct to call into .NET code, since they are not in the context of a page or control. So I like to expose the controls I'll need to manipulate this way, and then use $get() in my JavaScript file to get a reference to the control.

NOTE: you can still use server controls, and take advantage of the extensions they add to HTML controls. However, we don't want to cause a post back, as that will kill the state of our client objects. So for controls that always do an AutoPostBack, (like buttons) add "return false;" to end of the "client" event; this will not call __doPostback() behind the scenes.

Controller - JavaScript file

Code Listing 3

  1. var _guid = null;
  2. function GetNew()
  3. {
  4. WebApplicationJSONTestService.CreateNew(GetNewDone, OnError, null);
  5. }
  6. function GetNewDone(result)
  7. {
  8. _guid = result;
  9. }
  10. function SetTest()
  11. {
  12. WebApplicationJSONTestService.SetTest(_guid, $get(txtSetTestID).value, null, OnError, null);
  13. }
  14. function GetTest()
  15. {
  16. WebApplicationJSONTestService.GetTest(_guid, GetTestDone, OnError, null);
  17. }
  18. function GetTestDone(result)
  19. {
  20. $get(txtGetTestID).value = result;
  21. }
  22. function OnError(ex)
  23. {
  24. alert('Error: ' + ex._message);
  25. }

If any of the server code in a web method on your web service throws an exception, the OnError JavaScript function here will be called. The variable passed is a JSON serialized Exception object, providing the description of the error, as well as a full stack trace! This way, you can still catch-and-log-and-rethrow your exceptions on the server, and gracefully deal with them on the client.

Another benefit to using a loose JavaScript file as the controller is that it provides a rich debugging experience. I wrote more about that here.

I know that using a JavaScript file as a controller in the MVC architecture is a stretch, but it works. Since you can swap .js files on the fly, they are kind of a configurable broker; as long as they handle events on the client and can, in response, call services on the server, the paradigm fits. This way, you can completely replace your back end, and then merely tweak your JavaScript file to keep communication between your persistence and your UI flowing.

The only thing left to do is wire everything up in the code-behind of the page / control:

Code Listing 4

  1. using System;
  2. using System.Web.UI;
  3. namespace WebApplication1
  4. {
  5. public partial class JSONTest : UserControl //or Page
  6. {
  7. protected void Page_Load(object sender, EventArgs e)
  8. {
  9. if (!this.Page.IsPostBack)
  10. {
  11. ScriptManager sm = ScriptManager.GetCurrent(this.Page);
  12. sm.Scripts.Add(new ScriptReference("/JSONTest.js"));
  13. sm.Services.Add(new ServiceReference("/JSONTestService.asmx"));
  14. }
  15. }
  16. }
  17. }

And guess what: that might be all the code you need write in your page! In fact, since I've adopted this architecture, the only code-behind my pages and controls have is in the Load event, if IsPostBack is false. This separation of logic from presentation makes for a much cleaner ASPX / ASCX file, both in terms of managed code behind and HTML!

Despite the fact that the implementation of the "model" web services is still .NET code, they are called asynchronously though the AJAX web service infrastructure, not via a form submit, and therefore do not cause the Load event of your page to fire. This really does away with the page lifecycle as we know it. This way, we can move all of the business logic from the page to JavaScript, and then use JSON Web Services to either do persistence and call other services, or do any processing that is too complex for, or not supported by, JavaScript (for example, guids).

Speaking of the implementation of these web methods, there is more thing to point out. In the first post I linked to in this article, I talk about one main "reality check" of JSON Web Services: they do not share the same Session as the UI. So my workaround for this is to use the Application object, and store objects in it keyed off of guids.

This way, although all of instances of our app are using the same Application object, there will be no name collisions. And as far as the server is concerned, and object in memory is an object in memory; there shouldn't be any performance ramifications using this method verses using Session state.

All you have to do is keep track of your guid on the client, and dispose of objects when you're done using them on the server. The Cache object, with it's built-in expirations, might be even a better option. The best way to go, however, would be to write a custom module for this type of persistence; using the Application object, however, has worked out fine for me so far.

In conclusion, this is probably the closest we will come to having the client directly call .NET code on-the-fly before SilverLight takes over the web. But until we are all speaking XAML, consider my client MVC architecture, especially if your page is doing a lot of animation or dynamic control creation. Moving as much logic from managed code and embedded JavaScript to script controls and JSON Web Services can really make your pages dazzle.

4 Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.