Sample Application: Creating a Site Chat

In the following code samples, we’ll use the AjaxXmlWebPart and the XmlControl to develop a chat application that runs on a SharePoint site. The first step in the process is to create a chat protocol that runs as a REST-ful service through a simple XML endpoint.

Creating the Chat Protocol

Because WCF is not fully supported on the current SharePoint platform, we’ll follow an approach that uses HTTP handlers—however, we’ll make use of the WCF platform so that we can maintain a consistent API. By implementing the service using WCF technology, we can serve the same code through a WCF endpoint or through an HTTP handler wrapper class. We will also register the HTTP handler endpoint with the path chat.svc so that client code is completely neutral about whether the service is implemented in WCF or not. And whether the Web service is served through WCF or ASP.NET is trivial—we can easily convert the service to WCF if we want, which is demonstrated later in the chapter in Example 11-17.

The chat data itself will be implemented in a simple SharePoint list that is served through our Web service. However, we want to keep our protocol neutral with respect to the hosting platform, and we don’t want to tie our chat implementation to the SharePoint platform. To create a clean API, we can create our own XML schema that simplifies the interface and implements a data schema that isn’t tied to SharePoint. By creating a data contract, we have standard XML serialization in the Web service regardless of the service implementation. We can use the DataContractSerializer class in the HTTP handler instance, which will output the same XML as the WCF endpoint. The classes ChatData and Author will be used in the Chat data contract that wraps a list of chat entries in a common format. These data contracts are defined in Example 11-7, which will output XML in the following format:

<chat xmlns="http://soajax" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <lastModified>datetime last modified</lastModified>
    <chats>
        <chatData>
            <message>message text</message>
            <pubDate>datetime published</pubDate>
            <author>
                <name>author display name</name>
                <email>author email</email>
                <id>chat ID</id>
                <url>profile page URL</url>
                <picture>picture URL</picture>
            </author>
        </chatData>
     </chats>
</chat>

Example 11-7. The Chat data contract defines the XML schema for the custom chat protocol (SOAjax. SharePoint.Services/ChatData.cs).

using System;
using System.Runtime.Serialization;

namespace SOAjax.SharePoint.Services
{
    [DataContract(Name = "chat", Namespace = "http://soajax")]
    public class Chat
    {
        [DataMember(Name = "lastModified", Order = 1)]
        public DateTime LastModified { get; set; }
        [DataMember(Name = "chats", Order = 2)]
        public ChatData[] Chats { get; set; }
    }

    [DataContract(Name = "chatData", Namespace = "http://soajax")]
    public class ChatData
    {
        [DataMember(Name = "message", Order = 1)]
        public string Message { get; set; }

        [DataMember(Name = "pubDate", Order = 2)]
        public DateTime PostedDate { get; set; }

        [DataMember(Name = "author", Order = 3)]
        public Author Author { get; set; }
    }

    [DataContract(Name = "author", Namespace = "http://soajax")]
    public class Author
    {
        [DataMember(Name = "name", Order = 1)]
        public string Name { get; set; }

        [DataMember(Name = "email", Order = 2)]
        public string Email { get; set; }

        [DataMember(Name = "id", Order = 3)]
        public int UserID { get; set; }

        [DataMember(Name = "url", Order = 4)]
        public string Url { get; set; }

        [DataMember(Name = "picture", Order = 5)]
        public string Picture { get; set; }
    }
}

To create a service that implements the Chat data contract, create a WCF service class that implements a service contract and returns the Chat object. If the service class is exposed through a WCF endpoint, it requires ASP.NET compatibility mode to run in the SharePoint context. In this example, we’ll create a WCF service and wrap it in an HTTP handler, which gives us the consistency of the WCF platform and the stability of SharePoint’s supported platform. You can also deploy the service as a WCF svc endpoint if you want to without changing any code or breaking any external contracts. Creating the WCF service, even for an HTTP handler implementation, helps you create a more logical API by clearly defining the contracts, rather than just stream operations as you define with HTTP handlers.

The next step in creating the service is to define the service contract. In this example, the only methods supported are View and Post, where View returns only the current conversation and Post accepts a simple message from the calling identity. The Post operation accepts a single string as the message parameter and creates the chat entry by using the identity of the caller. Example 11-8 shows the service contract for the chat service, including support for WCF web binding with the GET and POST HTTP verbs.

