Using the Content Query Web Part and jQuery to create a staff desk locator

I recently took it upon myself to provide an alternative to the staff desk locator that was due to go to production in the near future. The back-story is that a requirement existed to locate staff on a floor map to assist with the move to a new office complex. The original concept was created by another developer, but with multiple requirement changes and multiple developers tweaking the original solution, the end result was far from perfect. It only worked completely in IE7 standards browser mode and some of the functionality performed in a less than ideal fashion – think scroll bars to navigate around a zoomed in map amongst other imperfections.

Now the solution I present here is also far from perfect, I have no illusions about that. I debated whether to post about it at all until I had a chance to optimse and refine it seeing that what exists here is essentially a protoype, however I believe it is in a complete enough state to provide some benefit or interest to people out there. If anyone can propose some better ways of achieving some of the functionality demonstrated here I’d be more than interested in hearing it. The first half of the post will explain the journey, so if you’re not interested in what drove the implementation decisions along the way, feel free to skip down to the solution.

The Journey

The concept I was going for was something similar to Google Maps; your standard zoom, move, mouse-wheel and drag functionality. The most attractive option I could find was a paid solution called MapBox which can be seen in their various featured map demos. I didn’t think a paid solution would be an option and besides, I wanted to see if I could create something similar myself. I found a jQuery plugin also called MapBox documented at Mapbox: Zoomable jQuery Map Plugin which definitely appeared to get me half way there. The only remaining challenge was to plot the points on the map and get it driven via SharePoint.

Plotting the points definitely proved challenging. I went through a few iterations as I lead myself down some wrong paths – the joys of experimentation. I went so far as to get everything adjusting correctly from a movement point of view (thinking I was better off starting easy and leaving the complicated functionality for later) only to have it breaking during zoom. It wasn’t until I further investigated the zoom requirements and changed the way in which I was approaching the challenge that I realised the work I had done to adjust for movement was redundant and was handled by the plugin. Lovely.

I eventually got the locations remaining in place during zooming however this exposed its own issues; the pins representing each location didn’t adjust in size as the map did, resulting in a very strange looking experience. It is here where my solution is least elegant – I essentially created multiple CSS classes and multiple sized images and dynamically changed the class of the elements as I zoomed in and out (using the width of the parent div to differentiate). I couldn’t simply scale the image as I was using background images and divs rather than image elements (for reasons based on issues with the previous solution and brief experimentation in this one). This exposed even more gotchyas – it appears different browsers round decimal values in different ways (there were variations around rounding, floor or ceiling). I also noticed that while on most occassions the plugin would zoom to fixed width sizes, occassionally it would result in completely unexpected values which I needed a safety net CSS definition for. I love Internet Explorer. The CSS is hence a pretty ugly beast, but for the most part it works.

The next step was adding some hover over functionality to display the staff members information. This was relatively simple and just leveraged the onMouseOver event and some jQuery for each plotted element. The search functionality was a little more complex. I had witnessed the functionality I desired during my research, present on the Rock ‘n’ Roll Metro Map, which I dug deeper into and discovered it was being achieved via the animate function of the jQuery UI plugin. A couple of equations and experimentation later to determine how I could establish my scroll-to points and it was all humming along nicely.

The final challenge was getting it all into SharePoint. This functionality was to sit on the intranet which had been created as a codeless solution, therefore rather than rendering what I needed via the API in a web part I decided to stick with the theme and leverage the Content Query Web Part (CQWP), drawing out of a custom list. The previous solution had done the same so it wasn’t too difficult with a few tweaks to achieve what I desired. A couple of the gotchyas encountered included ensuring the CommonViewFields were set in the CQWP (exporting and importing the web part was used to achieve this) and rediscovering that for a column to exist to filter on in the web part it needed to be a site column, not just a list column. One last little hurdle that needed to be jumped included how the parts sat on the page with the master page being used – and this differed between browsers (Did I mention how much I love IE?). Seeing this probably had a lot to do with the custom master page being used I’ll leave the specifics out in the solution below, however just know that it is something that may need to be considered.

The Solution

Part 1: The list to hold the data

This was a custom list with some custom columns. Note that the IsAResource column is a site column. The -1000 X and Y values reflect a staff member that does not exist in the building.

Part 2: Header.xsl

