Deploying the Runtime with ASP.NET Server Controls

In a pure AJAX application architecture, server-based controls are avoided in favor of using a pure client runtime. The one server control you do use, however, is the ScriptManager, which is used to deploy the client runtime to the page. The ScriptManager control also performs a small bit of script injection to specify the authentication status of users when you use it with application services, a capability that I’ll discuss in the Chapter 6. Although you can include the AJAX library’s JavaScript files manually, you’ll generally gain better results by letting ASP.NET handle script registration through this simple control.

Only one instance of the ScriptManager control can be included on a page. If more than one instance occurs, ASP.NET throws an InvalidOperationException. The consequences of this restriction mean that if you’re using master pages, you cannot include a ScriptManager on the master page and on the content page. Instead, you should use the ScriptManagerProxy control on the content page, which adds script references to the main ScriptManager control at compile time.

Tip

Tip

The ScriptManager control is also used to enable partial rendering through the Update Panel, a pseudo-AJAX technique I will not cover in this book.

When you use master pages, the master page should contain the ScriptManager control and any scripts that are common to content pages. (Ideally, you will have only one page for the entire AJAX application, but this is rarely the case.) Example 4-1 defines a simple master page that includes the ScriptManager control.

Example 4-1. The master page contains the ScriptManager control and any core libraries for your application (Web/Master/Example.master).

<%@ Master Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Service Oriented AJAX: Example Master Page</title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
    <link href="../Style/StyleSheet.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="pageform" runat="server">
    <div id="pageHeader">
        Service-Oriented AJAX on the Microsoft Platform
    </div>
    <div id="pageBody">

        <asp:ScriptManager ID="AjaxScriptManager" runat="server" />
        <asp:ContentPlaceHolder ID="MainMasterBody" runat="server">
            Application Content Goes Here
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

When the ScriptManager control is defined on the master page, the ScriptManagerProxy control is used to access the ScriptManager and add script and service references from content pages. Example 4-2 demonstrates a content page that adds the script JSBasics.js by using a ScriptManagerProxy.

Example 4-2. The content page contains custom application code (Web/Default.aspx).

<%@ Page Language="C#" MasterPageFile="~/Master/Example.master" %>

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID=MainMasterBody>
    <asp:ScriptManagerProxy ID="ScriptManagerProxy1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="~/SCRIPT/JSBasics.js " />
        </Scripts>
    </asp:ScriptManagerProxy>

    <div>Hello, Ajax Developers!</div>

</asp:Content>

A new capability in the 3.5 AJAX framework (included in .NET 3.5 service pack 1) is the ability to combine scripts. This capability reduces the number of network calls that a page is required to make for the page to be loaded. Both the ScriptManager and ScriptManagerProxy controls contain the CompositeScript tag, which allows you to generate a single script file from multiple scripts. While you might gain the benefits of fewer network calls on the initial download, the browser will cache the JavaScript on subsequent calls regardless, making this a trivial gain that makes it more difficult to debug. However, you might find it useful in certain situations to combine a portion of your scripts as a logical unit.

More Information

More Information

For more information on composite scripts, see the MSDN online documentation at http://msdn.microsoft.com/system.web.ui.scriptmanager.compositescript.aspx.

Compiled Script Resources

Using the ScriptManager control, you can reference compiled script resources instead of file-based script resources. This lets you deploy scripts in an assembly rather than through the file system. To include a compiled script resource, add the JavaScript file to a class library and choose Embedded Resource as the property of the file. You must then reference the script through the System.Web.UI.WebResource attribute, which makes the script resource accessible through the ASP.NET script resource handler. The name of the file will be the fully qualified resource name, as the following code sample demonstrates. As an example, to include the file DemoScript.js, add it to the root folder of the class library project and set its compile action to Embedded Resource. For an assembly named ScriptDemo, the resource name will be ScriptDemo.DemoScript.js. If the resource name is not in the root folder of the project, it needs to be changed to include the implied namespace based on the folder path. For example, placing the file in a Scripts folder would change the name to ScriptDemo.Scripts.DemoScript.js. The following attribute would be used to enable the compiled resource "DemoScript.js" to be accessible through the script handler:

[assembly: System.Web.UI.WebResource("ScriptDemo.DemoScript.js",
    "application/x-javascript")]

To reference this script in the ScriptManager, use the following entry, noting that the reference is to the named resource (the same name defined in the WebResource attribute) rather than a URL:

<asp:ScriptManager ID="AjaxScriptManager" runat="server"  >
    <Scripts>
        <asp:ScriptReference Assembly="ScriptDemo" Name="ScriptDemo.DemoScript.js" />
    </Scripts>
</asp:ScriptManager>

Tip

Tip

Although you can include scripts that are compiled as resources, I’ve found that doing so makes debugging much more difficult. I recommend including all scripts as files rather than compiled resources.

The Script Manager Programming Model

The ScriptManager exposes a programming model, and it can be used from server-side code to generate script library references and to ensure that the client AJAX runtime is loaded and configured properly. You can implement a ScriptManager wrapper by creating a composite control that checks for the existence of a ScriptManager in the page and add either a reference or create a new instance of the ScriptManager if needed. Using the ScriptManager in a composite control has several important benefits. For example, you can control script caching and also ensure that one and only one ScriptManager is present in situations in which you might not control the entire page runtime. You may be developing components that are deployed to a portal runtime where you may not "own" the page, or you may even be developing controls that are deployed to customers in their own runtime. In these cases, you might want to write code that checks for the presence of the ScriptManager. The following C# code sample can be used within a server control to check for the existence of a ScriptManager and add a ScriptManager to its own controls collection if necessary:

var scriptMan = ScriptManager.GetCurrent(this.Page);
if (scriptMan == null)
{
    scriptMan = new ScriptManager();
    this.Controls.Add(scriptMan);
}

Another benefit you gain from implementing a ScriptManager wrapper is control over script caching in the Web browser. Ideally, a script will be cached liberally by the client browser because JavaScript resources don’t often change. However, a cache policy that is too liberal can cause you to find that older versions of your script files are cached on the client and that users need to clear their cache to receive updated code. One way to work around cache-related bugs is to use a cache key that is appended to a script’s URL. The cache key can be generated in a variety of ways, including a string generated from the assembly signature (which changes with each compilation), the build number, or a custom key that identifies your release. Because script files are cached on the basis of their file name and query string, appending an arbitrary query string that changes with each build ensures that users are always using the latest versions of your scripts and not an earlier version from their cache.

To add a cache key to your script references, you can append a cache query string to the end of the path, as the following code demonstrates. You will also need to check whether the script has a path or is a compiled script resource reference, because the compiled assembly resource has a null Path, and setting it will cause an exception.

foreach (ScriptReference script in this.Scripts){
    if (!string.IsNullOrEmpty(script.Path)){
        char seperator = script.Path.Contains("?") ? '&' : '?';
        script.Path = string.Format("{0}{1}cache={2}", script.Path, seperator, this.CacheKey);
    }
    this.ScriptManager.Scripts.Add(script);
}

For commercial applications, I almost always implement a wrapper for the ScriptManager. Not only does this approach protect me from cached scripts, but in Web Part applications it ensures that my code doesn’t break my customer’s application, where the customer might or might not already have a ScriptManager control. Example 4-3 demonstrates a server control that can be used to encapsulate a client runtime. This control can be used in code or in markup using standard ASP.NET server control syntax.

Example 4-3. The ScriptManager control can be used in a server control to deploy custom script deployment logic (AjaxControls/ScriptRuntimeControl.cs).

using System;
using System.Web.UI;
using System.ComponentModel;
using System.Web;
using System.Drawing.Design;
using System.Globalization;
using System.Security.Permissions;
using System.Reflection;