Example 11-8. The Chat data contract defines the XML schema for the custom Chat protocol (SOAjax. SharePoint.Services/IChatService.cs).

using System;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace SOAjax.SharePoint.Services
{
    [ServiceContract]
    public interface IChatService
    {
        [OperationContract]
        [WebGet(UriTemplate="")]
        Chat View();

        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "")]
        void Post(string chat);
    }
}

To implement the service, we need to include some simple access code for SharePoint lists. The chat service could interface with external systems and proxy the chat across the network or store the results in a database, but in this example we’ll use the SharePoint list-storage mechanism. When programming the service, we don’t want to tie the service to the SharePoint implementation. We want to create a generic protocol that is platform neutral and use SharePoint as a data-storage mechanism. This is a key principle of service orientation; the protocol is defined independently of the implementation.

The GetChatList method in Example 11-9 includes a mechanism to get an instance of the Chat list. This sample code will create an instance of the chat list if the list does not exist.

Example 11-9. The GetChatList method wraps the SharePoint list API and retrieves the SPList reference (excerpt from SOAjax.SharePoint.Services/ChatService.cs).

private SPList GetChatList()
{
    SPList list;
    try{
        list = SPContext.Current.Web.Lists["chat"];
    }
    catch{
        // Only a user with rights to create the list will be able to run this code.
        SPContext.Current.Web.AllowUnsafeUpdates = true;
        Guid listGuid =
            SPContext.Current.Web.Lists.Add("chat", "A persisted chat",
            SPListTemplateType.GenericList);
        list = SPContext.Current.Web.Lists[listGuid];
        list.ReadSecurity = 1; // All users can read all items.
        list.WriteSecurity = 4; // Users cannot modify any list item.
        list.Update();
        SPContext.Current.Web.Update();
    }
    return list;
}

To get a list of recent chat list items with which we can create the ChatData item, we use the SharePoint API to perform a query on the list. The helper method GetRecentChats, shown in Example 11-10, encapsulates the SharePoint API call to get a list of the last ten chat entries, ordered in reverse chronological order.

Example 11-10. The GetRecentChats method returns list items from the SharePoint list API (excerpt from SOAjax.SharePoint.Services/ChatService.cs).

protected SPListItemCollection GetRecentChats(SPList list)
{
    var chats = new List<ChatData>();
    SPQuery query = new SPQuery();
    query.RowLimit = 10;
    query.ViewFields =
        @"<FieldRef Name='Title'/><FieldRef Name='Author'/>
          <FieldRef Name='Created'/>";
    query.Query = @"<OrderBy>
                       <FieldRef Name='Created' Ascending='FALSE'/>
                  </OrderBy>";
    return list.GetItems(query);
}

With the method to retrieve recent chat data in place, the next step is to create a helper method for converting the list item to a ChatData item. The helper method is part of the data contract we return to the client. The method in Example 11-11 demonstrates the conversion of the generic SharePoint SPListItem object into a ChatData object. While the SharePoint list item is powerful and able to implement many capabilities, it isn’t the ideal data format for our external chat protocol.

Tip

Tip

While I won’t attempt to explain the SharePoint list API in this book, we cover it in depth in Inside Microsoft Windows SharePoint Services 3.0.

Example 11-11. GetChatItem converts the SPListItem to a ChatData object (excerpt from SOAjax.SharePoint. Services/ChatService.cs).

private ChatData GetChatItem(SPListItem listItem)
{
    // Common fields, exist in EVERY list:
    var createdByFieldID =
        new Guid("1df5e554-ec7e-46a6-901d-d85a3881cb18");
    var createdFieldID =
        new Guid("8c06beca-0777-48f7-91c7-6da68bc07b69");

    var created = (DateTime)listItem[createdFieldID];
    var link = new Uri(
        string.Format("{0}/{1}",
        SPContext.Current.Web.Url, listItem.ParentList.DefaultViewUrl));
    var authorField = listItem.ParentList.Fields[createdByFieldID];
    var userValue =
        (SPFieldUserValue)authorField.GetFieldValue(
        (string)listItem[createdByFieldID]);
    var author = userValue.User;
    var users = SPContext.Current.Site.GetCatalog(
        SPListTemplateType.UserInformation);
    var userItem = users.GetItemById(author.ID);
    var pict = (string)userItem["Picture"];
    if (!string.IsNullOrEmpty(pict))
        pict = pict.Split(',')[0];

    return new ChatData
    {
        Author = new SharePointAuthor
        {
            Name = author.Name,
            Email = author.Email,
            UserID = author.ID,
            Picture = pict,
            Url = string.Format("{0}/_layouts/userdisp.aspx?ID={1}",
                SPContext.Current.Site.Url, author.ID)
        },
        Message = listItem.Title,
        PostedDate = (DateTime)listItem[createdFieldID]
    };
}

