Using ASP.NET AJAX with ASP.NET MVC

Yes, you can use ASP.NET AJAX with ASP.NET MVC. Several people have asked me recently how you can use ASP.NET AJAX in an ASP.NET MVC view. In this blog entry, I’m going to explain the problem and the solution.

The Problem

Normally, if you want to use ASP.NET AJAX in an ASP.NET page, you add a ScriptManager control to the page. The ScriptManager control requires a server-side form control. Therefore, in order to use a ScriptManager control, you must include a server-side form control in a page.

Here’s the problem. You should not include a server-side form control in an ASP.NET MVC view. Why not? Using a server-side form control violates the spirit of ASP.NET MVC since adding a form control forces you back into the Web Forms page model that forces you to use postbacks and view state.

Therefore, many people have concluded, ASP.NET AJAX is not compatible with ASP.NET MVC.

The Solution

The solution is simple, don’t use the ScriptManager control. Instead, just include the Microsoft AJAX Library with a standard <script src=”MicrosoftAjax.js”></script> tag.

You can download the standalone version of the Microsoft AJAX Library from the following location:

http://www.asp.net/ajax/downloads/

The download includes multiple versions of the ASP.NET AJAX Library. The two most important scripts are named MicrosoftAjax.js and MicrosoftAjax.debug.js (the download also includes localized versions of the Microsoft AJAX Library). The MicrosoftAjax.js script is the production version of the library and the MicrosoftAjax.debug.js script is the debug version of the library.

You can copy both the MicrosoftAjax.js and MicrosoftAjax.debug.js scripts directly into an ASP.NET MVC Web Application Project. A good location to add these scripts within an ASP.NET MVC application is the Content folder. After you add the scripts to your project, you can reference either the production or the debug version of the scripts within your views (or a master page).

For example, the view in Listing 1 uses the Microsoft AJAX Library to wire-up a button click handler.

Listing 1 – TestAjax.aspx

   1:  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestAjax.aspx.cs" Inherits="FirstMVCApp.Views.Test.TestAjax" %>
   2:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3:  <html xmlns="http://www.w3.org/1999/xhtml" >
   4:  <head runat="server">
   5:      <title>Test Ajax</title>
   6:      <script type="text/javascript" src="../../Content/MicrosoftAjax.debug.js"></script>
   7:      <script type="text/javascript">
   8:      
   9:          function pageLoad()
  10:          {
  11:              $addHandler( $get("btn"), "click", doSomething);
  12:          }
  13:          
  14:          function doSomething()
  15:          {
  16:              alert("Button clicked!");
  17:          }
  18:      
  19:      </script>
  20:  </head>
  21:  <body>
  22:      <div>
  23:   
  24:      <input id="btn" type="button" value="Click Here!" />
  25:   
  26:      </div>
  27:  </body>
  28:  </html>

The doSomething() JavaScript method is wired to the btn Click event within the pageLoad method. When you click the button, the alert “Button clicked!” is displayed (see Figure 1).

clip_image002

Figure 1 – Using the Microsoft AJAX Library in an MVC View

The page in Listing 1 uses the debug version of the Microsoft AJAX Library. The debug version of the library contains extra code that checks, for example, whether you are passing the right parameters to a method. In production, you should switch from the MicrosoftAjax.debug.js file to the MicrosoftAjax.js file. The MicrosoftAjax.js file has been minimized and it has been stripped of any debug code.

To make it easy to switch back and forth between the debug and production version of the Microsoft AJAX Library, you might want to add the script reference to a master page instead of each individual page.

What about Service References?

One of the nice things about using the ScriptManager control in a normal ASP.NET page is that you can use the control to easily add a reference to either a WCF or an ASMX Web Service. For example, if you want to call the MyService Web Service from your client-side JavaScript, then you can add a service reference like this:

    <asp:ScriptManager ID="sm1" runat="server">
    <Services>
        <asp:ServiceReference Path="/Services/MyService.asmx" />
    </Services>
    </asp:ScriptManager>

Since you should not use a ScriptManager control in an MVC view, you can’t add a service reference to a view in the same way. So, how do you call a web service?

The view in Listing 2 demonstrates how you can call the MyService.asmx service from an MVC view:

Listing 2 – TestService.aspx

   1:  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestService.aspx.cs" Inherits="FirstMVCApp.Views.Test.TestService" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <html xmlns="http://www.w3.org/1999/xhtml" >
   6:  <head runat="server">
   7:      <title>Test Service</title>
   8:      <script type="text/javascript" src="../../Content/MicrosoftAjax.debug.js"></script>
   9:      <script type="text/javascript">
  10:      
  11:          function pageLoad()
  12:          {
  13:              Sys.Net.WebServiceProxy.invoke
  14:              (
  15:                  "../../Services/MyService.asmx", 
  16:                  "Select", 
  17:                  false, 
  18:                  null, 
  19:                  success, 
  20:                  fail
  21:              );
  22:          }
  23:      
  24:      
  25:          function success(results)
  26:          {
  27:              alert(results)
  28:          }
  29:      
  30:          function fail(err)
  31:          {
  32:              alert( "ERROR: " + err.get_message() );
  33:          }
  34:      
  35:      </script>
  36:  </head>
  37:  <body>
  38:      <div>
  39:      
  40:      </div>
  41:  </body>
  42:  </html>