The templates in Header.xsl map to the Group Style settings selected in the Content Query Web Parts. They render once to open and close the items which are rendered in between. The below templates were defined.

  <xsl:template name="DeskfinderSelectName" match="*[@GroupStyle='DeskfinderSelectName']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div id="SearchByName"><span class="SearchLabel">Search by Name:</span><select onchange="FindUser(this.value);"><option value="">Please select an option...</option>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectNameClose" match="*[@GroupStyle='DeskfinderSelectName']" mode="footer">
    <script type="text/javascript">document.write('<![CDATA[</select></div>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectResource" match="*[@GroupStyle='DeskfinderSelectResource']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div id="SearchByResource"><span class="SearchLabel">Search by Resource:</span><select onchange="FindUser(this.value);"><option value="">Please select an option...</option>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderSelectResourceClose" match="*[@GroupStyle='DeskfinderSelectResource']" mode="footer">
    <script type="text/javascript">document.write('<![CDATA[</select></div>]]>')</script>
  </xsl:template>
  <xsl:template name="DeskfinderPin" match="*[@GroupStyle='DeskfinderPin']" mode="header">
    <script type="text/javascript">document.write('<![CDATA[<div class="StaffLocations">]]>')</script>
  </xsl:template>

The most important part to note here is that the DeskfinderPin doesn’t have an associated footer. This was a bit of a gotchya – the browsers closed off the div anyway but if I included the footer, the

<script type="text/javascript">document.write('<![CDATA[</div>]]>')</script>

node was appearing within the div which caused issues when I went to extract the HTML via jQuery and pump it into other locations. It worked fine without the footer so it was removed.

Part 3: ItemStyle.xsl

The templates in ItemStyle.xsl map to the Item Style settings selected in the Content Query Web Parts. They’re rendered for each item in the query. The below templates were defined.

  <xsl:template name="DeskfinderSelectOption" match="Row[@Style='DeskfinderSelectOption']" mode="itemstyle">
    <xsl:variable name="DisplayTitle">
      <xsl:call-template name="OuterTemplate.GetTitle">
        <xsl:with-param name="Title" select="@Title"/>
        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
      </xsl:call-template>
    </xsl:variable>
    <option value="{@ID}">
      <xsl:value-of select="$DisplayTitle"/>
    </option>
  </xsl:template>
  <xsl:template name="DeskfinderPin" match="Row[@Style='DeskfinderPin']" mode="itemstyle">
    <xsl:variable name="DisplayTitle">
      <xsl:call-template name="OuterTemplate.GetTitle">
        <xsl:with-param name="Title" select="@Title"/>
        <xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="starts-with(@Location,'L2')">
		<div class="pin2" style="left:{@X}px; top:{@Y}px;" onMouseOver="DisplayDetails(this);" data-id="{@ID}" data-xpos="{@X}" data-ypos="{@Y}" data-name="{$DisplayTitle}" data-phone="{@Phone}" data-email="{@Email}"></div>
      </xsl:when>
      <xsl:otherwise>
        <div class="pin3" style="left:{@X}px; top:{@Y}px;" onMouseOver="DisplayDetails(this);" data-id="{@ID}" data-xpos="{@X}" data-ypos="{@Y}" data-name="{$DisplayTitle}" data-phone="{@Phone}" data-email="{@Email}"></div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

It is important to note that all of the custom columns referenced in the template (X, Y, Email, Phone) need to appear in the CommonViewFields element of the Content Query Web Part to be recognised. This can be done by exporting the web part, manually editing the .webpart file to add those columns and importing it back onto the page.

Part 4: The resources

CSS – the CSS used was extended from that used for the MapBox demo with some slight tweaks to those definitions. A little more was added to style the drop down menus and staff information. The majority of CSS was to style the pins mapping to the locations of staff members. As mentioned before, this was a very bloated solution and far too much to include in this post. I’ll try and summarise.

There were essentially 4 groups of styles – level 2 pins, level 3 pins, level 2 ‘located’ pins and level 3 ‘located’ pins. These were seperated into whether they appeared in the firstLayer, secondLayer, thirdLayer or fourthLayer. They were further seperated into sizes mapping to the width of the parent div (the reason for 2 sizes 1 pixel apart is due to the rounding issue I mentioned earlier). Here are a few randomly selected examples:

#firstLayer .pin2
{
	position: absolute;
	width: 10px;
	height: 9px;
	background: url(../PublishingImages/pin2-size709.gif);
}

#secondLayer .pin2.found.size974, #secondLayer .pin2.found.size975
{
	width: 16px;
	height: 16px;
	background: url(../PublishingImages/found2-size975.gif);
}

#fourthLayer .pin3 {
	position: absolute;
	width: 30px;
	height: 27px;
	background: url(../PublishingImages/pin3-size2126.gif);
}