To post an item, the logic to create a list item is very simple. We create a list item with the call to list.Items.Add() and then simply assign the Title field with the value of the chat. SharePoint picks up the current authentication principal as the author of the item, so we don’t need to include any identity code. We can also assume that the list inherits permissions from the site context, so there’s no real security code we need to implement. Example 11-12 demonstrates the simplicity of the SharePoint list API for creating a new entry within the Post method.

Example 11-12. The SharePoint list API is simple to use in creating a new item (excerpt from SOAjax. SharePoint.Services/ChatService.cs).

public void Post(string chat)
{
    if (string.IsNullOrEmpty(chat)) return;
    SPContext.Current.Site.CatchAccessDeniedException = false;
    SPContext.Current.Web.AllowUnsafeUpdates = true;
    var list = this.GetChatList();
    var newItem = list.Items.Add();
    newItem["Title"] = chat;
    newItem.Update();
}

Because we will call the chat service frequently to poll for new data, we want to implement a conditional response. As I described in Chapter 3, the HTTP protocol supports conditional response with the Last-Modified and If-Modified-Since HTTP headers, as well as the ETag and If-None-Match headers. In this example, the Last-Modified HTTP header is used to implement a conditional response. If the chat list hasn’t been modified since the last response, the server returns an empty response with the NOT MODIFIED (304) HTTP status. To implement the conditional response, the View method checks for the Last-Modified string from the HTTP context. For code that works in WCF or in an HTTP handler, you can implement a conditional check for WebOperationContext.Current, which will be null outside the WCF runtime. The following code will get the If-Modified-Since string from either WCF or ASP.NET:

var lastModClientString =
    (WebOperationContext.Current != null) ?
    WebOperationContext.Current.IncomingRequest.Headers
    [HttpRequestHeader.IfModifiedSince]
    : HttpContext.Current.Request.Headers["If-Modified-Since"];

To implement a conditional response that works both in WCF and in an HTTP handler, you can use the following code within the View method. This code checks the HTTP header If-Modified-Since using the previous conditional logic and will set the status to 304 only in the case of the WCF runtime. If the current WCF WebOperationContext is null, we let the calling code (the HTTP handler) process the 304 response.

var list = this.GetChatList();
var lastModClientString =
    (WebOperationContext.Current != null) ?
    WebOperationContext.Current.IncomingRequest.Headers
    [HttpRequestHeader.IfModifiedSince]
    : HttpContext.Current.Request.Headers["If-Modified-Since"];
if (!string.IsNullOrEmpty(lastModClientString))
{
    var lastMod = DateTime.MinValue;
    var tempDateTime = DateTime.MinValue;
    if (DateTime.TryParse(lastModClientString,
            CultureInfo.InvariantCulture,
            DateTimeStyles.AssumeUniversal, out tempDateTime))
        lastMod = tempDateTime.ToUniversalTime();
    if (lastMod >= list.LastItemModifiedDate)
    {
        if (WebOperationContext.Current != null)
            WebOperationContext.Current.OutgoingResponse.StatusCode =
                System.Net.HttpStatusCode.NotModified;
        return null;
    }
}

The complete code for the ChatService class is included in Example 11-13. This code is compatible with the WCF service host. It is designed to run through the WebServiceHostFactory and can also be wrapped in an HTTP handler, as you’ll see in the next example.

Example 11-13. The Chat data contract defines the XML schema for the custom Chat protocol (SOAjax.SharePoint.Services/ChatService.cs).

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.ServiceModel.Activation;
using System.Diagnostics;
using System.Web;
using System.ServiceModel.Web;
using System.Net;
using System.Globalization;

