May 2009 - Posts

Later on this week, on Friday May 29th, I will be presenting two sessions at the Developer Days in The Hague (The Netherlands). I'm pretty excited to be there, not only because it's always lots of fun to speak at this event; but there's lots of cool SharePoint 2007 stuff (IMHO) packed in my presentations:

  • Getting Started with .NET 3.5 in SharePoint (more info)
    Abstract: Since the introduction of SharePoint 2007 lots has changed in the .NET world: version 3.0 and 3.5 of the .NET Framework have been released, which include technologies such as WCF, Silverlight and Linq. This session will focus on how you can use these new technologies in your SharePoint 2007 projects, the do's and the don'ts, and the resulting advantages.
  • Pimp Up Your SharePoint Site (more info)
    Abstract: SharePoint 2007 has a nice web user interface available for its users, but this out-of-the-box user interface can look quite old fashioned compared to new Web 2.0 web sites on the internet. This session will focus how you can give your existing SharePoint sites some extra punch by using Web 2.0 technologies such as AJAX, Silverlight and jQuery. And the good news is that it's not always required to implement very intrusive solutions; even small changes can make your SharePoint sites rise and shine again.
Btw, the DevDays are a great way to interact with the Dutch SharePoint community; which is very well represented: Mirjam van Olst, Wouter van Vugt, Ton Stegman, Waldek Mastykarz, and many, many more. So I hope to see you there and feel free to drop by to say hi! :-)

When working with the out-of-the-box SharePoint web services, it may have happened to you that the Web Service response contained the following exception embedded in the XML:

Exception of type 'Microsoft.SharePoint.SoapServer.SoapServerException' was thrown.
The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.
Error code: 0x8102006d

Undoubtedly this exception is quite strange in the context of a web service call; there is no Back button you can click. But in my cases the exception was just plain wrong, the issue had nothing to do with security whatsoever. It turned out that when you make a web service call to the SharePoint web services, in some cases you need to set the SOAPAction header  in the HTTP Request, in other cases it’s not necessary to do this (but it won’t do any bad if you do). When you consume web services from .NET code, you probably have Visual Studio generated proxies; they pass the correct header so you don’t need to do anything special. But if you construct your own HTTP Request to make the Web Service call, for example using Javascript and jQuery, you need to think about this. Check out following Javascript code for example, which creates a new List item by using the Lists.asmx web service (discussed in more detail in my previous post):

var soapEnv =
    "<?xml version=\"1.0\" encoding=\"utf-8\"?> \
    <soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \
        xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" \
        xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"> \
      <soap:Body> \
        <UpdateListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"> \
          <listName>Tasks</listName> \
          <updates> ... Updates XML ... </updates> \
        </UpdateListItems> \
      </soap:Body> \
    </soap:Envelope>";

$.ajax({
    url: "http://yoursite/_vti_bin/lists.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=utf-8"
});

This code will cause the Security Validation exception, because the SOAPAction Header is not correct. How do you know what value you need to set the SOAPAction Header to? Well, that’s very easy to figure out! Just navigate the the Lists.asmx web service (or UserGroup.asmx, Webs.asmx, Sites.asmx, SiteData.asmx or any Web Service you want to call of course) in your browser, and drill down to the Web Method you want to invoke.

 

As you can see, the required value of the SOAPAction value is displayed in the generated web page for the Web Method, so you can just copy and paste it in your code. When using the jQuery ajax function, you can use the beforeSend option so set additional Headers:

$.ajax({
    url: wsURL,
    beforeSend: function(xhr) {
        xhr.setRequestHeader("SOAPAction",
        "http://schemas.microsoft.com/sharepoint/soap/UpdateListItems");
    },

    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=utf-8"
});

That’s it, now the Web Service call will work without the irrelevant Security Validation exception.

If you have read some of the previous posts on this blog related to using jQuery in SharePoint 2007 sites, you probably know that it’s perfectly possible to make call the out-of-the-box SharePoint web services by making use of Javascript running in the client’s browser. This opens up a huge stream of possibilities from which I already covered some of them on my blog. A very important piece of information you need if you want to make a call to a web service is of course the URL of the web service. Figuring out this URL seems to be more trivial than it actually is. My first idea was to use the URL of the page in which the call to a web service actually happened; e.g. you’ve got a Site Page accessible in SharePoint using the URL http://mysite/mypage.aspx, so you strip the /mypage.suffix and add /_vti_bin/lists.asmx (if you’d like to call the Lists web service of course). The thing is, this will only work if your mypage.aspx file is setting in the Root Folder of your SharePoint site. For example: when you would put the Site Page in a Document Library instead, the URL of the page would be http://mysite/Shared Documents/mypage.aspx, so you’d have to strip the /Shared Documents/mypage.aspx and replace it with the Web Service suffix. This can get very complicated when you don’t know upfront in what kind of location the page will be stored (a Site Page in the Root Folder or a Document Library, an Application Page in _layouts, ...). It’s possible to write a bunch of code to figure that out, or you can make use of the following technique!

