11 Nov 2008

Clean landing page

I mentioned that your SharePoint site should be clean and simple and have search on it. Well what does that mean visually? Trust Michael O’ Donovan to provide the perfect screen shot in his last post, well actually not in his last post but in the attachments on his last post:


This is clean (duh), it’s easy to use (people can find the content easily), search is there (and has focus because it’s in the centre) and it’s familiar (remind you of any search engines).

11 Nov 2008

SharePoint Search Tips and Tricks

I thought I would share some tips and tricks for improving the search experience with SharePoint:

In any company you will have people of different backgrounds and skills using SharePoint, and one of the first issues is that search isn’t fine grained enough, and that users either don’t know or don’t feel comfortable with advanced search features to get it fine grained. To make “normal” search easier just add Faceted Search. If you are interested in what that is go and check out the site.

Next improve usage by lighting up SharePoint search to the browsers. Well what does that mean? It means that when you go to a website with a modern browser it “detects” the search functionality and allows you to add it to the build in search functions in your browser, so you can search the SharePoint site from your browser without even going to it first! It does this using an open standard called Open Search. To do this you first need to define an XML file which tells the browser what to do. Example:


Really simple, basically just the name and the encoding. The magic is handled by replacing the tag in the URL ({searchTerms}) with what the user is searching for. That file needs to be uploaded to a location on the SharePoint site where it can be read by users. The next step is exposing it to the browsers, to do this you just need to add a line to the header tag in your master page:

   1: <link rel="search" type="application/opensearchdescription+xml" href="/search/searchdefination.xml" title="BB&amp;D Portal" />

Now the browsers will see the tag and light up the search facility! This is really helpful for improving adoption of search.

Next up it would be great to search multiple locations and you can thanks to a feature in SP 1 called federated search. Where your search query actually calls other web sites for results and places them in a special section of the site, defined by a web part. As I am personally interested in a lot of technologies I think it would be great to have federated search to: Wikipedia, Linux.Com, Java.Sun.Com, MSDN.Microsoft.Com and TechNet.Microsoft.Com, like shown on the left.

What you may notice is that MSDN already has support for federated search, but the rest don’t! So how do you get around that? Well Live.Com also does has support for federated search and it also has support for limiting results to a specific site. So all that is needed to do is to create a search provider configuration for live.com and limit it to the specified website. You can download the sample providers I created below:


The last tip is to implement a very clean landing page for the site with a search box on it. As the new landing is cleaner and smaller than the it meant the initial feeling of SharePoint is it is that it is quicker and more responsive and so it also will improve adoption. To get the search box to search properly using just a tiny bit of HTML + JavaScript which looks like this (assuming you have a textbox with ID called query), this will create the button:

   1: <input type="button" width="100px" height="" value="Search" onclick="window.location='/pages/SearchResults.aspx?k='+document.getElementById('query').value+'&s=All%20Sites';">
07 Nov 2008

Slide.Show and SharePoint: Part III - Deploy and final config

This is a multi-part series. The other parts can be found at

In part II, I wrote about how to get the data out of SharePoint in a dynamic way using a custom ASP.NET web page which did the hard work for us. The last part of this series is getting that code to work on the SharePoint server. The first step is to create a folder on the server where you will put these files (available at the end of the post). This should be outside your SharePoint directory, something like c:\inetpub\wwwroot\addons would be a good place. Next you need to create a virtual directory on your SharePoint site in IIS which points to that folder. The defaults for the virtual directory will be fine, however from a safety point of view I would recommend it runs in it’s own application pool.

If you just created a ASP.NET application though and put it in there it would fail because the web.config file from SharePoint does some odd things. To get around that you need to few things to it. First clear all the inherited assemblies away (see line 6 below), this has the downside that all the defaults from the machine.config also disappear so you need to add the ones you need back (lines 7 to 13). Next you need to clear all inherited httpModules (see line 19) and lastly you need to increase the trust of this application to full (line 21). Since this is on a virtual directory this has the advantage that you are not messing with the SharePoint config and your SharePoint server will continue to run happily.

   1: <?xml version="1.0"?>
   2: <configuration>
   3:     <system.web>
   4:         <compilation debug="true">
   5:             <assemblies>
   6:                 <clear/>
   7:                 <add assembly="System, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   8:                 <add assembly="System.Drawing, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
   9:                 <add assembly="System.Web, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  10:                 <add assembly="System.Web.Services, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  11:                 <add assembly="System.Xml, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
  12:                 <add assembly="System.Core, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
  13:                 <add assembly="ListMonkey"/>
  14:             </assemblies>
  15:         </compilation>
  16:         <authentication mode="Windows"/>
  17:         <customErrors mode="Off"/>
  18:         <httpModules>
  19:             <clear/>
  20:         </httpModules>
  21:         <trust level="Full"/>
  22:     </system.web>    
  23: </configuration>