namespace SOAjax.SharePoint.Services
{
    [AspNetCompatibilityRequirements(RequirementsMode =
        AspNetCompatibilityRequirementsMode.Required)]
    public class ChatService : IChatService
    {
        // Common fields, exist in EVERY list:
        static readonly Guid createdByFieldID =
            new Guid("1df5e554-ec7e-46a6-901d-d85a3881cb18");
        static readonly Guid createdFieldID =
            new Guid("8c06beca-0777-48f7-91c7-6da68bc07b69");
 
       private SPList GetChatList()
        {
            SPContext.Current.Site.CatchAccessDeniedException = false;
            SPList list;
            try
            {
                list = SPContext.Current.Web.Lists["chat"];
            }
            catch
            {
                SPContext.Current.Web.AllowUnsafeUpdates = true;
                Guid listGuid =
                    SPContext.Current.Web.Lists.Add("chat", "A persisted chat",
                    SPListTemplateType.GenericList);
                list = SPContext.Current.Web.Lists[listGuid];
                list.ReadSecurity = 1; // All users can read all items.
                list.WriteSecurity = 4; // Users cannot modify any list item.
                list.Update();
                SPContext.Current.Web.Update();
            }
            return list;
        }

        public void Post(string chat)
        {
            SPContext.Current.Site.CatchAccessDeniedException = false;
            SPContext.Current.Web.AllowUnsafeUpdates = true;
            var list = this.GetChatList();
            var newItem = list.Items.Add();
            newItem["Title"] = chat;
            newItem.Update();
        }

        private SPListItemCollection GetRecentChats(SPList list)
        {
            var chats = new List<ChatData>();
            SPQuery query = new SPQuery();
            query.RowLimit = 25;
            query.ViewFields = @"<FieldRef Name=""Title""/><FieldRef Name=""Author""/
><FieldRef Name=""Created""/>";
            query.Query = @"<OrderBy><FieldRef Name=""Created"" Ascending='FALSE' /></
OrderBy>";
            return list.GetItems(query);
        }

        public Chat View()
        {
            var list = this.GetChatList();
            var lastModClientString =
                (WebOperationContext.Current != null) ?
                WebOperationContext.Current.IncomingRequest.Headers
                [HttpRequestHeader.IfModifiedSince]
                : HttpContext.Current.Request.Headers["If-Modified-Since"];

            if (!string.IsNullOrEmpty(lastModClientString))
            {
                var lastMod = DateTime.MinValue;
                var tempDateTime = DateTime.MinValue;
                if (DateTime.TryParse(lastModClientString,
                        CultureInfo.InvariantCulture,
                        DateTimeStyles.AssumeUniversal, out tempDateTime))
                    lastMod = tempDateTime.ToUniversalTime();

                if (lastMod >= list.LastItemModifiedDate)
                {
                    if (WebOperationContext.Current != null)
                        WebOperationContext.Current.OutgoingResponse.StatusCode =
                            System.Net.HttpStatusCode.NotModified;
                    return null;
                }
            }

            var chats = new List<ChatData>();
            var listItems = this.GetRecentChats(list);
            foreach (SPListItem listItem in listItems)
            {
                var created = (DateTime)listItem[createdFieldID];

                Uri link = new Uri(
                    string.Format("{0}/{1}",
                    SPContext.Current.Web.Url, list.DefaultViewUrl));
                SPField authorField = list.Fields[createdByFieldID];
                SPFieldUserValue userValue =
                    (SPFieldUserValue)authorField.GetFieldValue(
                    (string)listItem[createdByFieldID]);
                SPUser author = userValue.User;
                var users = SPContext.Current.Site.GetCatalog(
                    SPListTemplateType.UserInformation);
                var userItem = users.GetItemById(author.ID);
                var pict = (string) userItem["Picture"];
                if (!string.IsNullOrEmpty(pict))
                    pict = pict.Split(',')[0];

                var item = new ChatData
                {
                    Author = new SharePointAuthor{
                        Name = author.Name,
                        Email = author.Email,
                        UserID = author.ID,
                        Picture = pict,
                        Url = string.Format("{0}/_layouts/userdisp.aspx?ID={1}",
                            SPContext.Current.Site.Url, author.ID)
                        },
                    Message = listItem.Title,
                    PostedDate = (DateTime)listItem[createdFieldID]
                };
                chats.Add(item);
            }
            // Process this only for WCF code:
            if (WebOperationContext.Current != null)
            {
                WebOperationContext.Current.OutgoingResponse.Headers
                [HttpResponseHeader.CacheControl] = "Private";

                // Expire the content to force a request
                WebOperationContext.Current.OutgoingResponse.Headers
                    [HttpResponseHeader.Expires] =
                    list.LastItemModifiedDate.ToString("r");
                    DateTime.UtcNow.AddYears(-1).ToString("r");

                WebOperationContext.Current.OutgoingResponse.LastModified =
                    list.LastItemModifiedDate.ToLocalTime();
            }
            return new Chat {
                LastModified=list.LastItemModifiedDate, Chats = chats.ToArray() };
        }
    }
}

