Thursday 3 November 2011

Calling into SharePoint Web Service using AJAX-Enabled Web Parts - Part 1


Since SharePoint is built on top of the ASP.NET Framework, it inherits a wealth of powerful features. One of these is the ability to incorporate AJAX technologies inside application pages and web parts. As many know, AJAX allows you to bypass the traditional, formal postback experience and replace it with a much faster, partial postback. This brings a client-server like feel to your web applications and can really spice up a SharePoint solution.
There are a number of great articles explaining how to configure SharePoint (See Mike Ammerlaan's article for the de-facto guidelines). There are also a number of articles talking about how to use the UpdatePanel within a web part (such as this one on MSDN).
The goal of this 2-part blog series is to go deeper and give you a better understanding of how to call into Web Services from your SharePoint web parts. This gives you much more control over the limited UpdatePanel control, but the power does introduce added complexity. However, the AJAX extensions and guidance in these articles should put you well on your way to building faster and better web parts.
This first article will talk about the JavaScript code and the server-side code contained within the web part itself. Part 2 will cover how to develop and deploy a custom Web Service into which the Web Part will call.
As I walk you through this process, I'll be introducing and providing the source code for an AJAX-based web part. This web part will implement some of the functionality found in the MOSS search center, specifically the Search Web Part and Search Core Results Web Part.
Let's get started!

Solution Architecture

We start off by first discussing the architecture of this solution and how it looks. The Visual Studio solution consists of two projects, one is a custom ASP.NET Web Service, and one is a Web Part project. Using JavaScript in the browser, the Web Part calls into the Web Service on the server, passing the keyword query and an XSLT stylesheet as parameters. The Web Service executes the query, converts the results into XML, and then transforms this into HTML. The HTML result is then returned from the Web Service as a string. The JavaScript code in the Web Part then takes the result and places the results into a <SPAN> control. The result is a very clean and very fast search. Here is a screen shot:
image_2_2JfvOA

Server-side Code for the Web Part

Using VSeWSS, I have created a Web Part project. The project has two references, one to Microsoft.SharePoint and one to System.Web.Extensions (part of the AJAX v1.0 Extensions). Inside the web part class, I have defined two personalizable properties. One allows you to set the number of search results (defaulted to 10) and the other allows you to tweak the XSLT. Just like with the Search Core Results Web Part, XSLT is used to transform the XML results into HTML. More on this later. Here are the two properties:
[Personalizable(PersonalizationScope.Shared),
    WebBrowsable(true),
    WebDisplayName("XSLT"),
    WebDescription("Enter in the XSLT")]
public string xslt
{
    get { return _xslt; }
    set { _xslt = value; }
} 

[Personalizable(PersonalizationScope.Shared),
    WebBrowsable(true),
    WebDisplayName("Number of results"),
    WebDescription("Enter in number of results to return")]
public string maxResults
{
    get { return _maxResults; }
    set { _maxResults = value; }
}

Here is how these two properties appear when configuring the Web Part:

image_4_K809KQ
As many of you know, you can have a one and only one ScriptManager control on your web page. In my code, I use the same approach as blogged by Todd Snyder and others in that I dynamically create it if needed. The advantage to this approach is I don't have to worry about editing Master Pages, and this can coexist nicely with other web parts that do a similar thing. Here is how I do it:
//Add a new script manager if one doesn't exist
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    _scriptManager = ScriptManager.GetCurrent(this.Page); 

    // Add a script manager
    if (_scriptManager == null)
    {
        _scriptManager = new ScriptManager();
        _scriptManager.EnableScriptLocalization = true;
        Controls.AddAt(0, _scriptManager);
    }
    // Make sure create child control is called
    EnsureChildControls();
}

The CreateChildControls method is pretty simple. The code looks like this:
//Create script reference for this web's url
ServiceReference objReference = new ServiceReference();
string wsPath = SPControl.GetContextWeb(HttpContext.Current).Url + "/_vti_bin/ajaxSearch.asmx";
objReference.Path = wsPath;
_scriptManager.Services.Add(objReference); 

//specially encode conditional operators in XSLT doc
_xslt = _xslt.Replace("&lt;", @"\lt").Replace("&gt;", @"\gt"); 