A very easy and quick way to get a reference is to make use of the alternate link SharePoint will but by default in the head section of every rendered page:

<html><head>
...
<link type="text/xml" rel="alternate" href="http://weblogs.asp.net/_vti_bin/spsdisco.aspx" />
...
</head>...</html>

The href attribute will always point to a server relative URL of the spsdisco.aspx page; which is located in the same folder as the out-of-the-box SharePoint Web Services (/_vti_bin). Even if you are in a sub site, another site collection, an Application Page, ... the link elemen will always be there, and point to the correct URL. So the following piece of Javascript code, will retrieve the prefix you can use to make calls to the correct URL’s of the SharePoint Web Services:

var spsdiscoUrl = $("head link[rel='alternate']:eq(0)").attr("href");
spWSUrlPrefix = spsdiscoUrl.substr(0, spsdiscoUrl.length - 13);


(If you haven’t already noticed: I’m using the jQuery Javascript library.) The first line will get the value of the alternate link in the head of the HTML page; the second line will strip spsdisco.aspx (13 characters). Once you’ve got this prefix, you can use it to construct the URL of the SharePoint Web Services as follows:

$.ajax({
    url: spWSUrlPrefix + "lists.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnvTasks,
    complete: processResultTasks,
    contentType: "text/xml; charset=\"utf-8\""
});

UPDATE As Jaap pointed out in the comments, SharePoint creates a Javascript variable called L_Menu_BaseUrl that contains the value needed determine the URL of the SharePoint Web Services. I haven't been able to test it myself, or to figure out who/what is responsible to declare this variable, but it looks promising! The only downside I see related to this approach would be that you rely on the OOB Javascript to be 1) loaded in the page and 2) to have executed before your code is started.

In my previous post I explained how you can make use of the Lists.asmx web service of SharePoint, to load list items by using the jQuery Javascript library. The example discussed in that post is simple and easy to understand, but very, very boring. Let’s try to do something useful with that technique: display fancy, unobtrusive notifications for open tasks, when a user visits a SharePoint site. The screenshot below shows the result, but it’s static. In real life the user would see the yellow boxes popping up, and after a couple of seconds they would disappear again (they don’t block the user interface at all).

 

To display these notifications I’ll use the excellent jGrowl extension for jQuery. So to make use of this demo, you’ll need to upload both the jquery.jgrowl_minimized.js and jquery.jgrowl.css files to SharePoint (check the download at the end of this post to get the files). The code below assumes that those two files, and the jQuery library itself, are uploaded to a document library called Shared Documents (which is created by default in a Team Site).

<script type="text/javascript" src="Shared Documents/jquery-1.3.2.min.js"></script>

<script type="text/javascript" src="Shared Documents/jquery.jgrowl_minimized.js"></script>

<link href="Shared Documents/jquery.jgrowl.css" rel="Stylesheet"></link>

<script type="text/javascript">

$(document).ready(function() {
    var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>Tasks</listName> \
                        <viewFields> \
                            <ViewFields> \
                               <FieldRef Name='Title' /> \
                               <FieldRef Name='Body' /> \
                           </ViewFields> \
                        </viewFields> \
                        <query> \
                            <Query><Where> \
                                <And> \
                                    <Eq> \
                                        <FieldRef Name='AssignedTo' /> \
                                        <Value Type='Integer'><UserID Type='Integer' /></Value> \
                                    </Eq> \
                                    <Neq> \
                                        <FieldRef Name='Status' /> \
                                        <Value Type='Choice'>Completed</Value> \
                                    </Neq> \
                                </And> \
                            </Where> \
                        </Query>\
                        </query> \
                    </GetListItems> \
                </soapenv:Body> \
            </soapenv:Envelope>";
            
        $.ajax({
            url: "_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
            complete: processResult,
            contentType: "text/xml; charset=\"utf-8\""
        });
});