That is really all the config you need for the application to run, however don’t forget the config for Slide.Show to run we looked at in parts 1 and 2.

Next up is briefly looking at how the application works. Firstly override render method of the ASP.NET page. In there I do a lot of validation of the inputs which are provided in the query string. This is also an opportunity to set defaults if certain values are not provided. Next, and very importantly, I set the output to be text/xml (this tells the client, in our case JavaScript what to expect). To do this I do:

   1: Response.ContentType = "text/xml";

Then I call the lists web service of SharePoint which returns the results, which are parsed and rendered to XML (I am unfortunately using XMLNode still when I wrote this, instead of the beautiful XElement stuff… but I digress) and to the screen using:

   1: Response.Write(outputDocument.OuterXml);

This XML is in the format Slide.Show expects and so it’s configuration system we setup last time can just use it! All of this gives us a real time integration into SharePoint with Slide.Show!!! :)

As promised the files are available for download. It has occured that the download gets corrupted for some reason, if that is the case I recommend using this download, which is compressed with 7-Zip. NOTE: I have not tested this beyond my own simple tests and your mileage may vary. If you have problems please post a comment here I and will respond.

The files also include the source if you are interested to see what late night coding after months of no coding produces (may not be my finest work… looking over it as I wrote this I felt the need to refractor grow).

22 Oct 2008

Blog Aggregator for SharePoint

The new S.A. Architect site has some dynamic content on the home page, namely the S.A. Architect Community Leads section and the S.A Architect Events section which both use this cool web part from Zlatan, which in a nutshell allows me to specify a number of RSS feeds and a tag to filter on and displays the content. This means that the community leads do not need to cross post or post twice when they produce content for the site. They simply need to make sure they have the right tag associated and the post will appear. This also means that content that is no relevant to SA Arch does not appear. Very cool stuff and big thanks for Zlatan for it.

08 Oct 2008

WSS and audience targeting - Part II