//generate our html controls
string docXslt = "<textarea id='docXslt' name='docXslt' style='visibility:hidden;position: absolute'>" + _xslt + "</textarea>";
string txtSearch = "<input type='text' id='txtSearch' name='txtSearch'/>";
string btnSearch = "<input type='button' id='btnSearch' name='btnSearch' onclick='CallSearch(\"" + wsPath + "\", " + _maxResults + ")' value='Search'/>";
string lblResults = "<span id='lblResults' name='lblResults'>";
Controls.Add(new LiteralControl(docXslt + txtSearch + "&nbsp;" + btnSearch + "<br/><br/>" + lblResults)); 
The first part is to create a ServiceReference to define the web service that the web part will call into. This requires the current path to the .asmx page as it registers this Web Service call. The great thing about the ServiceReference is that it will call into the Web Service and expose a JavaScript proxy class for you, and this really simplifies the amount of JavaScript code you need to write. You can read more about how this works from the ASP.NET team here.
Note: ServiceReference and ScriptManager are logically found in the System.Web.UI namespace, but physically part of the System.Web.Extensions.dll.
The next part is a bit of a hack to ensure the XSLT document doesn’t get decoded (e.g. convert &lt; to <) inside the browser. This will get corrected in the Web Service method I cover in Part 2. Finally, I generate the HTML that will be placed in the web part. You’ll notice that I used HTML controls instead of web server controls. I use an HTML button as I am only interested in a client-side event and do not want a postback to occur. The other controls are also HTML as it is easier to reference the control ID’s from JavaScript. The XSLT gets embedded into a <TEXTAREA> named docXslt. When we get into the JavaScript next, you’ll see how this comes together.
The important line is the btnSearch’s onclick event. Remember this is a client-side event, and the function name is CallSearch. As you will see, this method takes two parameters, wsPath which is the path to the Web Service and maxResults, the value set that comes from the personalization property.
There’s just one more method in the server side, and this is the code to deliver the JavaScript to the client. There are many ways to get JavaScript code to the browser in a web part. For cases where the file isn’t likely to change, I like to use an embedded resource. So, after marking the AjaxSearch.js file as Embedded Resource and defining it in AssemblyInfo.cs, I provide the browser the URL to the resource. This is done in the OnPreRender event as shown here:
//register client side script
protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e); 

    //register client side script that is an embedded resource
    Page.ClientScript.RegisterClientScriptResource(GetType(), "AjaxSearch.AjaxSearch.js");
}
So now that you have a pretty good idea of the server-side, let’s turn our attention to the client-side code.

JavaScript Code for the Web Part

The JavaScript code provided to the Web Part calls into the Web Service. The code then retrieves the HTML formatted results and stores these inside a SPAN object. The primary method is CallSearch that I introduced above, and it is called when the Search button is clicked. Again, CallSearch method receives two parameters, wsPath and maxResults. wsPath is the path to the Web Service. This will look something like http://WebApp/_vti_bin/AjaxSearch.asmx. maxResults is just an integer value storing the number of results that should be shown. Here is the code:
//asynchronous ajax call to lookup this client
function CallSearch(wsPath, maxResults)
{
    //Ensure correct SharePoint path is used
    wsAjaxSearch.ajaxSearch.set_path(wsPath); 

    //get our txtSearch control
    var txtSearch = document.getElementById ('txtSearch'); 

    //get our xslt doc that is stored in the textarea
    var docXslt = document.getElementById ('docXslt');
    //Call web service
    wsAjaxSearch.ajaxSearch.ExecuteKeywordSearch (txtSearch.value, docXslt.value, maxResults, SearchResponse, SearchError);
}
We have to call the set_path method to ensure the SharePoint web context can be established when calling into the Web Service. Unfortunately, the proxy class doesn’t do this for us. The actual Web Service call is done by the ExecuteKeywordSearch method. You’ll notice that the JavaScript object notation mimics .NET in that there is a namespace.class.method convention. This is automatically created and downloaded in the proxy class. This method takes 5 parameters: The first three are natural inputs to the Web Service; the next two are delegate functions that receive a successful response or an error response.
Here is the JavaScript function that is called under normal circumstances (i.e. no error). As you can see, we simply take the HTML response and store it in the lblResults SPAN element by using the innerHTML property.
//get return value from web service
function SearchResponse (result)
{
    //alert (result);
    var lblResults = document.getElementById ('lblResults');  
    lblResults.innerHTML = result;
}
Here is the JavaScript function that is called when an error occurs. Nothing fancy here as we just provide an error message to the user.
//display error from web service
function SearchError (result)
{
    alert (result.get_message());
}
That is the extent of our JavaScript in the Web Part. So, as you can see, it’s not too painful and a huge improvement over the days when we used the XmlHttpRequest object.
With that, we conclude the Part 1 of this solution. When you’re ready, keep going on to Part2  to see how to write and deploy the custom Web Service into SharePoint. I welcome your comments and feedback.

No comments:

Post a Comment