Next, we’ll create a lightweight HTTP handler that wraps the WCF service method. The ChatHandler class defined in Example 11-14 acts as a simple HTTP handler that delegates the method to the ChatService class for its implementation. Note that the ChatService class does not execute in the WCF runtime it executes entirely in the ASP.NET runtime.

Example 11-14. The ChatHandler HTTP handler can be used to call the service class as an HTTP handler (SOAjax.Services/ChatHandler.cs).

using System;
using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Web;

namespace SOAjax.SharePoint.Services
{
    public class ChatHandler : IHttpHandler
    {
        public bool IsReusable{ get { return false; }}

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.HttpMethod.Equals("GET"))
            {
                context.Response.ContentType = "text/xml";
                ChatService chat = new ChatService();
                Chat chatData = chat.View();
                if (chatData == null)
                {
                    HttpContext.Current.Response.ClearContent();
                    HttpContext.Current.Response.StatusCode =
                        (int)HttpStatusCode.NotModified;
                }
                else
                {
                    var wcfSerializer = new DataContractSerializer(typeof(Chat));
                    wcfSerializer.WriteObject(context.Response.OutputStream,
                        chatData);
                    context.Response.StatusCode = (int)HttpStatusCode.OK;

                    HttpContext.Current.Response.Cache.SetCacheability(
                        HttpCacheability.Private);

                    HttpContext.Current.Response.Cache.SetExpires(
                        chatData.LastModified.ToLocalTime());

                    HttpContext.Current.Response.Cache.SetLastModified(
                        chatData.LastModified.ToLocalTime());
                }
            }
            else if (context.Request.HttpMethod.Equals("POST"))
            {
                var message =
                    new StreamReader(context.Request.InputStream).ReadToEnd();
                if (message.Length > 0 && message.Length < 1024)
                {
                    ChatService chat = new ChatService();
                    chat.Post(message);
                    context.Response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    context.Response.StatusDescription = "Bad request";
                    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                }
            }
            else
            {
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                context.Response.StatusDescription = "Method not supported";
                context.Response.Write("Method not supported");
            }
        }
    }
}

To serve the chat service from an HTTP handler in the virtualized _Layouts directory, we need to include the web.config file shown in Example 11-15. This web.config file is deployed to the file location Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12 TEMPLATELAYOUTSSOAjax.Servicesweb.config.

By registering this handler in the layouts directory, you identify the URL of the chat service to be http://[server]/[site]/_layouts/soajax.services/chat.svc.

Example 11-15. The Chat Web service is implemented through the HTTP handler registered with ASP.NET through the web.config file (SOAjax.SharePoint.Services/SOAjax.Services/web.config).

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration>

    <system.web>
        <httpHandlers>
            <add verb="*" path="chat.svc"
                 type="SOAjax.SharePoint.Services.ChatHandler" validate="false"/>
        </httpHandlers>
    </system.web>

</configuration>

As an alternative, you could remove the HTTP handler from the web.config and include an SVC file that utilizes WebServiceHostFactory, as demonstrated in Example 11-16. However, this requires modifications to the SharePoint runtime as described in the document "WCF Support in SharePoint," available with the book’s companion content.

Example 11-16. The Chat Web service can easily be deployed using a WCF endpoint through WebServiceHostFactory (SOAjax.SharePoint.Services/SOAjax.Services/chat.svc).

<%@ Assembly Name="SOAjax.SharePoint.Services, Version=1.0.0.0, Culture=neutral,
 PublicKeyToken=5716b00ea413c97d"%>
<%@ServiceHost Service="SOAjax.SharePoint.Services.ChatService"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