Today is NOT my day for things just working. Besides the demo gods eating every demo I did today and presenting me with excrement to work with, I found out that the super cool WSS audience targeting is broken on every browser except IE :( There is no use in complaining so silver lining is that I do get a chance to solve the problem and learn something new about browsers.

First off is that XmlHttpRequest is supported by all browsers, the XML DOM implementation is not compatible so even though you can get the data you can’t work with it easy. The cause is that Firefox or Chrome do not support selectNodes or selectSingleNodes methods or IE has unique methods which no other browser supports (depending which camp you are in). selectSingleNode is what I use to parse my results! To solve this I found some code at http://km0.la/js/mozXPath/ which adds the methods to the JavaScript classes! This is great in theory but it didn’t work because I have a default namespace being returned by my XML! The code I found didn’t work as selectSingleNode always returned null because it needed the namespace prefix on the XPath (i.e. /default:user) which IE can’t work with. So that meant not only implementing a new resolver which I found out about on developer.mozilla.org, it also meant doing a check and doing two XPath queries.

Something I didn’t mention in the first post is security and how secure or insecure this method is. Your biggest attack vector on this is that it runs client side and that the LoadXmlDoc method or even just the length check on the username can be changed easily so that it shows the content for logged in users to users who are not logged in! Basically it is not secure, but it doesn’t mean it opened a security hole because so you need to think about what is shown.In my case I made sure what is show is the bare minimum, just links to other pages. If an attacker gets the link they shouldn’t it does not worry me because the change password page, which signed in people can click to, is protected by WSS’s security. So even if someone were to try and access it they would get denied! The page itself also has security on it to help prevent security issues. The point I want to emphases is that this is NOT a security model, but a model for a better user interface. 


<script type="text/javascript">
    // Created by RMacLean - for comments email drpsupport@bbd.co.za
    // Partially from http://www.w3schools.com/XML/xml_http.asp
    // Partially from http://en.wikipedia.org/wiki/Xmlhttprequest
    // Partially from http://km0.la/js/mozXPath/
    // Partially from http://developer.mozilla.org/en/Introduction_to_using_XPath_in_JavaScript#Implementing_a_User_Defined_Namespace_Resolver
    // check for XPath implementation
    if (document.implementation.hasFeature("XPath", "3.0")) {
        // prototying the XMLDocument.selectNodes
        XMLDocument.prototype.selectNodes = function(cXPathString, xNode) {
            if (!xNode) { xNode = this; }
            var oNSResolver = document.createNSResolver(this.ownerDocument == null ? this.documentElement : this.ownerDocument.documentElement);
            function resolver() {
                return 'http://schemas.saarchitect.net/ajax/2008/09/user';
            var aItems = this.evaluate(cXPathString, xNode, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
            var aResult = [];
            for (var i = 0; i < aItems.snapshotLength; i++) {
                aResult[i] = aItems.snapshotItem(i);
            return aResult;
        // prototying the Element
        Element.prototype.selectNodes = function(cXPathString) {
            if (this.ownerDocument.selectNodes) {
                return this.ownerDocument.selectNodes(cXPathString, this);
            else { throw "For XML Elements Only"; }
        // prototying the XMLDocument.selectSingleNode
        XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode) {
            if (!xNode) { xNode = this; }
            var xItems = this.selectNodes(cXPathString, xNode);
            if (xItems.length > 0) {
                return xItems[0];
            else {
                return null;
        // prototying the Element
        Element.prototype.selectSingleNode = function(cXPathString) {
            if (this.ownerDocument.selectSingleNode) {
                return this.ownerDocument.selectSingleNode(cXPathString, this);
            else { throw "For XML Elements Only"; }
    // Provide the XMLHttpRequest class for IE 5.x-6.x:
    if (typeof XMLHttpRequest == "undefined") XMLHttpRequest = function() {
        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch (e) { }
        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch (e) { }
        try { return new ActiveXObject("Msxml2.XMLHTTP") } catch (e) { }
        try { return new ActiveXObject("Microsoft.XMLHTTP") } catch (e) { }
        throw new Error("This browser does not support XMLHttpRequest.")
    var xmlhttp;
    function loadXMLDoc(url) {
        xmlhttp = new XMLHttpRequest();
        if (xmlhttp != null) {
            xmlhttp.onreadystatechange = state_Change;
            xmlhttp.open("GET", url, true);
        else {
            alert("Your browser does not support XMLHTTP.");
    function state_Change() {
        if (xmlhttp.readyState == 4) {// 4 = "loaded"
            if (xmlhttp.status == 200) {// 200 = OK
                var username = "";
                if (xmlhttp.responseXML.selectSingleNode('//user') == null) {
                    username = xmlhttp.responseXML.selectSingleNode('//myns:user').getAttribute('username');
                else {
                    username = xmlhttp.responseXML.selectSingleNode('//user').getAttribute('username');
                if (username.length > 0) {
                    // user logged in
                    document.getElementById('resultText').innerHTML = '<P><A href="/memberPages/changepassword.aspx">Change Password</A></P>';
                else {
                    // anonymous
                    document.getElementById('resultText').innerHTML = '<P><A href="/Pages/signup.aspx">Signup</A><BR><A href="/Pages/forgotpassword.aspx">Lost Password</A></P>';
            else {
                alert("Problem retrieving XML data");
<span id="resultText">Loading...</span>
06 Oct 2008

Podcasting Kit for Sharepoint + Sub Site = It can be done

Recently I aged about a hundred years due to my intension to deploy the August 2008 release of the Podcasting Kit for SharePoint on a sub site due to the fact that there is so much hard coded into it (you can vote on that link for the work item to be done to fix this) that expects it to be at the root. Being stubborn and trying to show my elite SharePoint skills to all around me, I did not let little things like hard coding values stop me. No, I gave up a few years of my boyish good looks to get it working and in the end I did :) It’s not elegant but it works and should tide you over until the fixed release comes along.

First off let me say how great the August release is compared to the July release, it’s great. I actually don’t think you could’ve got this to work on the July release, there is that much cleanup and streamlining in it. One of the things they did was streamline the documentation from a monolithic single beast to a lot of smaller edible chunks. This is also a double edged sword for first timers because it means you go through the install doc and think you are done… when you aren’t, but I shall cover that later on.

For the tale assume the following facts:

  • SharePoint is deployed to a site available at http://intranet
  • We want the Podcasting kit to be available at http://intranet/sites/multimedia
  • I have created a life jacket file which contains versions of all the files I changed and may be of use to you. Note I have not tested it so it may not work. Best case is a simple find and replace and upload and you are in business, but worst case you need to follow the steps to get your files done. Download the life jacket file HERE
  • I am insane and this could all be wrong and my mind has made me believe it works.

Now I want to tell my tale of heroism at defeating bugs so that future crazy people don’t need to do this themselves. If you have the August 08 install guide and are following along (good idea, since that’s what I am doing to write this) I assume you have done your prerequisites and have a site collection as a sub site (i.e. http://intranet/sites/multimedia) and are at Installation Method #1 (Easy Method). Easy method that in itself should be the sign of the devil which awaits, because anything labeled easy never is. What you need to do is run the install to the root site (i.e. ssm.exe install PKSFull.xml http://intranet) and then uninstall it (i.e. ssm.exe uninstall PKSFull.xml http://intranet). I know it seems pointless but the installer doesn’t clean up well and leaves a few files behind (The key stuff it leaves is CSS’s, XSL’s and some JS files), and having these files at root helps us later on. Now run the installer again but now to your site collection (i.e. ssm.exe install PKSFull.xml http://intranet/sites/multimedia).

The next steps in the guide of the ratings DB, media encoder and feature activation work as documented. The user interface steps also work as expected but make sure you “fix” their links to have your site collection. So for a few examples:

  • SmartPhone access make the URL /sites/multimedia/mobilepages/pksmobilehome.aspx
  • Upload Podcasts make the URL: /sites/multimedia/PKS Podcasts/NewForm.aspx?RootFolder=%2FPKS%20Podcasts&Source=/sites/multimedia/pages/pkshomepage.aspx

a1Moving swiftly along the master and welcome pages instructions should work fine and that leads us to the first big hurdle the SmartPhone page because this is where the hard coding comes in. See the PKS uses a special web part called the Content Query Override web part, which is like the out of the box Content Query web part but allows you access to all the properties you couldn’t normally get to. However the PKS team set these up on their side and when you import it you are assured of doom and destruction if you are on a sub site. One of the properties that is hard coded is the path to the web URL for the site.  If you simply open the pksmobilehome.aspx page, find the weburl tag and change it to be your site path (in my case /sites/multimedia) prior to uploading the file, as per the document, you will be fine and continue through the document happily following the sections on Profiles, Silverlight 2.0, Rating and Commenting, External File Store, and Media Encoder Service to bring yourself merrily to the Pages section.

Before heading into the pages section we need to fix up the XSL files so they point correctly. You can find these in All Site Content –> Style Library –> XSL Style Sheets. Best is to switch to explorer view get all the files out of there and open them all up in text editor which allows Find & Replace across multiple files (I used Visual Studio, how developer of me) to fix the URL’s up. There are hundreds of the URLs so I suggest doing find and replace to fix it. As I said earlier all changed files are in the life jacket file, which includes these. It may be easier to use mine since you just need to search for /sites/multimedia and replace it with what ever your URL is. Use it, don’t use it, whatever. Once done re-upload the files and then make sure you check each file in as a major version.

The reporting page works great but you will find you are stuck at the podcasterdetail.aspx page because you can’t actually get to those settings. Once again this has to do with the fact that this web part is the content query override webpart. This one is a little worse than the mobile page because it has been pre-configured and is incorrect. To solve it, prior to editing the settings export the web part and save it to disk then open it up in your favorite text editor and make sure all the paths are correct, those being:

  • ItemXslLink
  • WebUrl
  • MainXslLink
  • ItemXslLinkOverride
  • MainXslLinkOverride
  • Xsl

Once you have done that you can upload the web part (either to the web part gallery or direct on the page) and you should be able to work with it successfully now.

Moving to the podcastdetail.aspx make sure you set the URL correct for the Xslt Override Location on the Content Rating-Review Results web part. You can/should skip the 9 to 15 (about the content editor web part which shows the edit link) since it references a JS file I have yet to be able to find and fix so it will point to the wrong URL and it will not work. If you do fix this please let me know. Setting up the web connections on the page should be fine and so should the search. If you do not have the metadata properties, like in my case, please read the troubleshooting guide. It’s likely you assumed too much and thought life was easy… easy install and all ;)

That should take you to the end of the guide, but not the end of the of our tale. If you recall I mentioned the double edged sword of splitting the documentation well the next step is not obvious. You should now go through the How To Apply Security Settings file and follow that because if you don’t you will have lots of crap. You should also be aware of a bug in the PKS which can prevent any non-admin like people from viewing/downloading content see here for details on that.


a3Right, now that the security is sorted we can get back to getting the site working as a sub-site. The next step is to fix the configuration so go to All Site Content and go to Content Rating Configuration Settings. You were here earlier and find the item you copied from before (the one that is a GUID@GUID) and make sure the value points correctly. Now head back to All Site Content and go to PKS Configuration Settings and set the various values in their to point to the correct URLs, this includes:

  • DownloadTracking.Location
  • ErrorHandling.InvalidMediaFile.Image
  • ErrorHandling.NoVideoStream.Image
  • Thumbnail.Custom.Images (not you have multiple URL’s in the value here, make sure you get them all).


Now you have the main page pkshomepage.aspx which suffers from the same issue as the podcasterdetail.aspx page in that the content query override webpart is incorrectly configured. You can follow the same procedure as before (export, edit, upload) to solve it.

At this point everything should look like it works fine, but there is a bug lurking under the covers. Anytime anyone watches a video it will increment the download count which is stored on the file entry in the PKS Podcasts list, this update of the list item causes the File URL to change and even though originally it pointed correctly to /sites/multimedia/_layouts/MSIT.customfiles/Download.aspx?…  it gets updated to point to the root site again, in other words /_layouts/MSIT.customfiles/Download.aspx?…

Before: a4

After: a5

I am not sure all the circumstances around the bug (it doesn’t seem to effect admins). To solve this I wrote a quick ASP.NET page which will redirect requests back to the correct URL, the file is in the life jacket file and is called downloads.aspx. Now if you have a different sub site path (i.e. not /sites/multimedia like me) then you need to open that file up in a text editor and change the URL as needed.


Important Notes:

  • YOU DO NOT NEED VISUAL STUDIO FOR THIS. The file is a single ASPX page with the code in it, the server will compile automatically at runtime.
  • The fact it points to a file called realdownload.aspx is correct.

Now get on to your SharePoint server (if you aren’t there already) and navigate to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\template\layouts\MSIT.CustomPages. This folder should have a single file called download.aspx, which you will rename to realdownload.aspx and copy the download.aspx file from the life jacket file into this folder. So all requests will hit the custom file which will redirect them back to the correct file. This should fix up the issue caused by the bug.

Now finally at this point you are actually done! You can now go through the documents on workflow and series and get all your content up and (hopefully) enjoy your Podcasting kit!

I want to thank a few people who helped me along in getting this right namely Michael Gannotti, Zlatan Dzinic, and of course the PKS team who responded to my posts on the site!

06 Oct 2008

Slide.Show and SharePoint: Part II - Getting the data

This is a multi-part series. The other parts can be found at

In part I we focused on getting Slide.Show to work with the content editor web part (CEWP), now the next step is getting the image information out of of the picture library so Slide.Show can display it. The first point you’ll need to understand is how Slide.Show knows where to find the images. It does it with a DataProvider, which is really a XML file reader. In the default configuration it looks for a file in same directory as the configuration file named Data.XML for the information, but it can be overridden with a custom DataProvider, like the Flickr one which was used in the last step of part I. Unfortunately there is no SharePoint data provider yet so we need to either roll our own version or provide the SharePoint data in a format Slide.Show can work with natively. Both require development but I prefer the second option because it means that I am not investing massively into Slide.Show, rather I am investing my time into a tool to get the data out and then a small bit of time into formatting it for Slide.Show. The advantage of this means that should a better solution come along in the future I can easily change the formatting part and have it work with the newer system. The other advantage of the second option is a personal one, because I prefer C# development (which I will build the provider in) with all of Visual Studio’s helping and documentation to JavaScript development (which is what would have to be used for the DataProvider).

The XML format which Slide.Show requires is something like this:

   1: <data>
   2:     <album ...>
   3:         <slide .../>
   4:         <slide .../>
   5:         <slide .../>
   6:         ...
   7:     </album>
   8: </data>

SharePoint won’t give the data to us in the format so to solve this I wrote an ASP.NET page does the following:


The Get stage is fairly logical in that we connect to the Lists web service in SharePoint and using the GetListItems method we retrieve the items from the list which returns as an XMLNode, which is not the easiest way to work for two reasons. Firstly SharePoint has a lot of namespaces so using XPath to extract the items is tricky and annoying, but thankfully John Wood did post a nice way to work with the XMLNamespaceManager which I use. The second reason comes back to the reason why I am doing this in C# as opposed to a dedicated Slide.Show provider. Once I convert it to easier to work with structure I can do the outputting to various systems a lot quicker. So in this case I created a simple class which contains a few properties to define the slide image and pop that into a List<T>. I am a bit of a List<T> fan boy using it where I can because it is fast and it allows me to use Linq with it easily (yes I know I can use Linq To XML but I prefer the rich integration in VS of Linq to objects and the thought of SharePoint’s XML scares me), and using Linq will allow for some nice features later on.


The last stage Provide is where I take my list of slides and use it to build up an XMLDocument which I will return. The building of the XMLDocument is not rocket science (just a couple of foreach’s really), but the returning it may be of interest to some people so I will just cover that briefly. What I do is drop the PageLoad method of the ASP.NET page and work in the render method which allows me to get into the page life cycle earlier on and control a few more parts of the output using the Response class. There are only two things I really care about, first is Response.ContentType which specifies to the caller (normally your browser but in this case Slide.Show’s DataProvider) what the content is. So we set that to Text/XML. This is one of the first things I do, and then one of the last things I do is send the XMLDocument.OuterXML to the browser using Response.Write. Provided I have done all my stages correctly I should be able to browse to the ASP.NET page and see XML formatted as I wanted it.


Part of building this also means making it reusable so there is a couple of things I did. First off the security is handled using built in Windows Authentication which may be a problem on web facing sites but it does mean that on intranet sites the security of the list items is maintained which is vitally imported. This is easily done by getting the configuration on IIS right (which we will cover in part III) and setting the Lists.UseDefaultCredentials to true. The next two vital configuration items are the URL to the lists web service and the name (or GUID) of the list we want. This is done using mandatory query string parameters (failure to provide them throws an AgrumentException) so your most minimalistic query string would look like this:


Breaking that down it has the following parts

I have added support for a few extra optional items to the query string as well which allows you to tailor the results:

  • view : Used to specify the view name (or GUID) you want returned. Leaving it out returns the default view. Type is a string.
  • limit : Used to specify the number of items to return. Default is all items. Type is a integer.
  • recurse : Used to specify if you want to recurse into folders or just return the items from the root. Default is to recurse. Type is a boolean.
  • group : Used to create Slide.Show albums based on the folders. Default is false. Type is a boolean.
  • random : Used to return the items (not albums, if group is true) in a random order. Default is true and type is boolean.

An example of using some of those options would look like this: http://sharepoint/addons/slideshow.aspx?url=http://sharepoint/site/_vti_bin/lists.asmx&list=Photo%20Gallery&group=true&random=false&limit=20


So the default provides all images (regardless of folders) in random order which looks great with the standard Slide.Show settings (lots of images, randomly displaying etc..).

That is the hard part of this, the last part is getting this web application to deploy on the server (some things to be aware of) and configuring Slide.Show to use it, which is surprisingly easy.

29 Sep 2008

WSS and audience targeting

If you follow Willy’s blog you would have seen a post mentioning I have been a busy little person recently (if you hadn’t see it you can go here to read it). One of the things about SAArchitect.net is that it does not run on the big boys edition of SharePoint, it actually runs on WSS. However the thing people commonly forget about WSS is that it actually can do everything it’s brother can do, it just does not have all the templates and the fiddly bits out of the box. This means the time/cost/pain to get to the same point as it’s brother is a bit/minor/average/excessively more. One of the fiddly little bits which is missing, is audience targeting. Which I think is super sexy for public web sites, especially when you want to show different things to users who have logged in or and those who haven’t. An example is on the new SAArchitect site when you are logged in it should let you change your password and if you aren’t logged in then it should let you apply or request a forgotten password.

So putting my developer cap on (or at least adjusting it since it doesn’t ever come off) I fired up my trusty copy of Visual Studio. I needed VS as I had decided to solve this using ASP.NET and a web part which does come with WSS, the Content Editor Web Part (CEWP). Utilizing all of the power of Visual Studio 2008, I toiled away to produce my ASP.NET page with a staggering 22 lines of code which looks something like this:

Note: All the code is available in the file at the end of this post.

   1: <%@ Page Language="C#" ContentType="text/xml" %>
   3: <%@ Import Namespace="System" %>
   4: <%@ Import Namespace="System.Globalization" %>
   5: <%@ Import Namespace="System.Web.UI" %>
   6: <script language="C#" runat="server">
   7:     protected override void OnPreRender(EventArgs e)
   8:         {
   9:             string result = string.Empty;
  10:             if (Page.User.Identity.IsAuthenticated)
  11:             {
  12:                 result = String.Format(CultureInfo.CurrentCulture, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
  13:                     "<user xmlns=\"http://schemas.saarchitect.net/ajax/2008/09/user\" username=\"{0}\" />", Page.User.Identity.Name);
  14:             }
  15:             else
  16:             {
  17:                 result = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
  18:                     "<user xmlns=\"http://schemas.saarchitect.net/ajax/2008/09/user\" username=\"\" />";
  19:             }
  20:             Response.Write(result);
  21:         }
  22: </script>

For those out there looking at that code and thinking it looks a little odd, it could be that the code all goes in the .aspx page itself. No code behind magic or web.configs or app_data folders to weigh this down. At 22 lines of code it’s all very simple: just checking if you are authenticated and if you are it returns an XML fragment with your name in it, and if you aren’t a XML fragment with no name and even a pretty namespace. In reality that could have been smaller like <u a=”0”/> if you aren’t logged in and  <u a=”1”/> if you are, but that isn’t very human readable ;) That single file sits on the WSS site in a document library, which is interesting (at least to me) because SharePoint will not let the code run by default as a way of protecting you from security issues. However if you are smart/brave/stupid enough you can tell SharePoint to let you run it, using the SharePoint web.config. Microsoft has a great article on how to do that over there.

Now that I have the code to tell me if I am logged in or not, I need to hide/show content based on it, which is easily done using the source editing options of the CEWP to plop in a bit of JavaScript. This bit of JavaScript uses a XMLHttpRequest to call the ASP.NET page and gets the result, which I check and based on the result show the relevant content as described below:

   1: <script type="text/javascript">
   2: //Partially from http://www.w3schools.com/XML/xml_http.asp
   3: // Partially from http://en.wikipedia.org/wiki/Xmlhttprequest
   4: // Provide the XMLHttpRequest class for IE 5.x-6.x:
   5: if( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function() {
   6:   try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
   7:   try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
   8:   try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
   9:   try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
  10:   throw new Error( "This browser does not support XMLHttpRequest." )
  11: };
  14: var xmlhttp;
  15: function loadXMLDoc(url)
  16: {
  17:   xmlhttp = new XMLHttpRequest();
  19:   if (xmlhttp != null)
  20:    {
  21:      xmlhttp.onreadystatechange=state_Change;
  22:      xmlhttp.open("GET",url,true);
  23:      xmlhttp.send(null);
  24:    }
  25:    else
  26:    {
  27:      alert("Your browser does not support XMLHTTP.");
  28:    }
  29:  }
  31:  function state_Change()
  32:  {
  33:    if (xmlhttp.readyState == 4)
  34:    {// 4 = "loaded"
  35:      if (xmlhttp.status == 200)
  36:      {// 200 = OK
  37:        var username = xmlhttp.responseXML.selectSingleNode('//user').getAttribute('username');
  38:        if (username.length > 0)
  39:        {
  40:          // user logged in
  41:  document.getElementById('resultText').outerHTML = '<P><A href="/memberPages/changepassword.aspx">Change Password</A></P>';
  42:        }
  43:        else
  44:        {
  45:          // anonymous
  46:          document.getElementById('resultText').outerHTML = '<P><A href="/Pages/signup.aspx">Signup</A><BR><A href="/Pages/forgotpassword.aspx">Lost Password</A></P>';
  47:        }
  48:      }
  49:      else
  50:      {
  51:        alert("Problem retrieving XML data");
  52:      }
  53:    }
  54:  }
  56:  loadXMLDoc("/Pages/loggedinuser.aspx");
  57:  </script> 
  58:  <span id="resultText"/>

Admittedly this is not a solution that the little old lady with the white lunch box will be able to implement while she updates the bridge league site, but this might work just fine for a site about architecture which has a few smart people, a couple of MVP’s and me running around behind it.

You can download the page and the JavaScript in this file: DOWNLOAD

IMPORTANT NOTE: I have posted a follow up article which fixes some issues with this version. For more information see WSS and audience targeting - Part II.