Damian Mehers' Blog Evernote and Wearable devices. All opinions my own.

22Oct/080

ASP.NET MVC with the AJAX Control Toolkit: automatically getting control dependencies

[Edited 23rd Oct to compress returned JavaScript]

I recently wrote a blog post showing how you can use controls from the AJAX Control Toolkit in ASP.NET MVC applications, specifically the ListSearchExtender.  Stephen Walther also wrote one on using the Calendar control here.

One thing that bugged me was that it was painful to find out what scripts a particular control depended on, so that they could be included in the JavaScript used to initialize a control.

I decided to create a simple MVC Controller/View that would return back all the JavaScript required for any particular ASP.NET AJAX Control Toolkit control.

This is the old code for using the ListSearchExtender in an ASP.NET MVC View:

<select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
</select>

<script src="/Scripts/MicrosoftAjax.debug.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Common.Common.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.ExtenderBase.BaseScripts.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.DynamicPopulate.DynamicPopulateBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Compat.Timer.Timer.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Animation.Animations.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.Animation.AnimationBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.PopupExtender.PopupBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.PopupControl.PopupControlBehavior.js" type="text/javascript"></script>
<script src="/Scripts/AjaxControlToolkit.ListSearch.ListSearchBehavior.js" type="text/javascript"></script>

<script type="text/javascript">
Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
        { "id": "ListBox1_ListSearchExtender" },
        null, null, $get("Countries"));
});
</script>

This is the new code to pull in the dependencies:

<select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
</select>

<script src="/ControlDependencies/Get?extenderTypeName=AjaxControlToolkit.ListSearchExtender" type="text/javascript"></script>

<script type="text/javascript">
Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
        { "id": "ListBox1_ListSearchExtender" },
        null, null, $get("Countries"));
});
</script>

You'll see that all the individual script includes have been replaced by a single call to the Get action on the ControlDependencies controller.

This is the source to the controller:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Mvc;
using AjaxControlToolkit;

namespace TestDependencies.Controllers {
public class ControlDependenciesController : Controller {
    readonly Assembly toolkitAssembly =
        typeof(AjaxControlToolkit.Utility).Assembly;

    [OutputCache(VaryByParam = "extenderTypeName",
                 Duration = 86400,  // One day
                 Location = System.Web.UI.OutputCacheLocation.Client)]
    public ActionResult Get(string extenderTypeName) {

        if (string.IsNullOrEmpty(extenderTypeName)) {
            return new EmptyResult();
        }

        // Get the type representing the extender we are handling
        Type extenderType = toolkitAssembly.GetType(extenderTypeName,
                                                    false);
        if(extenderType == null) {
            return new EmptyResult();
        }

        // What other extenders does this one depend on?
        Stack<Type> dependencies = new Stack<Type>();
        AddDependencies(extenderType, dependencies);

        // What scripts do those extenders require?
        List<string> scriptsToInclude = new List<string>();
        GetDependencyScripts(dependencies, scriptsToInclude);

        return PartialView(scriptsToInclude);
    }

    // Find the types that the specified extender type depends on
    static void AddDependencies(Type extenderType,
                                Stack<Type> dependencies) {
        dependencies.Push(extenderType);
        Attribute[] attributes =
            Attribute.GetCustomAttributes(extenderType,
                      typeof(RequiredScriptAttribute));

        foreach (RequiredScriptAttribute attribute in attributes) {
            AddDependencies(attribute.ExtenderType, dependencies);
        }
    }

    // Find the scripts used by the specified extender types
    static void GetDependencyScripts(IEnumerable<Type> dependencies,
                                     ICollection<string> scripts) {
        foreach (Type dependency in dependencies) {
            Attribute[] attributes =
                Attribute.GetCustomAttributes(dependency,
                           typeof(ClientScriptResourceAttribute));

            foreach (ClientScriptResourceAttribute attribute in attributes) {
                if (!scripts.Contains(attribute.ResourcePath)) {
                    scripts.Add(attribute.ResourcePath);
                }
            }
        }
    }
}
}

The View is a User Control that simply outputs all of the required scripts.  This is the code-behind:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Web.Mvc;

namespace TestDependencies.Views.ControlDependencies {
  public partial class Get : ViewUserControl<List<string>> {

    protected void Page_Load(object sender, EventArgs e) {
      Response.ContentType = "application/x-javascript";

      // Get compressed stream if possible
      Stream outputStream = GetOutputStream();

      using(StreamWriter outputWriter = new StreamWriter(outputStream)) {

        // Standard AJAX library
        string script = File.ReadAllText(
                              MapPath("/Scripts/MicrosoftAjax.js"));
        outputWriter.WriteLine(script);

        // Required scripts
        foreach(string scriptPath in ViewData.Model) {
          script = File.ReadAllText(MapPath("/Scripts/" + scriptPath));
          outputWriter.WriteLine(script);
        }

      }
      Response.End();
    }