With the service in place, we can access the chat service from any site-relative URL, such as http://sharepoint/development/_layouts/SOAjax.Services/chat.svc, which would return the chat data stream that is specific to the http://sharepoint/development site context.

By using .svc as the file extension for the HTTP handler, you can later remove the HTTP handler registration from web.config and include an SVC file that implements WebServiceHostFactory after adding WCF support to the SharePoint Web application. The SVC file that implements the service is shown in Example 11-17.

Example 11-17. An SVC file can host the WCF endpoint natively if WCF is supported on the SharePoint Server.

<%@ Assembly Name="SOAjax.SharePoint.Services, Version=1.0.0.0, Culture=neutral,
    PublicKeyToken=5716b00ea413c97d"%>
<%@ServiceHost Service="SOAjax.SharePoint.Services.ChatService"
    Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

To implement a read-only view of the chat, we can use the AJAX XML Web Part defined earlier, using an XSLT file and by hard-coding the chat service URL. This is generally a great first step in developing more complex XML-based AJAX components. In the following section we’ll create a specific implementation of the AjaxXmlWebPart that will add bidirectional chat functionality.

As discussed in Chapter 9, when you’re creating XML-based AJAX components, you typically create an XSLT file for each view of the data that you want to define. For example, you might want to create compact and full views of the chat control. In this example, we’ll create a single view for the Chat data contract. The chat XSLT file, shown in Example 11-18, can be used by the AJAX XML Web Part and demonstrates the simplicity and flexibility of the XML-based AJAX architecture.

Example 11-18. The chat view is implemented in XSLT (SOAjax.SharePoint/SOAjax.Script/ChatView.xslt).

<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xml:space="default"
    xmlns:sa="http://soajax" version="1.0">

    <xsl:strip-space elements="true"/>
    <xsl:output omit-xml-declaration="yes" method="html" />

    <xsl:template match='/sa:chat'>
        <xsl:apply-templates select='sa:chats' />
    </xsl:template>

    <xsl:template match='sa:chats'>
        <xsl:apply-templates select='sa:chatData' />
    </xsl:template>

    <xsl:template match='sa:chatData'>
        <div class="" style="border-bottom:1px solid #87ceeb;margin:7px;">

            <table>
                <tr valign="top">
                    <td width="48">
                        <xsl:choose>
                            <xsl:when test="sa:author/sa:picture">
                                <a href="{sa:author/sa:url}">
                                    <img src="{sa:author/sa:picture}"
                                        border="0" width="48" height="48" />
                                </a>
                            </xsl:when>
                            <xsl:otherwise></xsl:otherwise>
                        </xsl:choose>
                    </td>
                    <td>
                        <span style="margin-bottom:5px; color:gray;
                                font-weight:bold;">
                            <xsl:value-of select='sa:author/sa:name'/>
                        </span>
                        <xsl:text
                            disable-output-escaping="yes">&amp;nbsp;</xsl:text>
                        <xsl:value-of select='sa:message' />
                    </td>
                </tr>
            </table>
        </div>
    </xsl:template>
</xsl:stylesheet>

To create a control that can post data to the chat service, we’ll create a second AJAX control that wraps an input control and a post button and includes a reference to the XmlControl class. To instantiate the control, you can use the same initialization logic as shown with the XmlControl script, looking for the window._ChatControlTemplates array.

To post the data, we’ll use the following instance method of the Ajax control, which is added as an event handler. In the onChatInput method, we create an HTTP POST to the site-relative URL of the chat service. On the return call of the post, we tell the XmlControl instance to reload, which will update the user interface.

onChatInput: function(sender, eventArgs) {
    var input = this.get_chatInput();
    var chat = input.value;
    input.value = '';
    var post = new Sys.Net.WebRequest();
    post.set_httpVerb('post'),
    post.set_url(window._spweb + '/_layouts/soajax.services/chat.svc'),
    var json = Sys.Serialization.JavaScriptSerializer.serialize(chat);
    post.set_body(json);
    post.get_headers()["Content-Type"] = "application/json";
    post.add_completed(Function.createDelegate(this, this.onPostComplete));
    post.invoke();
},

onPostComplete: function(response, context, args) {
    var xmlControl = this.get_xmlControl();
    if (xmlControl) xmlControl.reload();
},

The complete code sample for the ChatControl AJAX control is shown in Example 11-19. This control is programmed against the chat protocol defined by our WCF chat service.