Notice that the Sys.Net.WebServiceProxy.invoke() method is called in the pageLoad() function. This method invokes the Web Service. The invoke() method accepts the following parameters:

· servicePath – The path to the WCF or ASMX Web Service

· methodName – The name of the web method to call

· useGet – Determines whether to use GET or POST (GET is disabled by default)

· params – An object literal that represents a list of parameters to pass to the web method

· onSuccess – The JavaScript function to call if the web service call is successful

· onFailure – The JavaScript function to call if the web service call is not successful

· userContext – Arbitrary data passed back to the client

· timeout – The amount of time before the web service all times out

You can use the page in Listing 2 with the ASMX Web Service in Listing 3.

Listing 3 – MyService.asmx

   1:  using System;
   2:  using System.Collections;
   3:  using System.ComponentModel;
   4:  using System.Data;
   5:  using System.Linq;
   6:  using System.Web;
   7:  using System.Web.Services;
   8:  using System.Web.Services.Protocols;
   9:  using System.Xml.Linq;
  10:  using System.Collections.Generic;
  11:   
  12:  namespace FirstMVCApp.Services
  13:  {
  14:      [WebService(Namespace = "http://tempuri.org/")]
  15:      [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  16:      [ToolboxItem(false)]
  17:      [System.Web.Script.Services.ScriptService]
  18:      public class MyService : System.Web.Services.WebService
  19:      {
  20:   
  21:          [WebMethod]
  22:          public string Select()
  23:          {
  24:              var quotes = new List<string>()
  25:              {
  26:                  "A stitch in time saves nine",
  27:                  "Man is the measure of all things",
  28:                  "Look before you leap"
  29:              };
  30:   
  31:              Random rnd = new Random();
  32:              return quotes[rnd.Next(quotes.Count)];
  33:          }
  34:      }
  35:  }

The Web Service in Listing 3 returns a random quotation. Notice that the Web Service includes a [ScriptService] attribute. You must include this attribute when you want to be able to call a Web Service from client-side script.

When you request the page in Listing 2, the random quotation is displayed in an alert box (see Figure 2).

clip_image004

Figure 2 – Calling a Web Service from an MVC View

What about the UpdatePanel Control?

The UpdatePanel, when used in a normal ASP.NET page, enables you to update the content of part of a page without updating the content of the entire page (it enables you to avoid a full postback by performing a sneaky postback). How do you use the UpdatePanel control in an MVC view?

The UpdatePanel cannot be used in a page that does not contain a ScriptManager control. Therefore, since the ScriptManager control depends on the server-side form control and you should not use a server-side form control in an MVC view, the UpdatePanel cannot be used in an MVC view.

But don’t worry, you have another option. You don’t need to use the UpdatePanel control to perform a partial view update using the Microsoft AJAX Library. Instead, you can use the Sys.Net.WebRequest object.

For example, the view in Listing 4 contains two buttons. When you click the first button, the contents of the Content1.htm file are pasted into a DIV tag named up1. When you click the second button, the contents of the Content2.htm file are pasted into the DIV tag. The page performs a partial update using the Microsoft AJAX Library support for making AJAX calls.

Listing 4 – TestUpdatePanel.aspx

   1:  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestUpdatePanel.aspx.cs" Inherits="FirstMVCApp.Views.Test.TestUpdatePanel" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <html xmlns="http://www.w3.org/1999/xhtml" >
   6:  <head runat="server">
   7:      <title>Test UpdatePanel</title>
   8:      <script type="text/javascript" src="../../Content/MicrosoftAjax.debug.js"></script>
   9:      <script type="text/javascript">
  10:      
  11:          function pageLoad()
  12:          {
  13:              $addHandler($get("btn1"), "click", 
  14:                  function() {getContent("../../Content/Content1.htm"); } );
  15:              $addHandler($get("btn2"), "click", 
  16:                  function() {getContent("../../Content/Content2.htm"); } );        
  17:          }
  18:   
  19:          function getContent(url)
  20:          {
  21:              var request = new Sys.Net.WebRequest();
  22:              request.set_url(url);
  23:              request.set_httpVerb("GET");
  24:              request.add_completed(updatePage);
  25:              request.invoke();        
  26:          }
  27:          
  28:          function updatePage(executor, eventArgs)
  29:          {
  30:              if(executor.get_responseAvailable()) 
  31:              {
  32:                  $get("up1").innerHTML = executor.get_responseData();
  33:              }
  34:              else
  35:              {
  36:                  if (executor.get_timedOut())
  37:                      alert("Timed Out");
  38:                  else if (executor.get_aborted())
  39:                      alert("Aborted");
  40:              }
  41:          }
  42:          
  43:      </script>
  44:  </head>
  45:  <body>
  46:      <div>
  47:          
  48:          <input 
  49:              id="btn1" 
  50:              type="button" 
  51:              value="Content 1" />
  52:          
  53:          <input 
  54:              id="btn2" 
  55:              type="button" 
  56:              value="Content 2" />
  57:          
  58:          <div id="up1"></div>
  59:      
  60:      </div>
  61:  </body>
  62:  </html>

In Listing 4, the pageLoad() method is used to wire-up the two buttons in the body of the page to the getContent() JavaScript function. When you click the first button, the URL "../../Content/Content1.htm" is passed to the getContent() method. When you click the second button, the URL "../../Content/Content2.htm" is passed to the getContent() method.

The getContent() method performs all of the work. In this method, a WebRequest object is created. Two properties of this object are set: the URL and the HTTP Verb. A handler is setup for the WebRequest so that when the WebRequest object is invoked, the updatePage() method is called.

The updatePage() method simply updates the innerHTML of a DIV tag named up1. Updating the innerHTML of an element updates the content of the page dynamically.

The contents of Content1.htm are contained in Listing 5 and the contents of Content2.htm are contained in Listing 6.

Listing 5 – Content1.htm

   1:  <b>Hello from Content1 !!!</b>

Listing 6 – Content2.htm

   1:  <b>Hello from Content2 !!!</b>

Neither Content1.htm nor Content2.htm can contain server-side controls. That’s okay in the MVC universe since we are trying to avoid (heavy weight) server-side controls anyway.

Conclusion

The purpose of this blog entry was to explain how to use the Microsoft AJAX Library in an MVC Web Application. I explained how to add script references, add service references, and perform partial page updates without using the ScriptManager or UpdatePanel controls. Microsoft ASP.NET AJAX works just fine with Microsoft ASP.NET MVC.

16 Comments

  • Good work Stephen!!!

    Last week I was fighting with this problems in my test application and I had to evaluate others alternatives like JQuery.

    I'll try it

    Thanks in advance!!!

  • How about using server controls in MVC? I wrote this script to remove the ugly container control prefixes from the request, to enable MVC runtime to match server control's input data with Action argument names. It works fine for me!

    Application_BeginRequest:

    var form = HttpContext.Current.Request.Form;

    form.GetType().GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
    .SetValue(form, false, null);

    foreach (var key in form.AllKeys.Where(key => key.Contains("$")))
    {
    var value = form[key];
    form.Remove(key);
    var newKey = key.Substring(key.LastIndexOf("$") + 1);
    form.Add(newKey, value);
    }

  • @Paymon,

    Nice code! I like how you make the Request.Form collection writable. I suspect that MVC purists would not be happy with your approach. The problem is that the current server controls are too closely tied to the Web Forms model. It's not just the NamingContainer issue, it is also the view state, postback, only one form per page, extra JavaScript, no control over markup, and auto-generated id issue.

  • Thanks Stephen, I haven't had a chance to get into MVC yet, but this helps.

  • Any idea how I can use the new MediaPlayer control in a MVC scenario?

  • Good stuff. Can you share the source codes for these examples listed above? Any help? Thanks.

  • Nice post... there´s microsoft javascript framework like script.auculo.us?

  • Very good article. Thanks!!
    Could you make a followup on how to use some of the extenders in the toolkit as well (the calendar for example)? :-P

  • Stephen, it is unclear to me whether this is how AJAX will work in the RTM MVC architecture, or whether this is just "how to do it today". Can you comment on that? I noticed that AJAX MVC is a topic in your upcoming MVC book. Is this how it's going to work?

    Thanks

  • @Kevin - Great question! I wrote this blog post quite a while ago (before I started working at Microsoft). The details of how AJAX will work in MVC are still being worked out. However, it will certainly be easier than the way that I describe in this post.

  • This is an excellent post.. Exactly what I was looking for.. I was not able to figure out the AJAX + MVC mantra .. great going Stephen

  • I like it very much, if you make video tutorial it will very wonderful

  • Using asp net ajax with asp net mvc.. Outstanding :)

  • Using asp net ajax with asp net mvc.. Retweeted it :)

  • was all my fault. I was tniryg this under VS 2010, ASP.NET MVC 1.1 and din't change all assembly references as needed. It works fine now and without any changes.

  • Absolutely great book so far. Really like the loauyt/format and your explanations are very good. Learning a lot. Highly recommend this to anyone on the fence as far as spending the extra money.

Comments have been disabled for this content.