namespace AjaxControls
{
    [NonVisualControl,
        ToolboxItemFilter("System.Web.Extensions, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35",
        ToolboxItemFilterType.Require),
        DefaultProperty("Scripts"),
        ParseChildren(true),
        PersistChildren(false),
        AspNetHostingPermission(SecurityAction.LinkDemand,
            Level=AspNetHostingPermissionLevel.Minimal),
        AspNetHostingPermission(SecurityAction.InheritanceDemand,
            Level=AspNetHostingPermissionLevel.Minimal)]
    public class ScriptRuntimeControl : Control
    {
        private ScriptReferenceCollection _scripts;
        [Editor("System.Web.UI.Design.CollectionEditorBase, System.Web.Extensions.
Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
                typeof(UITypeEditor)),
            Category("Behavior"),
            PersistenceMode(PersistenceMode.InnerProperty),
            DefaultValue((string)null),
            MergableProperty(false)]
        public ScriptReferenceCollection Scripts
        {
            get
            {
                if (this._scripts == null)
                {
                    this._scripts = new ScriptReferenceCollection();
                }
                return this._scripts;
            }
        }

        private ServiceReferenceCollection _services;
        [PersistenceMode(PersistenceMode.InnerProperty),
            Category("Behavior"),
            Editor("System.Web.UI.Design.CollectionEditorBase, System.Web.Extensions.
Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
            typeof(UITypeEditor)),
            DefaultValue((string)null),
            MergableProperty(false)]
        public ServiceReferenceCollection Services
        {
            get
            {
                if (this._services == null)
                {
                    this._services = new ServiceReferenceCollection();
                }
                return this._services;
            }
        }

        private ScriptManager scriptMan;
        // A null-safe reference to the page's script manager.
        public ScriptManager ScriptManager
        {
            get
            {
                EnsureScriptManger();
                return scriptMan;
            }
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            EnsureScriptManger();
        }

        // Make sure there is only 1 script manager on the page.
        private void EnsureScriptManger()
        {
            if (scriptMan == null)
            {
                scriptMan = ScriptManager.GetCurrent(this.Page);
                if (scriptMan == null)
                {
                    scriptMan = new ScriptManager();
                    this.Controls.Add(scriptMan);
                }
            }
        }

        private string cacheKey;
        /// <summary>A cache key for script paths</summary>
        /// <remarks>Ideally, this would be generated from the build.</remarks>
        public string CacheKey
        {
            get
            {
                if (cacheKey == null)
                    cacheKey = Assembly.GetExecutingAssembly()
                            .ManifestModule.ModuleVersionId.ToString();
                return cacheKey;
            }
        }

        // Add any scripts and service references to the real script manager.
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            this.EnsureChildControls();
            foreach (ScriptReference script in this.Scripts) {
                if (!string.IsNullOrEmpty(script.Path)) {
                    char seperator = script.Path.Contains("?") ? '&' : '?';
                    script.Path = string.Format("{0}{1}cache={2}",
                         script.Path, seperator, this.CacheKey);
                }
                this.ScriptManager.Scripts.Add(script);
            }

            foreach (ServiceReference proxy in this.Services)
            {
                this.ScriptManager.Services.Add(proxy);
            }

            // Common scipt manager cconfiguration:
            this.ScriptManager.EnableHistory = true;
        }
    }
}

The ScriptRuntimeControl demonstrated in Example 4-3 wraps the ScriptManager and provides basic functionality for the client-side AJAX application while also adding a cache key to script URLs to prevent caching of earlier scripts. The cache key is dependent on the assembly’s build, an ideal strategy if you implement a build server. Alternatively, you might want to use another algorithm to build the cache key, such as a file-dependent timestamp.

As shown in the previous examples, the ScriptManager control has a server side API that is useful for configuring custom JavaScript application deployment. You can also package a ScriptManager wrapper that automatically includes your client-side runtime by adding the script references in the prerender method. Now that we’ve covered the server-side interface of the ScriptManager control, let’s take a detailed look at the client-side runtime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset