Subscribe to this Blog

Subscribe to this Blog

Using ASP.NET AJAX with ASP.NET MVC - Stephen Walther on ASP.NET MVC

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.

Published Friday, March 14, 2008 1:20 AM by swalther
Filed under: ,

Comments

# re: Using ASP.NET AJAX with ASP.NET MVC

Friday, March 14, 2008 5:54 AM by Luis Ruiz Pavón

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!!!

# re: Using ASP.NET AJAX with ASP.NET MVC

Friday, March 14, 2008 9:53 AM by Paymon

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);

}

# re: Using ASP.NET AJAX with ASP.NET MVC

Friday, March 14, 2008 12:37 PM by swalther

@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.

# re: Using ASP.NET AJAX with ASP.NET MVC

Friday, March 14, 2008 1:34 PM by jamesshaw

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

# re: Using ASP.NET AJAX with ASP.NET MVC

Saturday, March 22, 2008 7:40 AM by Rainer

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

# re: Using ASP.NET AJAX with ASP.NET MVC

Thursday, March 27, 2008 10:01 AM by Chua Wen Ching

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

# re: Using ASP.NET AJAX with ASP.NET MVC

Wednesday, April 16, 2008 1:03 PM by Raf4

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

# re: Using ASP.NET AJAX with ASP.NET MVC

Tuesday, April 22, 2008 4:28 AM by skaue

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

# links for 2008-06-21 &laquo; Praveen&#8217;s Blog

Saturday, June 21, 2008 7:31 AM by links for 2008-06-21 « Praveen’s Blog

Pingback from  links for 2008-06-21 &laquo; Praveen&#8217;s Blog

# What I Brought from Developer Connection at Orlando,Florida

Tuesday, July 22, 2008 12:44 PM by Rabin Limbu's Blog

Held on April 20-23, it was an amazing experience to see so many passionate developers like us and learn

# re: Using ASP.NET AJAX with ASP.NET MVC

Wednesday, August 27, 2008 1:06 PM by Kevin

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

# re: Using ASP.NET AJAX with ASP.NET MVC

Wednesday, August 27, 2008 11:10 PM by swalther

@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.

# re: Using ASP.NET AJAX with ASP.NET MVC

Sunday, September 14, 2008 4:22 AM by vsood

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

# ASP.net AJAX and MVC

Sunday, September 14, 2008 4:31 AM by DotNetKicks.com

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# re: Using ASP.NET AJAX with ASP.NET MVC

Monday, October 13, 2008 4:34 AM by Dang Khoa

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

# ASP.NET MVC Archived Buzz, Page 1

Tuesday, November 18, 2008 4:42 AM by ASP.NET MVC Archived Buzz, Page 1

Pingback from  ASP.NET MVC Archived Buzz, Page 1

Leave a Comment

(required) 
(required) 
(optional)
(required)