#fourthLayer .pin3.size1505, #fourthLayer .pin3.size1506 {
	width: 21px;
	height: 19px;
	background: url(../PublishingImages/pin3-size1506.gif);
}

You get the idea. Perhaps look into an alternative solution!

Images – The images included the 4 different sized floor map images, the map control image and 100 different pin images to match the CSS styles.

JavaScript – The javascript leveraged that provided with the MapBox demo with the following adjustments.

Functionality to populate the staff information:

function DisplayDetails(person)
{
	$("#staffName").text($(person).attr("data-name"));
	$("#staffPhone").text($(person).attr("data-phone"));
	$("#staffEmail").html('<a href="mailto:' + $(person).attr("data-email") + '">' + $(person).attr("data-email") + '</a>');
}

Functionality to find a given user or resource:

function FindUser(id)
{
	var limitX = 0, limitY = 0, mapWidth = $(".mapwrapper").width(), mapHeight = $(".mapwrapper").height(),
	nodeWidth = $(".current-map-layer").width(), nodeHeight = $(".current-map-layer").height();

	if(mapWidth < nodeWidth) limitX = mapWidth - nodeWidth;
	if(mapHeight < nodeHeight) limitY = mapHeight - nodeHeight;

	var element = $(".current-map-layer div[data-id='" + id + "']");
	var left = (parseInt($(element).css("left")) * -1) + (parseInt($(".mapwrapper").css("width")) / 2);
	var top = (parseInt($(element).css("top")) * -1) + (parseInt($(".mapwrapper").css("height")) / 2);

	left = (left > 0) ? 0 : left;
	left = (left < limitX) ? limitX : left;
	top = (top > 0) ? 0 : top;
	top = (top < limitY) ? limitY : top;

	var width = parseInt($(".current-map-layer").css("width"));
	if ($("div[data-id='" + id + "']").attr("class").indexOf("pin2") == -1)
		$("div[data-id='" + id + "']").attr("class", "pin3 found size" + width);
	else
		$("div[data-id='" + id + "']").attr("class", "pin2 found size" + width);

	$(".current-map-layer").animate({top: top,left: left}, {easing: "easeOutQuart"});
	DisplayDetails(element);
}

The important thing to note here is that extra code was required to ensure the map wasn’t moved ‘out of viewport’. Thankfully, the MapBox JavaScript already included that logic for its drag functionality so I just leveraged that.

Document Ready functionality:

$(document).ready(function() {
	$("#firstLayer,.mapcontent").html($(".StaffLocations").html());

	$("#viewport").mapbox({
		mousewheel: true,
		layerSplit: 8//smoother transition for mousewheel
	});
	$(".map-control a").click(function() {//control panel
		var viewport = $("#viewport");
		//this.className is same as method to be called
		if(this.className == "zoom" || this.className == "back") {
			viewport.mapbox(this.className, 2);//step twice
		}
		else {
			viewport.mapbox(this.className);
		}
		return false;
	});
})

The first line is the most important – this is where the HTML is taken from the hidden div rendered by our Content Query Web Part and injected into the necessary locations. The rest of the code is the same that existed for the MapBox demo.

Custom changes to the _zoom function (insert above return movement; line)

/* Custom changes */
var xChange = (totalWidth == undefined) ? 1 : totalWidth / $("#firstLayer").width();
var yChange = (totalHeight == undefined) ? 1 : totalHeight / $("#firstLayer").height();

$(".pin2,.pin3").each(function(index, value) {
	$(this).css("left", ($(this).attr("data-xpos") * xChange));
	$(this).css("top", ($(this).attr("data-ypos") * yChange));

	var width = parseInt($(".current-map-layer").css("width"));
	if ($(this).attr("class").indexOf("pin2") == -1)
	{
		if ($(this).attr("class").indexOf("found") == -1)
			$(this).attr("class", "pin3 size" + width);
		else
			$(this).attr("class", "pin3 found size" + width);
	}
	else
	{
		if ($(this).attr("class").indexOf("found") == -1)
			$(this).attr("class", "pin2 size" + width);
		else
			$(this).attr("class", "pin2 found size" + width);
	}
});
/* End Custom changes */