    // Compress scripts if possible -- stolen from ToolkitScriptManager
    // in AJAX Control Toolkit
    private Stream GetOutputStream() {
      Stream outputStream = Response.OutputStream;
      if(!Request.Browser.IsBrowser("IE") ||
                         (6 < Request.Browser.MajorVersion)) {
        foreach(
          string acceptEncoding in (Request.Headers["Accept-Encoding"] ??
                                       "").ToUpperInvariant().Split(',')) {
          if("GZIP" == acceptEncoding) {
            Response.AddHeader("Content-encoding", "gzip");
            outputStream = new GZipStream(outputStream,
                                          CompressionMode.Compress);
            break;
          }
          if("DEFLATE" == acceptEncoding) {
            Response.AddHeader("Content-encoding", "deflate");
            outputStream = new DeflateStream(outputStream,
                                             CompressionMode.Compress);
            break;
          }
        }
      }
      return outputStream;
    }
  }
}

The ASCX is empty:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Get.ascx.cs" Inherits="TestDependencies.Views.ControlDependencies.Get"  %>

There are two advantages to using this technique.  Firstly performance should be increased since all the required scripts are returned in a single response.  Secondly you don't have to manually work out the dependencies for a particular ASP.NET AJAX Control Toolkit control.

Filed under: AJAX, MVC No Comments
7Sep/082

Using ASP.NET MVC and the AJAX Control Toolkit

There are a whole range of useful AJAX behaviors available in the ASP.NET AJAX Control Toolkit, and it would be cool to be able to use them from ASP.NET MVC. 

In this post I'll show you how you can use one of the extenders that I created, the ListSearch Extender, with ASP.NET MVC.

Set up your ASP.NET MVC Project

To start create a new ASP.NET MVC project.

Next download the Microsoft AJAX Library, which contains the Javascript library used in the Microsoft ASP.NET AJAX implementation. Copy the MicrosoftAjaxLibrary folder in the ZIP you downloaded to the Content folder in your MVC project.

Now download the Script Only Files from the ASP.NET AJAX Control Toolkit and copy the AjaxControlToolkit folder in the ZIP to the Content f0lder in your MVC project.

Now that the folders are copied you need to make them visible to the project.  Open the Content branch of your project in the Solution Explorer, and then click on the Show All Files button:

 

Once you do this, you should see the two folders that you copied into the Content folder.  Right click on each folder and select Include In Project.

Now your MVC project will have access to the JavaScript files it requires.

Add the SELECT

To make use of these JavaScript libraries, open the Home action's Index view under Views->Home->Index.aspx.  Modify the HTML to include a simple SELECT List:

<%@ Page Language="C#" ... %>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2> <%= Html.Encode(ViewData["Message"]) %></h2>
    <p>
        To learn more about ASP.NET MVC visit ...
    </p>

    <select id="Countries">
    <option>Switzerland</option>
    <option>United Kindom</option>
    <option>United States</option>
    </select>

</asp:Content>
 

Include the required Javascript files

Next you'll need to have the page pull in the JavaScript files that are used by the ListSearch Extender:

<script type="text/javascript" src="/Content/MicrosoftAjaxLibrary/System.Web.Extensions/3.5.0.0/3.5.21022.8/MicrosoftAjax.debug.js"/>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Common.Common.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.ExtenderBase.BaseScripts.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.DynamicPopulate.DynamicPopulateBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Compat.Timer.Timer.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Animation.Animations.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.Animation.AnimationBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.PopupExtender.PopupBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.PopupControl.PopupControlBehavior.js" type="text/javascript"></script>
<script src="/Content/AjaxControlToolkit/3.0.20820.16598/3.0.20820.0/AjaxControlToolkit.ListSearch.ListSearchBehavior.js" type="text/javascript"></script>

 
You may need to change the version numbers depending on the versions of the Microsoft AJAX library and Toolkits you downloaded.
 
I found out which script files to include by creating a normal ASP.NET Project that used the ListSearch Extender, and set the ScriptManager's ScriptPath property to a new folder I created in the project called /MicrosoftAjaxLibrary, to which I copied the same two folders that I copied to the Content folder above.  I accessed the page, and did a View Source to see what JavaScript was being used.
 

Initialize the Extender

Having included the required JavaScript files, you can now create a new ListSearch Extender and attach it to the SELECT you created above:

<script type="text/javascript">
    Sys.Application.initialize();
    Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ListSearchBehavior,
            { "id": "ListBox1_ListSearchExtender" },
            null, null, $get("Countries"));
    });
</script>
 

Access your page

 
Now when you run you'll get the ListSearch Extender behavior attached to your SELECT:
 

Summary

 
Using the AJAX Control Toolkit is very straight-forward.  The trickiest part is determining exactly what script files you need to include.
 
Now you can use the whole set of behaviors that other people have created to add AJAX goodness to your ASP.NET MVC projects.
Filed under: AJAX, MVC 2 Comments