Example 11-19. The Chat data contract defines the XML schema for the custom Chat protocol (SOAjax.SharePoint/SOAjax.Script/ChatControl.js).

/// <reference name="MicrosoftAjax.js"/>
/// <reference path="XmlControl.js"/>

Type.registerNamespace('SOAjax.Controls'),

SOAjax.Controls.ChatControl = function(element) {
    /// <summary>
    /// A component that implements chat with the chat service.
    /// </summary>
    SOAjax.Controls.ChatControl.initializeBase(this, [element]);
}

SOAjax.Controls.ChatControl.prototype = {
    // ------------ Private fields ---------
    //_chatPostedDelegate: null,
    _chatInput: null,
    _chatButton: null,
    _xmlControl: null,

    // ------------- Properties -------------
    get_chatInput: function() {
        /// <value></value>
        return this._chatInput;
    },
    set_chatInput: function(value) {
        if (this._chatInput !== value) {
            this._chatInput = value;
            this.raisePropertyChanged('chatInput'),
        }
    },

    get_chatButton: function() {
        /// <value></value>
        return this._chatButton;
    },
    set_chatButton: function(value) {
        if (this._chatButton !== value) {
            this._chatButton = value;
            this.raisePropertyChanged('chatButton'),
        }
    },

    get_xmlControl: function() {
        /// <value>Gets or sets the XmlControl that implements the chat.</value>
        if (this._xmlControl.control)
            return this._xmlControl.control;
        else
            return this._xmlControl;
    },
    set_xmlControl: function(value) {
        if (typeof (value) == 'string') {
            value = $get(value);
        }
        if (value == null) { throw Error.argument('xmlControl',
            'Expected an XmlControl.') }
        this._xmlControl = value;
    },

    get_chatButton: function() {
        /// <value>Gets or sets the chat button.</value>
        return this._chatButton;
    },
    set_chatButton: function(value) {
        if (typeof (value) == 'string') {
            value = $get(value);
        }
        this._chatButton = value;
        $addHandler(this._chatButton, 'click',
            Function.createDelegate(this, this.onChatInput));
    },
    _chatInput: null,
    get_chatInput: function() {
        /// <value>Gets or sets the Input control.</value>
        return this._chatInput;
    },
    set_chatInput: function(value) {
        if (typeof (value) == 'string') {
            value = $get(value);
        }
        this._chatInput = value;
    },

    onChatInput: function(sender, eventArgs) {
        var input = this.get_chatInput();
        var chat = input.value;
        input.value = '';
        var post = new Sys.Net.WebRequest();
        post.set_httpVerb('post'),
        post.set_url(window._spweb + '/_layouts/soajax.services/chat.svc'),
        //post.set_userContext(chatPost);
        var json = Sys.Serialization.JavaScriptSerializer.serialize(chat);
        post.set_body(json);
        post.get_headers()["Content-Type"] = "application/json";
        post.add_completed(Function.createDelegate(this, this.onPostComplete));
        post.invoke();
    },

    onPostComplete: function(response, context, args) {
        String.format('Status: {0} ({1}) ',
            response.get_statusText(), response.get_statusCode())
            );
        var xmlControl = this.get_xmlControl();
        if (xmlControl) xmlControl.reload();
    },
    dispose: function() {
        ///<summary>Release resources before control is disposed.</summary>
        var element = this.get_element();
        if (element) $clearHandlers(this.get_element());
        SOAjax.Controls.ChatControl.callBaseMethod(this, 'dispose'),
    },

    initialize: function() {
        ///<summary>Initialize the component.</summary>
        var element = this.get_element();
        SOAjax.Controls.ChatControl.callBaseMethod(this, 'initialize'),
    }
}

SOAjax.Controls.ChatControl.registerClass(
    'SOAjax.Controls.ChatControl', Sys.UI.Control);

// Initializes the ChatControl templates during the page load.
SOAjax.Controls.ChatControl.OnPageInit = function() {
    if (window.__ChatControlTemplates != 'undefined' &&
            window.__ChatControlTemplates != null) {
        while (window.__ChatControlTemplates.length > 0) {
            var template = Array.dequeue(window.__ChatControlTemplates);
            try {
                var element = $get(template.elementID);
                var control = $create(SOAjax.Controls.ChatControl,
                    template.properties, template.events,
                    template.references, element);
            } catch (e) {
                Sys.Debug.trace(
                    'Could not create ChatControl instance from template.'),
                Sys.Debug.traceDump(template, 'invalid ChatControl template'),
                if (Sys.Debug.isDebug)
                 Sys.Debug.fail('Error in SOAjax.Controls.ChatControl.OnPageInit.'),
            }
        } // end while
    }
}
Sys.Application.add_init(SOAjax.Controls.ChatControl.OnPageInit);
Sys.Application.notifyScriptLoaded();