Part 5: Referencing the resources

This was a chromeless Content Editor Web Part referencing the required resources:

<link href="../Documents/master.css" type="text/css" rel="stylesheet">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
<script type="text/javascript" src="../Documents/mousewheel.js"></script>
<script type="text/javascript" src="../Documents/mapbox.js"></script>

Take note the version of jQuery referenced – it’s not the latest, but was required for MapBox to function correctly.

Part 6: The search box

This was a chromeless Content Query Web Part pointing to the list in Part 1. It was set to show items of type ‘Custom List’ and filtered where ‘IsAResource is equal to 0′. The Presentation was grouped by <Site> (to ensure the group element was rendered) and sorted by ‘Title ascending’. The ‘Limit the number of items to display’ was turned off. The Group Style was set to ‘DeskfinderSelectName’ and Item Style set to ‘DeskfinderSelectOption’.

To include a Search by Resource box as well as a Search by Name box, the steps above can be followed, however the filter value should be 1 and the Group Style should be set to ‘DeskfinderSelectResource’.

Part 7: The map, associated controls and staff information

This was a chromeless Content Editor Web Part which essentially rendered the HTML which existed in the MapBox demo with a few minor tweaks.

<div class="mapwrapper">
	<div id="viewport" style="overflow: hidden; position: relative;">
		<div id="firstLayer" class="current-map-layer" style="background: url('Images/FloorImage1.png') no-repeat scroll 0% 0% transparent; width: 709px; height: 631px; position: absolute; display: block; left: 0px; top: 0px;">
		</div>
		<div id="secondLayer" style="height: 947px; width: 1063px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
		<div id="thirdLayer" style="height: 1262px; width: 1417px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
		<div id="fourthLayer" style="height: 1893px; width: 2126px; position: absolute; display: none;">
			<img style="width: 100%; position: absolute; left: 0pt; top: 0pt;" src="Images/FloorImage.png" alt="">
			<div style="position: absolute; left: 0pt; top: 0pt; background: none repeat scroll 0% 0% white; opacity: 0;" class="map-layer-mask"></div>
			<div style="position: absolute; top: 0pt; left: 0pt;" class="mapcontent">
			</div>
		</div>
	</div>
	<div class="map-control">
		<a href="#left" class="left">Left</a>
		<a href="#right" class="right">Right</a>
		<a href="#up" class="up">Up</a>
		<a href="#down" class="down">Down</a>
		<a href="#zoom" class="zoom">Zoom</a>
		<a href="#zoom_out" class="back">Back</a>
	</div>
	<div class="staff-information">
		<div><span class="staff-info-label">Name: </span><span id="staffName"></span></div>
		<div><span class="staff-info-label">Phone: </span><span id="staffPhone"></span></div>
		<div><span class="staff-info-label">Email: </span><span id="staffEmail"></span></div>
	</div>
</div>

Note that the images and sizes were adjusted respectively and each layer div was given an id so I could reference them via both jQuery and CSS. The staff-information div was also included to present the staff information.

Part 8: The staff locations

This was a chromeless Content Query Web Part pointing to the list in Part 1. It was set to show items of type ‘Custom List’, grouped by <Site> and the ‘Limit the number of items to display’ was turned off. The Group Style was set to ‘DeskfinderPin’ and Item Style set to ‘DeskfinderPin’.

Note that the contents rendered here were within a div hidden by the CSS – it’s main purpose was to render the data once, then jQuery injected it into the locations required.

The End Result

An image doesn’t really do it justice, but for the purposes of completing the article, the end result looks something like that depicted below.

Either that or you can watch a video below on how the functionality works.

Error Logging in SharePoint

FRUB938D3AJG
As identified in my post Exception Handling in SharePoint it is important to determine the most appropriate form of logging for your situation and use it. My original intention for this post was to create more of a how-to for each possibility but in my research discovered that the number of quality resources on this topic is extensive. This being the case, the focus for this post will instead discuss the different options available, point out some of the best resources I found on the subject if appropriate and summarise what I think is the best overall option to use in most instances.