function processResult(xData, status) {
    $.jGrowl.defaults.position = "bottom-right";
    $.jGrowl.defaults.life = 10000;
    
    var firstMessage = true;
    
    $(xData.responseXML).find("z\\:row").each(function() {
        if(firstMessage)
        {
            firstMessage = false;
            $.jGrowl("<div class='ms-vb'><b>You have open tasks on this site.</b><div>",
                {   
                    life: 5000
                }
            );
        }
    
        var messageHtml =
            "<div class='ms-vb'>" +
                "<a href='Lists/Tasks/DispForm.aspx?ID=" + $(this).attr("ows_ID")
                     + "&Source=" + window.location + "'>" +
                     "<img src='/_layouts/images/ITTASK.GIF' border='0' align='middle'> " +
                     $(this).attr("ows_Title") + "</a>" +
                "<br/>" + $(this).attr("ows_Body") +
            "</div>";
        $.jGrowl(messageHtml);
    });
}
</script>

Since we’d like to display those notifications when a user visits the site, we need to put a Content Editor Web Part of the home page (typically /default.aspx). In this content editor web part, copy and paste the Javascript code from above. In this code once again, first the SOAP envelope message is constructed. Notice that both the Title and Description fields are requested (the internal name of the Description field is Body). In the query element two conditions are set; the AssignedTo field should be equal to the currently logged on user, and the Status field can’t be equal to Completed. The message is POST-ed to the Lists.asmx web service by using jQuery’s ajax function.

The response of the web service call is processed in the processResult function. For every row element in the result, the jGrowl function is called to display a notification. The contents of such a notification are a small HTLM string containing a link to the task, and the body of the task. So that’s the story of how a small piece of Javascript code can have a pretty nice result in SharePoint! :-)

You can download the source code for this demo here. The zip file contains the necessary libraries and CSS files (which you have to upload to the Shared Documents document library for example) and the code you have to copy/past in a Content Editor Web Part (using the Source Editor button!). Additionally you can also find an exported web part (.dwp file) in the zip file, which you can very easily import or add to the web part gallery of a site (so you don’t have to copy/past the code yourself).

Due to popular demand I’ve created another sample of how you can make use of the jQuery Javascript library in your SharePoint sites. This example uses SharePoint’s Lists.asmx web service to retrieve all the list items of a specific list. In my previous posts I showed how you could use jQuery in SharePoint Site Pages (regular .aspx pages uploaded to a Document Library), so let’s do something different now; let’s use jQuery in a plain Content Editor Web Part.

To try this sample navigate to the home page (usually /default.aspx) of a SharePoint site that has a list with some list items in it, in my code I’ll use the Task list of a plain vanilla Team Site. Switch the page to Edit mode (Site Actions, Edit Page), and add a new instance of the Content Editor Web Part to the page. In the properties of that web part, copy and paste the following code using the Source Editor button.

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>

<script type="text/javascript">
    $(document).ready(function() {
        var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>Tasks</listName> \
                        <viewFields> \
                            <ViewFields> \
                               <FieldRef Name='Title' /> \
                           </ViewFields> \
                        </viewFields> \
                    </GetListItems> \
                </soapenv:Body> \
            </soapenv:Envelope>";

        $.ajax({
            url: "_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
            complete: processResult,
            contentType: "text/xml; charset=\"utf-8\""
        });
    });

    function processResult(xData, status) {
        $(xData.responseXML).find("z\\:row").each(function() {
            var liHtml = "<li>" + $(this).attr("ows_Title") + "</li>";
            $("#tasksUL").append(liHtml);
        });
    }
</script>

<ul id="tasksUL"/> 

On the first line the jQuery library is loaded from googlecode.com. To make this your, your client browser needs to have Internet access of course. Alternativly you can host the jQuery library yourself (see my previous examples) or even load the jQuery library in every page using the SmartTools.jQuery component. After that a function is attached to the jQuery document ready event. In this function the SOAP envelope message is constructed (the soapEnv variable). If you’d like to see the code getting list items from another list than the Task list, you’d have to change the listName element. The second part POST-ing the SOAP envelope to the web service by using jQuery’s ajax function. When the web service comes back with the result, the processResult method is called. In this function a loop is created over every row element (in the namespace z). Notice that "z:row" escapes in Javascript to "z\\:row". For every row element a new li HTML element is added to the ul element with ID tasksUL. And that’s it! You can see the result in the screenshot below.

More Posts