Building Rich Internet Applications with ASP.NET AJAX


To begin using ASP.NET AJAX, install the ASP.NET AJAX Framework from http://ajax.asp.net. The ASP.NET 2.0 AJAX SDK, server runtime, source code, and documentation can be installed from http://ajax.asp.net and is a prerequisite for the code samples in this chapter. ASP.NET AJAX installs System.Web.Extensions to the Global Assembly Cache as a Microsoft-supported extension to ASP.NET. Similar to other components in ASP.NET, objects within the ASP.NET AJAX Framework exist in the System.Web namespace.

To be an effective AJAX developer, you need a fundamental understanding of JavaScript. Microsoft extends JavaScript through the Microsoft AJAX Library, with which you can create rich Web interfaces and call backend data streams from the browser without writing a great deal of overly complicated JavaScript. You will write JavaScript, but you will use Microsoft’s code library for the plumbing.

For AJAX code in WSS, we will use the SharePoint AJAX Toolkit that further abstracts JavaScript programming tasks for Web Parts. Although you will be writing more JavaScript in AJAX applications than in non-AJAX applications, the Microsoft AJAX Library adds a rich library of client-side API calls and adds enhancements to the object-oriented capabilities of JavaScript. Through libraries defined in the Microsoft AJAX Library and SharePoint AJAX Toolkit, the code you write will be greatly simplified. However, an understanding of the JavaScript language and Microsoft’s client-side APIs is essential for developers.

Tip 

Script debugging tools used directly in the browser are essential for effective AJAX development. Essential debugging tools include Nikhil Kothari’s Web Development Helper for Microsoft Internet Explorer 7.0, available from http://projects.nikhilk.net/Projects/WebDevHelper.aspx and FireBug for Mozilla Firefox 2.0, available from http://www.getfirebug.com.

The ASP.NET AJAX Update Panel is a popular choice for creating dynamic data grids in ASP.NET applications. However, it is only a minor part of ASP.NET AJAX and is used mainly to enable AJAX responsiveness in server-side controls. One drawback to the Update Panel is that it promotes a tightly coupled architecture in which the control is bound to a server-side data source that is not directly exposed to the client. With a pure AJAX architecture, the data sources are exposed to the client in the form of XML, JSON, or text data streams that can be consumed by multiple controls. AJAX pushes the presentation all the way down to the client and promotes server-side architectures based on services rather than controls. In the long run, the pure AJAX architecture is more flexible because it promotes reuse while exposing data sources to remote applications. Though the Update Panel has its place, we will not use the Update Panel in this book as it is a server-side technology that detracts from the AJAX programming model. To build truly dynamic rich Internet applications, you must embrace the client-side programming model of JavaScript components built against backend data services and dynamic manipulation of the DOM.

Object-Oriented JavaScript with ASP.NET AJAX

When creating JavaScript AJAX components, we will create classes just like we would build a class library in C#. We won’t base them on a specific implementation, but we will program generic controls that a script runtime will initialize for the page. Though many programmers use JavaScript for pure procedural programming, JavaScript is, in fact, a rich object-oriented language. Because of its interpreted nature as a scripting language, it was misunderstood, misused, and neglected during the early 2000s as developers embraced pure server-oriented environments. But with advances in developer tools and AJAX development trends, JavaScript is an essential language to master for modern rich Internet applications. The Microsoft ASP.NET AJAX Script Library builds on the object-oriented nature of JavaScript and adds conventions similar to those found in .NET languages. The following conventions and techniques will help you write more robust script components and frameworks.

Namespaces

Namespaces can be used in script to differentiate your classes from other frameworks, just as namespaces within the .NET Framework prevent naming clashes between class libraries. To define a namespace, use the Type.registerNamespace method. To check whether a namespace is defined, simply check for its existence with the typeof function.

 if (typeof(Litware) == 'undefined')     Type.registerNamespace('Litware');

Functions as Objects

Functions are used to create objects when called with the new keyword. For example, the following code is used to define and instantiate an EditableControl object without a namespace.

 function EditableControl(){} var exampleControl = new EditableControl();

When defining a function within a namespace, you need to use the following syntax:

 Litware.EditableControl = function(){} var editControl = new Litware.EditableControl();

Note that you cannot create a function in a namespace by declaring the function. You need to assign the function to the namespace object as the previous example demonstrates.

Prototypes as Class Definitions

The prototype property of a function defines class properties and methods. Syntax within the prototype differs from function syntax-outside of functions, code in the prototype uses colons instead of equal signs, functions and fields must be separated by commas, and instance variables must be referred to with the “this” scope. The following code demonstrates a simple prototype for the EditableControl JavaScript class, which takes an element in the constructor and defines the initialize method.

 Litware.EditableControl = function(element){     element.editControl = this;     this.element = element; } Litware.WikiControl.prototype = {     element : null,     editElement : null,     initialize : function(text){         this.editElement.innerHTML = text;     } }