The forms of logging I identified and will discuss below include logging to the SharePoint ULS, to the Event Log, via email, a database, CRM or a SharePoint list.

SharePoint ULS

Logging to the SharePoint ULS is arguably the most logical option when it comes to logging exceptions in your custom code. It is most appropriate for developers or support staff if they have access to the files and are able to monitor them consistently. Ideally, the ULS should be monitored regardless to ensure the ongoing health of the site but under some scenarios the files may not be made available and hence logging here would go unnoticed – in this situation an alternative logging option should be considered.

The difficulty with logging to the ULS surrounds the different options available depending on what version of SharePoint you’re using. SharePoint 2010 makes this task a breeze with its SPDiagnosticsService functionality which is available in both the free (SPF) and licenced (SPS) versions of the product. The best articles I found on this topic were Tobias Zimmergren’s article on SP 2010: Developing for performance Part 4 – Logging and Waldek Mastykarz’s extension of this in Logging to ULS in SharePoint 2010.

The task is more difficult in SharePoint 2007. MOSS at least provides some functionality to write to the logs via Microsoft.Office.Server.Diagnostics.PortalLog.LogString (briefly documented in Clayton James’ post Log errors to SharePoint Log file) but that is not available in WSS. There are some examples listed including Eli Robillard’s SharePoint Trace Logs and the Unified Logging Service (ULS) however a number of links in that article are broken now. Gunnar Peipman’s SharePoint: Writing messages to ULS (Unified Logging System) is another quality post however a reasonably complicated solution. There is also a Codeplex SharePoint Logging Library by Ayman El-Hattab that may be able to be harnessed. As you’re probably appreciating now, logging to the ULS in WSS was not made easy.

There is however one saving grace for any version of SharePoint when it comes to logging to the ULS and that is the
patterns & practices SharePoint Guidance Library which i’ll discuss in more detail later.

The other possible qualm about logging to the SharePoint ULS is the difficulty in reading them. This is made easier in SharePoint 2010 by Microsoft’s ULS Viewer or Stefan Gordon’s SharePoint ULS Log Viewer on Codeplex. Tools also exist for SharePoint 2007 including Florin Muntean’s SPTraceView and Stuart Starrs’ WSS / MOSS Log File Reader – both on Codeplex.

Event Logs

The other logical place to log exceptions is the Event Log. It is most suitable for system administrators or developers and support staff who have access to the server (hopefully this isn’t the case in production environments!). Much like the ULS it is reliant on the logs being monitored on a consistent basis and also on the system administrators reporting any issues back to the development or support teams.

Again SharePoint 2010 steps up to the plate with it’s SPDiagnosticsService class, also documented in Tobias’ post outlined previously. SharePoint 2007 has to rely on core functionality in the System.Diagnostics namespace called EventLog.WriteEntry briefly documented in SharePoint King’s Event Log Access Denied : Sharepoint /.NET. As that article points out (to a degree), you need to ensure that access to write to the Event Log is granted to the relevant accounts.

One thing to point out here is, like the ULS logs above, the patterns & practices SharePoint Guidance Library provides the ability to write to the Event Logs as well.

Email

Logging exceptions via email is an interesting one. In my opinion, email should only be used hand in hand with another logging option primarily for the purposes of notification. It is mostly required if the primary log store isn’t checked on a regular basis. There are 2 options available here; either use the in-built SharePoint emailing capability of SPUtility.SendEmail or using .NETs SmtpClient class – both documented in Mohd Sukri At’s post Send Email in SharePoint via .NET SmtpClient Class and SharePoint SPUtility Class.

Database

I’m not a huge advocate of using a database logging mechanism in a SharePoint context however it may be appropriate if there is a need to aggregate exceptions from multiple (and non-SharePoint) applications across an organisation. If database logging is desired then a library should be used such as log4net or the Enterprise Library Application Block. There is a good article on Configuring Enterprise Library 4.0 for Exception handling and Logging by Suleman Ibrahim and some potential issues in Cory Roth’s How to get Enterprise Library working with SharePoint if you choose to go down this path. While on the topic of the Enterprise Library it may be worth pointing out that it is also possible to use it to log to the ULS logs demonstrated in Madhur Ahuja’s post Implementation of Logging and Instrumentation Application Blocks in MOSS 2007.