After creating the Chat Control JavaScript library, we can create a specialized instance of AjaxXmlWebPart that includes the XML and XSLT paths, adds an HTML input control with a POST button, and includes the ChatControl JavaScript library. The code for ChatWebPart is included in Example 11-20. The Web Part will be rendered as the chat control shown in Figure 11-4.

Example 11-20. The ChatPart Web Part implements a chat control based on XmlControl.

using System;
using Microsoft.SharePoint;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint.Utilities;
using System.Web.UI;

namespace SOAjax.SharePoint
{
    public class ChatPart : AjaxXmlWebPart
    {
        /// <summary>Don't return the base editor parts.</summary>
        /// <returns>null</returns>
        public override EditorPartCollection CreateEditorParts()
        {
            // DO NOT: return base.CreateEditorParts(),
            // as Xml and XSLT are predefined for this part.
            return null;
        }

        /// <summary>Override the base part by adding XML endpoints.</summary>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            this.XmlUrl = SPContext.Current.Web.Url +
                "/_layouts/SOAjax.Services/chat.svc"; ;
            this.XsltUrl = SPContext.Current.Web.Url +
                "/_layouts/SOAjax.Script/chatview.xslt";
            if (this.Title == this.GetType().Name) this.Title = "Site Chat";
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            this.ScriptManager.Scripts.Add(
                new ScriptReference("/_layouts/soajax.script/chatcontrol.js"));
            this.RefreshInterval = 3;
        }ddan

        protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
        {
            writer.Write(
              @"<div id=""ChatControl_{0}"" style=""display:block; padding:5px;"">",
              this.ClientID);
            writer.Write(
                @"<input id='ChatInput_{0}' maxlength='200' type='text' />",
                this.ClientID);
            writer.Write(@"<span id='ChatButton_{0}'
                style='background-color:gray;border:1px solid black; cursor:pointer;
                padding: 3px; color:white; font-weight:bold;'>chat</span>",
                this.ClientID);
            writer.Write(@"</div>");

            base.RenderContents(writer);
            string scriptFormat =
                @"if (window.__ChatControlTemplates == null){{
                    window.__ChatControlTemplates = new Array();
                    }}
                    var template = {{
                        elementID: 'ChatControl_{0}',
                        properties : {{
                            xmlControl : 'XmlControl_{0}',
                            chatButton : 'ChatButton_{0}',
                            chatInput : 'ChatInput_{0}'
                        }}
                    }};
                    window.__ChatControlTemplates.push(template);
                ";

            string script = string.Format(scriptFormat, this.ClientID);
            writer.Write(
                @"<script type=""text/javascript"" language=""javascript"">");
            writer.Write(script);
            writer.Write(@"</script>");
        }
    }
}
The Chat Web Part renders a custom chat interface, programmed against the custom WCF chat protocol.

Figure 11-4. The Chat Web Part renders a custom chat interface, programmed against the custom WCF chat protocol.

The Chat Web Part will appear like the image shown in Figure 11-4. Because the XmlControl is checking for new data every second in a lightweight call that returns 304 (NOT MODIFIED) in most cases, the Web application maintains a responsive user interface while enabling real-time socializing within the SharePoint site context.

While we implemented the Chat Service using SharePoint data storage, it’s common to build services that integrate external data sources that run within the SharePoint site context. You could easily swap out the data access code from the ChatService class in the previous example to access data from an external system such as a chat server running the Jabber XMPP protocol (www.jabber.org). To do this, you would use a SharePoint site context to identify the site’s identity and the caller’s identity before making a call to the external system, delegating the credentials using Kerberos or a custom delegation scheme.

With the techniques used in this example, you can build complex and powerful collaborative applications that run in the SharePoint site context and can easily be extended and consumed through external systems that use the same Web service APIs that the JavaScript AJAX application uses.

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

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