Class Registration and Inheritance

The ASP.NET AJAX Framework extends the object type with the registerClass and inheritsFrom type system functions. These functions are used by the framework to manage type system and inheritance chains. Type registration is useful when creating JavaScript base classes and derived classes. The following method registers the Litware.EditableControl JavaScript class with Microsoft’s JavaScript class framework so as to enable inheritance.

 Litware.EditableControl.registerClass('Litware.EditableControl);

Likewise, the inheritsFrom function is used to manage inheritance of registered types. For example, the following WikiControl class inherits from EditableControl and therefore has all of the functionality of the EditableControl class, including the initialize method and any other methods we define in the EditableControl prototype.

 Litware.WikiControl = function(element){     Litware.WikiControl.initializeBase(this, [element]); } Litware.WikiControl.registerClass(     'Litware.WikiControl', Litware.EditableControl); Litware.WikiControl.inheritsFrom(Litware.EditableControl);

Global Namespace Functions

The ASP.NET AJAX Framework extends the global object and other primitives including String, Array, Boolean, Number, and Object. Additionally, the framework provides shortcuts to common functions, such as getElementById. The $get method can be used to obtain a reference to a named element. You might also want to use the $addHandler method to attach event handlers to objects. There are too many APIs to list here, but full reference to the client library is available as a local Microsoft Visual Studio Web site that you can download from http://ajax.asp.net or access online at http://ajax.asp.net/docs/ClientReference.

Creating a JavaScript Component with ASP.NET AJAX

To create your first application with ASP.NET AJAX, create a new ASP.NET AJAX–Enabled Web Site using Microsoft Visual Studio 2005. The ASP.NET AJAX-Enabled Web Site is a new Visual Studio project template installed with ASP.NET AJAX. In the following examples, we will create a simple wiki component. A wiki is a collaboration tool in which wiki users can freely edit text-based content and add links to new content. Our example wiki won’t be as functional as the wiki library in WSS, but it will be a good example of a lightweight AJAX architecture. The following samples are part of the LitwareAjaxWebSite example code, available in the code downloads as a Visual Studio Web Site.

The base component for the wiki is an EditableControl component based on an HTML div element, providing rich text-editing capability. Remember that we are defining a JavaScript class for use in a deployable component, such as a Web page or even a Web Part control, so we should therefore strive to make the EditableControl as flexible as possible so that we can reuse it within different contexts. Listing 5-1 demonstrates a simple EditableControl class for use in an AJAX component. The EditableControl constructor takes an element as the parameter and defines an initialize method that creates the control with the specified text. Within the makeEditable method of the EditableControl, we will manipulate the DOM at runtime to make the content of the EditableControl editable. Note the use of $addHandler and $clearHandlers to manage JavaScript event handlers. You will also notice that EditableControl isn’t responsible for data persistence, which will be implemented in the control that will inherit from this class. We will also include some very simple wiki syntax parsing to the EditableControl, which we will utilize within child classes. You can see that objects and inheritance in script can help you create reusable JavaScript components just as you’re used to in C# code!

Listing 5-1: EditableControl.js-a simple component for an editable AJAX control

image from book
  EditableControl JavaScript Class // Defines the EditableControl Component // A control-based component for a simple editable control if (typeof(Litware) == 'undefined')   Type.registerNamespace('Litware'); Litware.EditableControl = function(element){   element.editControl = this;   this.element = element; } Litware.EditableControl.prototype = {   element : null,   editElement : null,   saveButton : null,   toolbar : null,   // initializes the control   initialize : function(text){     this.toolbar = document.createElement('DIV');     this.toolbar.style.border = '1px solid silver';     this.element.appendChild(this.toolbar);     this.toolbar.style.visibility='hidden';     this.saveButton = document.createElement('IMG');     this.saveButton.src = '/_layouts/images/save.gif';     this.saveButton.style.top='3px';     this.saveButton.style.left='3px';     this.saveButton.style.zIndex=100;     this.toolbar.appendChild(this.saveButton);     this.editElement = document.createElement('DIV');     this.editElement.style.border = '1px solid';     this.editElement.style.padding='3px';     this.editElement.innerHTML = text;     this.element.appendChild(this.editElement);     $addHandler(this.editElement,'dblclick',Litware.EditableControl.MakeEditable);     },     // makes it editable     makeEditable : function(){       if (this.editElement.contentEditable == 'true'){         this.saveContent();         $addHandler(this.editElement,'dblclick',         Litware.EditableControl.MakeEditable);         $clearHandlers(this.saveButton);         this.toolbar.style.visibility='hidden';       }else{         document.designMode = "On";         this.editElement.contentEditable = 'true';         this.editElement.style.backgroundColor='yellow';         $clearHandlers(this.editElement);         this.toolbar.style.visibility='visible';         $addHandler(this.saveButton,'click',         Litware.EditableControl.MakeEditable);       }     },   // Saves the content   saveContent : function() {     document.designMode = "Off";     this.editElement.contentEditable = 'false';     this.editElement.style.backgroundColor='';   },   // converts the HTML format to WIKI text   convertToWiki : function(element){     var links = element.getElementsByTagName('span');     var placeholders = new Array();     for(var i=0;i<links.length;i++){       if (links[i].className=='WIKILINK')         Array.add(placeholders, links[i]);     }     for(var i=0;i<placeholders.length;i++){       var wik = document.createTextNode(         String.format('[[{0}]]', this.getText(placeholders[i])) );       var old = placeholders[i].parentNode.replaceChild(         wik, placeholders[i]);       placeholders[i] = null;     }   },   // Converts WIKI text to HTML format   wikiToHtml : function(wikiText){     var rex = new RegExp('\\[\\[([^\\]]+)\\]\\]',"mg");     var match = rex.exec(wikiText);     while (match){       var linked = String.format(       "<span class=\"WIKILINK\">{0}</span>",match[1]);       wikiText = wikiText.replace(match[0],linked);       match = rex.exec(wikiText);     }     return wikiText;   },   // Gets the text content of a DOM node   getText : function(node){     if (node == null)       return '';     if (node.innerText)       return node.innerText;     else if (node.textContent)       return node.textContent;     else return '';   } } Litware.EditableControl.registerClass('Litware.EditableControl'); // anonymous method that calls into the Litware.editControl Litware.EditableControl.MakeEditable = function(evt){   var editControl = findParentEditElement(evt.target);   if (editControl != null)     editControl.makeEditable();   else     alert('Could not enable wiki!'); } // Finds the parent editable element function findParentEditElement(element){   while (element.editControl == null || element == null){     if (element.parentNode)       element = element.parentNode;     else if (element.parentElement)       element = element.parentElement;     else {       var err = Error.invalidOperation();       throw err;     }   }   if (element != null)     return element.editControl; } 
image from book

You see that the initialize function attaches the Litware.EditableControl.MakeEditable handler to the element’s double-click event by using the $addHandler function, part of the Microsoft JavaScript library that attaches an event handler to an object. MakeEditable is called with an event, so you are not able to pass a direct object reference. However, you can find the parent EditableControl by looking up the control tree, as in the findParentEditElement function. Because you cannot attach an instance method of the EditableControl in the handler, you must look at the event target’s parentElement (parentNode in Mozilla) to check for the editControl expando property (remembering that we set the element’s editControl property to the instance of the EditableControl). An expando property is an arbitrary property set by script on an object. Because JavaScript objects are not constrained to predefined interfaces as they are with .NET objects, you can set expando properties on any object.

With the EditableControl component defined in the EditableControl.js file, we are now ready to add it to a Web page to test it out. Using the AJAX-enabled Web site project, all we need to do is add a ScriptReference to the ScriptManager control, add a placeholder control, and create a page load handler. The ScriptManager is used to register script references for the Microsoft AJAX Library and custom scripts (including scripts compiled as Web resources), and to manage JavaScript proxies to any registered Web services. To test the script we include the ScriptReference as a path. When working with the ScriptManager in code, you want to register a compiled script resource with the assembly and resource name. For now, the ScriptManager control is declared as follows:

 <asp:ScriptManager  runat="server">   <Scripts>     <asp:ScriptReference Path="EditableControl.js" />   </Scripts> </asp:ScriptManager>

After creating the script reference, we can simply add a div placeholder and create a page load event handler. The page load event handler is similar to what we will later add to a Web Part-it is the code that creates the AJAX component based on the class we’ve defined. After defining a page load method named Litware.PageLoad, we can add that to the AJAX application’s load event with the Sys.Application.add_load method. This is similar to handling the implementation of an ASP.NET page_load handler in C#, although this page load handler runs entirely on the client browser. The following code instantiates a new EditableControl with the MainPlaceHolder div and initializes the EditableControl with the text “Hello, Litware Employees!” This code demonstrates the client-side load event, which happens after the JavaScript application runtime is loaded. Use this event to initialize your components and ensure that all required script resources are loaded.

 <script type="text/javascript" language="javascript" > if (typeof(Litware) == 'undefined')   Type.registerNamespace('Litware'); Litware.PageLoad = function(){   var control = $get('MainPlaceHolder');   window.wiki = new Litware.WikiControl(control);   window.wiki.initialize('Hello, Litware Employees!'); } // Add the PageLoad to the AJAX Runtime load event Sys.Application.add_load(Litware.PageLoad); </script>

Now that we have a base control for notepad-like functionality, we can extend it with a persistence service to create a simple wiki (a micro-wiki). In traditional ASP.NET development, we might put code within a Page class to store control values such as the wiki text. In traditional WSS development, we might persist the wiki text within a Web Part. But in an AJAX development architecture, you define reusable services as an API framework that can be used in multiple components. In the following example, we will build a script-enabled Web service called WikiService that simply remembers the contents of the wikis. We will later tie this Web service to a simple WSS list instance, but for this prototype, we will define two methods and simply store the data in application memory.

To enable a JavaScript proxy, mark the Web service with the ScriptService attribute defined in the System.Web.Script.Services namespace. Web services that are marked with the ScriptService attribute can be referenced with Script References in the ScriptManager control. Listing 5-2 demonstrates a very simple persistence Web service for our very simple wiki.

Listing 5-2: AJAX-enabled Web services are marked with the ScriptService attribute.

image from book
  A Script-Enabled Web Service for a Simple Wiki <%@ WebService Language="C#"  %> using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using System.Web.Script.Services; namespace Litware {   [WebService(Namespace = "http://Litware.com/insideWSS/AJAX")]   [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]   [ScriptService]   public class WikiWebService: System.Web.Services.WebService {     private const string DefaultContent = "Welcome to the Simple Wiki!";     private const string keyPrefix = @"litwiki_";     [WebMethod]     public string GetContent(string wikiID) {       return ((string) Application[keyPrefix + wikiID] ?? DefaultContent);     }     [WebMethod]     public void SetContent(string wikiID, string wikiContent) {       Application[keyPrefix + wikiID] = wikiContent;     }   } } 
image from book

With our Web service endpoint in place, we can create a reference to it in the Script Manager and reference it in our component. The Script Manager creates a client-side script reference to the component’s generated JavaScript proxy, which is the path of the ASMX file with a “/js” switch appended. For example, the following Script Manager will create a reference to the script generated at the URL “WikiWebService.asmx/js”:

 <asp:ScriptManager  runat="server">   <Services>     <asp:ServiceReference Path="WikiWebService.asmx" />   </Services> </asp:ScriptManager>

If the application is run in debug mode (as defined by the web.config compilation attribute), the Script Manager’s reference will point to “WikiService.asmx/jsdebug.”

Tip 

To examine the generated JavaScript proxy for script-enabled Web services, simply save the [service].asmx/jsdebug endpoint to a text file.

Adding to the EditableControl, we can create a WikiControl JavaScript class that inherits from the EditableControl but adds a layer of persistence. With this strategy, you can develop common base components and add custom functionality per your application needs. This simple example lays the foundation for real-world component inheritance later in this chapter. To provide persistence, we override the makeEditable method and add custom functionality to the base functionality. If the editElement is not editable after the base call, we know that we should save the element. This lets the base class take care of the rendering and user interface while letting the concrete WikiControl JavaScript class handle integration with our WikiService Web service API.

JavaScript proxies are generated with the same signature as the Web service signature, with the addition of a success callback handler, a failure callback handler, and a userContext object. For example, the signature public string GetContent(string) translates to the JavaScript signature GetContent(string,onSuccess,onFailed,userContext). The userContext parameter is an arbitrary object that is returned in the asynchronous callback. You could use it to pass a JavaScript object representing state, or you could simply pass a reference to the current object by using the keyword this. The value returned from the Web service is passed to the asynchronous callback handler and is a simple type, an XMLDOM object, or a JavaScript object. (If the Web service returns void, no value is returned on the callback other than the userContext object.) If you return a nonprimitive type from the Web service, ASP.NET AJAX automatically sends a JavaScript serialized object (formatted in JSON, the JavaScript object notation) that is passed to your callback handler unless you have specified the XML format with the ScriptMethod attribute.

With these concepts in mind, look at Listing 5-3, which demonstrates the full WikiControl containing the Web Service calls. This component is dependent on the WikiService Web service proxy and can be instantiated against different wikis as defined by the wiki ID passed in the control parameter and Web service calls. Note that this interface is designed for the MicroWiki list instance that we will create later in WSS.

Listing 5-3: The WikiControl uses script-enabled Web services for persistence.

image from book
  WikiControl: An EditableControl with Persistence // Defines the Micro Wiki Component tied to the WikiList Litware.WikiControl = function(element,wikiID){   Litware.WikiControl.initializeBase(this, [element]);   this.wikiID = wikiID ;   element.controlType='Litware.WikiControl';   element.WikiControl = this; } Litware.WikiControl.prototype = {   wikiID : null,   makeEditable : function(){     Litware.WikiControl.callBaseMethod(this, 'makeEditable');     if (this.editElement.contentEditable == 'false'){       this.editElement.innerHTML=this.wikiToHtml(this.editElement.innerHTML);       this.addWikiHandlers(this.editElement);       // Save the wiki in the wiki service       // Required in WSS:       // Litware.WikiWebService.set_path(window.spWebUrl+       // '/_vti_bin/Litware/WikiWebService.asmx');       Litware.WikiWebService.SetContent(         this.wikiID,this.editElement.innerHTML);     }     else // Convert to wiki syntax       this.convertToWiki(this.editElement);   },   initialize : function(){     Litware.WikiControl.callBaseMethod(this, 'initialize',[ 'Loading...' ]);     this.loadContent(this.wikiID);   },   loadContent : function(wikiID){     this.wikiID = wikiID;     // Required in WSS:     // Litware.WikiWebService.set_path(window.spWebUrl+     // '/_vti_bin/Litware/WikiWebService.asmx');     Litware.WikiWebService.GetContent(wikiID,       Litware.WikiControl.OnGetContent,       Litware.WikiControl.OnGetFail, this);   },   setContent : function(html){       this.editElement.innerHTML = html;       this.addWikiHandlers(this.editElement);   },   // Adds click handlers to the WIKILINK spans   addWikiHandlers : function(element){     var links = element.getElementsByTagName('span');     for(var i=0;i<links.length;i++){       if (links[i].className=='WIKILINK')         $addHandler(links[i],'click', Litware.wikiLink);     }   } } Litware.wikiLink = function(evt){   var wiki = SharePoint.Ajax.FindParentControl(     evt.target,'Litware.WikiControl');   var link;   if (evt.target.innerText)     link = evt.target.innerText;   else if (evt.target.textContent)     link = evt.target.textContent;   if (wiki != null && link != null && link != '')     wiki.WikiControl.loadContent(link); } Litware.WikiControl.OnGetContent = function(contentResponse, WikiControl){     WikiControl.setContent(contentResponse); } Litware.WikiControl.OnGetFail = function(error){   alert(error.get_message() + '\n' + error.get_stackTrace()); } Litware.WikiControl.registerClass('Litware.WikiControl',   Litware.EditableControl); Litware.WikiControl.inheritsFrom(Litware.EditableControl); // WebPart initialization script: Litware.WikiControl.Init = function(){   if( window.WikiControlTemplates ){     for(var i=0;i<window.WikiControlTemplates.length;i++){       var o = window.WikiControlTemplates[i];       if (o != null){         var wiki = new Litware.WikiControl($get(o.Control), o.WikiID);         wiki.initialize();         o = null;       }     }     window.WikiControlTemplates = null;   } } Sys.Application.add_load(Litware.WikiControl.Init); 
image from book

With the backend Web service in place and integrated with the WikiControl, we are ready to add it to our Web page. To add the Web service proxy and all of our JavaScript controls, the ScriptManager will be declared with the following references:

 <asp:ScriptManager runat="server">   <Scripts>     <asp:ScriptReference Path="EditableControl.js" />     <asp:ScriptReference Path="WikiControl.js" />   </Scripts>   <Services>     <asp:ServiceReference Path="WikiService.asmx" />   </Services> </asp:ScriptManager>

To instantiate the WikiControl, we again add a handler to the load event with the method Sys.Application.add_load from within the Web page. To do this, we define a function and add it to the load as follows:

 <script type="text/javascript" language="javascript" > if (typeof(Litware) == 'undefined')   Type.registerNamespace('Litware'); Litware.WikiPageLoad = function(){   var control = $get('MainPlaceHolder');   window.wiki = new Litware.WikiControl(     control, '6614ed027597d4b6a9cfaa16f0bcd95e');   window.wiki.initialize(); } </script>

AJAX Architecture and WSS

The core architecture for AJAX applications consists of a library of XML APIs on the server and JavaScript components on the client that perform user interface implementation, XML data requests, and Web service calls. To apply this technique to Web Part development, we define JavaScript components that run in the context of a Web Part and program against XML data sources on the server. The server data sources utilize the WSS site context and object model as well as the WSS authorization and security framework. Some of these endpoints already exist for WSS, including RSS syndication for lists. Unfortunately, WSS 3.0 RTM Web Services are not script-enabled, so you may need to build your own API for AJAX components in the Microsoft Office 2007 timeframe. To develop AJAX components for WSS, you first define an API of service endpoints, including XML feeds and Web services. Because actual users are passing their credentials directly to the API, the API can utilize the caller’s security context for authentication and impersonation within your server-side handlers.

Tip 

Within AJAX APIs, all endpoints must be secured and validated because we are directly exposing the API to the user.

Web Service Endpoints for WSS AJAX Components

Custom Web services utilizing the WSS site context can be used to create an API for WSS AJAX components as well as external applications. Building on the previous WikiWebService endpoint, we can modify the Web service to use a WSS list for persistence. By utilizing a flexible API, we are able to abstract the data store from the component so that we can later add a more robust user interface in script without modifying the back end. The WikiWebService, which is an adopted version of the WikiWebService with a WSS List as the persistence layer, is shown in Listing 5-4. Note that the Web service utilizes some advanced security concepts (discussed further in Chapter 10, “Application Security”) and requires either full-trust or custom permissions (defined in the sample code solution manifest). This API defines one hidden wiki list per Web site, with a list of wiki items keyed by the title. By using this API, you could extend the wiki to a full-featured wiki application. Within the code samples, we include the Web services and Web Parts in a single assembly, but real application APIs should be developed independently and loosely coupled.

Tip 

To build a full-featured AJAX-wiki, you could add wiki text parsing to the EditableControl JavaScript component. (For more information on the full wiki syntax, refer to the built-in WSS wiki components.)

Listing 5-4: The WikiWebService with WSS list backing demonstrates a flexible API for WSS AJAX components.

image from book
  WikiWebService for WSS using System; using System.Collections.Generic; using System.Text; using System.Web.Services; using System.Web.Script.Services; using Microsoft.SharePoint; using System.Security.Permissions; namespace Litware {   // An example Web Service API for a Litware MicroWiki.   [WebService(Namespace = "http://Litware.com/insideWSS/AJAX")]   [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]   [ScriptService]   public class WikiWebService : System.Web.Services.WebService {     private const string wikiName = "litwareMicroWiki";     private const string DefaultContent = "&nbsp;"     [WebMethod]     public string GetContent(string wikiID) {       SPList wiki = this.GetWikiList(wikiID);       if (wiki == null)         return "Welcome to the MicroWiki!";       SPQuery query = new SPQuery();       query.ViewFields=@"<FieldRef Name='Content'/><FieldRef Name='Title'/>";       query.Query = string.Format(         @"<Where><Eq><FieldRef Name='Title'/>           <Value Type='Text'>{0}</Value></Eq></Where>", wikiID);       SPListItemCollection items = wiki.GetItems(query);       if (items.Count > 0)         return ((string)items[0]["Content"]);       else         return DefaultContent;     }     [WebMethod]     public void SetContent(string wikiID, string wikiContent) {       SPContext.Current.Web.AllowUnsafeUpdates = true;       SPList wiki = this.GetWikiList(wikiID);       SPQuery query = new SPQuery();       query.ViewFields = @"<FieldRef Name='Content'/><FieldRef Name='Title'/>";       query.Query = string.Format(         @"<Where><Eq><FieldRef Name='Title'/>           <Value Type='Text'>{0}</Value></Eq></Where>", wikiID);       SPListItemCollection items = wiki.GetItems(query);       if (items.Count > 0){         // Queried items are read only.         SPListItem item = wiki.Items[items[0].UniqueId];         item["Content"] = wikiContent;         item.Update();       }       else {         SPListItem item = wiki.Items.Add();         item["Content"] = wikiContent;         item["Title"] = wikiID;         item.Update();       }     }          // Returns the wiki list for this site.     [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]     private SPList GetWikiList(string wikiID) {       SPWeb site = SPContext.Current.Web;       SPList list = null;       foreach (SPList alist in site.Lists) {         if (alist.Title.Equals(wikiName,             StringComparison.InvariantCultureIgnoreCase)) {           list = alist;           break;         }       }       if (list != null)         return list;       else {         // See chapter 10 for more details on RunWithElevatedPrivileges         SPSecurity.RunWithElevatedPrivileges(         delegate() {           using (SPSite siteCollection=new SPSite(SPContext.Current.Site.ID)){             using (SPWeb elevatedSite =                 siteCollection.OpenWeb(SPContext.Current.Web.ID)) {               elevatedSite.AllowUnsafeUpdates = true;               Guid listID = elevatedSite.Lists.Add(wikiName,                 "A microwiki", SPListTemplateType.GenericList);               SPList wikiList = elevatedSite.Lists[listID];               wikiList.WriteSecurity = 1; // All users               wikiList.ReadSecurity = 1; // All users               wikiList.Hidden = true;               wikiList.Fields.Add("Content", SPFieldType.Note, true);               wikiList.Update();               SPListItem homeWiki = wikiList.Items.Add();               homeWiki["Content"] = "Welcome to the MicroWiki!";               homeWiki["Title"] = wikiID;               homeWiki.Update();             }           }         });       }       return null;     }   } } 
image from book

Before we build the wiki into a Web Part, we will look at the additional server-side services that are common in an AJAX architecture.

HTTP Handlers as Data Sources

HTTP handler endpoints are another preferred data source for AJAX applications in which data is returned in a simple XML, text, or JSON format representing the data query. This is a subset of the REST architecture pattern, or Representational State Transfer, in which the data served from a simple URL-based protocol is the representation of the current data’s state. In a REST architecture, each URL endpoint defines the data for an item. For example, an Outline Processor Markup Language (OPML) endpoint for WSS would represent feeds for each site.

The site-relative endpoint opml.aspx would define the site feeds available in OPML format, and you would know by this convention that you can access the feed data for each site at this endpoint. This approach is taken in the following example. Because WSS applications are virtualized, we simply define one endpoint in web.config, and our handler picks up the WSS site context.

HTTP handlers are defined by the interface System.Web.IHttpHandler and provide a mechanism for working with the HTTP request and response. By using handlers, you can create a library of XML data sources that can be consumed by both JavaScript and .NET clients. The handler is similar to a Page object but does not have the control life cycle overhead of the Page class. In addition, it exists entirely in compiled code without interpreted markup. To implement a simple handler, merely implement the IHttpHandler interface’s IsReusable readonly property and process the request within the ProcessRequest method. You generally want to set the Response’s ContentType to “text/xml” and write XML by using the XmlTextWriter. The following handler demonstrates the use of the SharePoint site context, the IHttpHandler interface, and the XmlTextWriter.

 using System; using System.Web; using Microsoft.SharePoint; using System.Xml; namespace LitwareAjaxWebParts {   public class SimpleSiteHandler : IHttpHandler {     public bool IsReusable { get { return false; } }     public void ProcessRequest(HttpContext context) {       SPWeb site = SPContext.Current.Web;       XmlWriter writer = new XmlTextWriter(context.Response.Output);       writer.WriteStartElement("site");       writer.WriteAttributeString("url", site.Url);       writer.WriteString(site.Title);       writer.WriteEndElement();       writer.Flush();     }   } }

Tip 

The IHttpAsyncHandler is the preferred interface for handlers that need to perform longer-running tasks, such as Web requests or database queries. For more information on the IHttpAsyncHandler interface, see the MSDN Library (http://msdn.microsoft.com/library).

Now that we’ve looked at a simple HTTP handler, let’s look at an example of a real-life endpoint for an XML API. In the following Web Part examples, we will build a Feed List Web Part similar to the previous non-AJAX Feed List Web Part (see Listing 4-8 in Chapter 4, “Web Parts”). However, the Feed List will be built with an AJAX component that can navigate an entire site collection’s hierarchy and send client-side connections to an AJAX List View Web Part, a specialized instance of an RSS Web Part. To build these components, we need a navigable XML data source of site feeds and an RSS data source for each WSS list instance. Fortunately, WSS already gives us RSS feeds for each list, so we need to build the data stream for the hierarchical list of feeds.

The format we used for the Feed List is OPML, an industry-standard XML format used to display outlines of RSS feeds in a hierarchical manner. The outline comprises outline nodes, in which a type can be specified in each node. We specify the type site for sites and the type rss for list feeds. Outline nodes typically include a title, htmlUrl, and/or an xmlUrl attribute that define either the “human readable” URL for an item or the XML URL for a feed or data source. Following is an example OPML document that we would generate for each site with our Feed List Handler. XML data sources, such as the Feed List Handler, are essential components in an AJAX architecture and enable rapid development of user interface components based on them. Because WSS applications run under site context, this same data source can be accessed through site-relative URLs to provide feed data from the current site context. To enable navigation, we include outline nodes for the direct children of the current site context and provide an attribute for the parent site if the site is not the root site. This scheme lets client code navigate up and down the site collection by using the URL of the site and the URL of the site-relative endpoint.

The following example XML stream is generated by the Feed List Handler and is used to build the user interface for the feed navigation:

 <opml> <head><title>Team Site: Site Feeds</title></head> <body>   <outline title="Team Site" type="site"     htmlUrl="http://litwareserver" current="true">     <outline title="Announcements" type="rss"       xmlUrl="http://litwareserver/_layouts/listfeed.aspx?List=announcements"       htmlUrl="http://litwareserver/Lists/Announcements/AllItems.aspx"       listType="Announcements" />     <outline title="Customers" type="rss"       xmlUrl="http://litwareserver/_layouts/listfeed.aspx?List=customers"       htmlUrl="http://litwareserver/Customers/AllItems.aspx"       listType="10002" />     <outline title="blog" type="site" htmlUrl="http://litwareserver/blog" />     <outline title="litware" type="site" htmlUrl="http://litwareserver/litware" />   </outline> </body> </opml>

To generate the OPML for the endpoint, we use a simple HTTP handler that just enumerates the child sites and lists of the current site. Because we are accessing the site by using the WSS site context, we have built-in security and only render lists that the user can access. We will discuss list security in more detail in Chapter 10 in our discussion of the ISecurableObject interface. For now, we can use the ISecurableObject interface to ensure that the requesting user can access the WSS list. To take this further, you can create a filter to serve only a particular type of list, such as the Posts list, to build a blog reader for a site collection. The FeedListHandler class is listed in Listing 5-5.

Listing 5-5: The Feed List Handler provides an outline of XML feeds for the site.

image from book
  The FeedListHandler XML Data Source using System; using System.Web; using Microsoft.SharePoint; using System.Xml; using System.Security.Permissions; using System.Globalization; using System.Net; namespace LitwareAjaxWebParts {   [PermissionSet(SecurityAction.Demand)]   public sealed class FeedListHandler : IHttpHandler {     public bool IsReusable { get { return false; }}     bool recursive = false;     internal const string WssRssFeedFormat =       @"{0}/_layouts/listfeed.aspx?List={{{1}}}";     private SPWeb Site {get { return SPContext.Current.Web; }}     public void ProcessRequest(HttpContext context) {         if (!this.Site.AllowRssFeeds){             context.Response.StatusCode = (int) HttpStatusCode.NoContent;             return;     }     // Set the XML headers     context.Response.ContentType = @"text/xml";     context.Response.Expires = -1;     XmlWriter xw = new XmlTextWriter(context.Response.Output);     xw.WriteStartDocument();     xw.WriteStartElement("opml");     xw.WriteStartElement("head");     xw.WriteElementString("title",     SPContext.Current.Web.Title + ": " + @"Site Feeds");     xw.WriteEndElement();     xw.WriteStartElement("body");     this.WriteSiteFeeds(SPContext.Current.Web, xw);     xw.WriteEndElement();     xw.WriteEndElement(); } // Writes the site feed list private void WriteSiteFeeds(SPWeb site, XmlWriter xw) {     if (!site.AllowRssFeeds) return;     xw.WriteStartElement(@"outline");     xw.WriteAttributeString(@"title", site.Title);     xw.WriteAttributeString(@"type", @"site");     xw.WriteAttributeString(@"htmlUrl", site.Url);     if (site == SPContext.Current.Web)         xw.WriteAttributeString(@"current", @"true");     if (site.ParentWeb != null)         xw.WriteAttributeString(@"parentWeb", site.ParentWeb.Url);     foreach (SPList list in site.Lists) {       if (!list.AllowRssFeeds) continue;       if (HttpContext.Current.User.Identity.IsAuthenticated == false){       if ((list.AnonymousPermMask64 & SPBasePermissions.ViewListItems)             == SPBasePermissions.ViewListItems)         WriteListReference(xw, list);       } else {      if (list.DoesUserHavePermissions(SPBasePermissions.ViewListItems))        WriteListReference(xw, list);      }     }     foreach (SPWeb childSite in site.Webs) {         if (HttpContext.Current.User.Identity.IsAuthenticated==false){             if ((childSite.AnonymousPermMask64 &                     SPBasePermissions.ViewListItems) ==                     SPBasePermissions.ViewListItems)                 this.WriteSite(childSite, xw);         } else if (childSite.DoesUserHavePermissions(                  SPBasePermissions.ViewListItems))              this.WriteSite(childSite, xw);         }         xw.WriteEndElement();     } private void WriteSite(SPWeb site, XmlWriter xw) {   xw.WriteStartElement(@"outline");   xw.WriteAttributeString(@"title", site.Title);   xw.WriteAttributeString(@"type", @"site");   xw.WriteAttributeString(@"htmlUrl", site.Url);   xw.WriteEndElement(); } private static void WriteListReference(XmlWriter xw, SPList list){   if (!list.AllowRssFeeds || list.Hidden)     continue;   list.CheckPermissions(SPBasePermissions.ViewListItems);   xw.WriteStartElement(@"outline");   xw.WriteAttributeString(@"title", list.Title);   string listFeed = string.Format(     CultureInfo.InvariantCulture,     WssRssFeedFormat,     list.ParentWeb.Url,     HttpContext.Current.Server.UrlEncode(list.ID.ToString()));     xw.WriteAttributeString(@"xmlUrl", listFeed);     xw.WriteAttributeString(@"type", @"rss");     xw.WriteAttributeString(@"htmlUrl", list.ParentWeb.Site.Url +       list.DefaultViewUrl);     xw.WriteAttributeString(@"listType", list.BaseTemplate.ToString());     xw.WriteEndElement(); // outline   }  } } 
image from book

The Feed List Handler is registered in the web.config file through the following element in the httpHandlers node. In the code samples provided, this is registered through a Feature installer when deployed in the solution package or through the Visual Studio “Deploy” build.

 <add verb="*" path="opml.ashx"     type="LitwareAjaxWebParts.FeedListHandler, LitwareAjaxWebParts"/>

The HTTP handler defined in web.config is virtualized, meaning that this endpoint is exposed in each WSS site. When building your API, you want to program both virtualized endpoints that can expose data from within the site context and common nonvirtualized endpoints that provide application functionality outside of the site context. Oftentimes, you might want to implement custom endpoints that aggregate, filter, or manipulate external data, providing endpoints to external systems that also interact with the WSS site context.




Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
EAN: N/A
Year: 2007
Pages: 92

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net