CRM

My opinions on logging to CRM are similar to that of a database. The most appropriate reason for this however would be in a tightly integrated SharePoint and CRM application where interaction mostly takes place on the CRM side. I’ve actually been involved in projects like this where logging exceptions to CRM was deemed more appropriate than SharePoint as it would aggregate issues from both systems and be more closely monitored. At the risk of exposing my lack of CRM knowledge and in lieu of providing a link to an example, the premise would be to have a log entity in CRM and have a library code-side which creates a dynamic entity of that type, populates the relevant fields, creates a TargetCreateDynamic object binding the dynamic entity and a request binding the target, then executing the request via the CrmService. Sounds easy, is relatively easy, but will leave this to the CRM experts.

SharePoint list

I find it interesting that I have yet to see an implementation of exception logging to a SharePoint list. It’s a highly visible and accessible location to monitor issues within a SharePoint site. Another plus to logging to a SharePoint list is that it is sandbox-friendly and hence a viable option for SharePoint 365. Timmy Gilissen identifies this in his post SharePoint 365 logging and the example is not restricted to just sandboxed solutions (note that if you’re going to take the code from that page you may also want to read Rob Garrett’s post Efficient way to add a new item to a SharePoint list).

On the topic of sandboxed solutions it is something worth pointing out in terms of the other logging mechanisms. It essentially makes them far more difficult to implement, for instance logging to the ULS or Event Logs in a sandbox requires a full-trust proxy due to the Microsoft.SharePoint.Administration namespace not being available in a sandboxed environment. The patterns & practices SharePoint Guidance Library however includes a full trust proxy which enables the use of it’s SharePoint Logger within the sandbox.

The patterns & practices SharePoint Guidance Library

I’ve been a little coy throughout this post regarding the patterns & practices SharePoint Guidance Library solely for the reason that I think it’s worthy of it’s own section in this post. The library is amazing on a number of fronts and this is no different when it comes to Logging. In fact, it is my most recommended option for logging exceptions assuming logging to the ULS and/or the Event Logs is suitable for your circumstances. I highly recommend you visit the page and read what the library offers both from a SharePoint 2007 and 2010 perspective. Another post worth reading in regards to the library is Alex Angas’ Intro to SharePoint 2010 patterns & practices – Logging.

As you can see there are a number of options when it comes to logging exceptions in SharePoint and depending on your circumstances, different ones may be the most suitable. The important part is that you use some form of exception logging and do it in a best practice manner.

Application Settings in SharePoint

One of the most commonly neglected features in SharePoint I find across a number of different projects is the manner in which application settings are managed. Far too often it is left to manually modifying the <AppSettings> section of the web.config file for the relevant web application. Perhaps this is due to legacy behaviour of ASP.NET developers moving into the SharePoint sphere, perhaps its due to environments being small and the risks of manually modifying the file not being exposed. It may be due to organisational process or procedure, or (most likely) it may be because on face value it appears to be the most simple, quick and easy option to maintain some configuration values.

It’s ironic that in projects laced with features and solutions to deploy resources ‘the right way’ that configuration is overlooked and remains a manual process. Aside from wanting to avoid any manual modification of files directly on the server by principle, dangers exist in terms of ensuring the web.config files are the same on all web front end servers. Maintaining application settings this way also requires access to the web.config file (and hence server) itself which wouldn’t be considered best practice, and the act of changing a variable in the web.config directly would cause the application pool to refresh and cause the site to load slowly on the next hit.

I write this post with a tinge of regret – I too have been guilty on multiple occassions of maintaining the status quo and leaving (and even adding to) configuration values maintained manually in a web.config file. So not only should this post serve as a source of information regarding the best practice methods to maintain application settings, but it should also serve as a motivator to myself and others to practice what I/we preach on all future greenfield projects.

Anyone who has studied for or taken the application development exam (70-576) would have noticed that 4 main options consistently get presented for application configuration: web.config, lists, property bags and the hierarchical object store. I’ll discuss each option briefly and provide some situations where the method would be appropriate.

Web.config

Now it may seem like i’m beginning to contradict myself here – I previously mentioned that manually maintaining application settings in the web.config was a BAD idea. It is. The beauty of SharePoint though is that it provides a mechanism to maintain application settings in the web.config file automatically via the SPWebConfigModification class. If you’re wanting to store them in the web.config, then a feature should be created which on activation uses the SPWebConfigModification class to modify the web.config for you. This ensures that the same values are written to the web.config files on all front-end web servers and even ensures they’re consistent amongst new servers added to the farm.

You’re likely to want to use this option if you just can’t pull yourself away from the web.config for variable configuration. At least its a safer method. There are some valid reasons however – it’s reasonably simple to incorporate this functionality and then easy to program with seeing you’re probably used to it already. It ensures values are consistent across all front-end web servers and are available and consistent across the entire web application. It’s also fairly useful for ‘set and forget’ the values – if you’re unlikely to need to change them in the future, then you won’t run into the application pool refresh issue I mentioned previously and will get the performance benefits of the cached settings.

Before running off and using this method however, be warned that the web is littered with issues and problems with using SPWebConfigModification. I’d probably want to avoid making any ‘unnecessary’ changes to the web.config especially for the purposes of setting configuration values considering there are other options available.

SharePoint lists

It almost seems too easy and obvious. SharePoint provides us with these useful things called lists. Most of us would be familiar with or have used database-driven variable configuration mechanisms before. Lists, on face value, are reasonably similar to database tables. Why don’t we use lists for variable configuration? It’s not actually that silly a proposition and is in fact one of the more reasonable options. One of the beauties of using lists is that you get all that SharePoint listy goodness. Versioning, security and an out-of-the-box UI for example. You’re likely already familiar with list-based programming, so that isn’t too much of an inconvenience.

Using SharePoint lists is definitely one of the preferred methods. It’s probably easier to outline some of the reasons why you may not want to use it; a site with lax security restrictions would probably be wise to avoid this method – you wouldn’t want any user being able to modify or delete configuration items or the list itself. If you wanted the application settings to be available to multiple site collections/web applications then you’d potentially end up duplicating the list values across these sites.

There’s another huge advantage to choosing this method. An MVP named Chris O’Brien has written a Config store framework available on Codeplex which makes the option even more attractive and erases some of the drawbacks from choosing it. I’d definitely recommend reading a bit about it in his post A better Config Store for SharePoint sites.

Property bags

This option is perhaps a little less obvious but similarly powerful. A number of SharePoint objects contain a Properties property which stores a collection of key/value pairs. These obviously lend themselves towards being used for storing application settings.

This option is possibly the best one to harness when scope is an important consideration for configuration. It lends itself to being able to maintain numerous variables with different values on a per-scope basis (per web, per web application and so forth).

Like the SharePoint list option, this also benefits from addons which improve the usability and effectiveness of the option. The SharePoint Guidance Library provides the Application Setting Manager which is an API to read and write application settings. Codeplex also has the SharePoint Property Bag Settings project which provides a GUI in Central Administration to manage the settings.

Hierarchical Object Store

This is a farm-scoped store similar to a property bag. It requires custom code to create and is far more complex than the other options, however has the benefit of being able to store more complex types if that’s neccessary. Generally for the purpose of variable configuration though, in my opinion i’d go for one of the other options.

You’d possibly choose this option if you were looking for a global storage option for values or required complex values to be stored. If the values are simple key/value pairs however a farm-scoped property bag would probably be easier to use and do the trick.

WebPart properties

I know I said before there were 4 main options, but I wanted to briefly cover this one too. In the event that your custom web parts require configuration values specific to the web part then it will often make sense to include custom properties editable in the web part itself – they are then able to be set by modifying the settings of the web part.

My initial concept for this post was to include an array of advantages, disadvantages and code samples on each option to more completely highlight where each could be used and how they’re used, however during my research for this post came across an MSDN article written by David Mann titled Managing Custom Configuration Options for a SharePoint Application – it essentially covers in reasonable detail those points and I would strongly recommend reading.

Overall I’d have to say I prefer the SharePoint list method the best. I’m somewhat surprised, I remember thinking a while ago that using lists for application settings would be a bit primitive and that surely the other options which have been provided (obviously for the purpose of variable configuration?) like SPWebConfigModification, SPPersistedObject and the Properties collections would be the best practice method to use. I guess sometimes the easy option actually is the best, and it’s sandbox-friendly to boot.

Follow

Get every new post delivered to your Inbox.