Tuesday, March 28, 2006
Who Am I? Demystifying SharePoint Impersonation.
But what about those times when you need to execute code with permissions greater than that of the current user (like instantiating a site collection or enumerating list permissions)? Well, that’s not quite so easy, but it’s not that hard, either – Lois and Clark have an excellent post on various methods for altering security context. There’s also Todd Bleeker’s method and Maurice Prather’s approach. And just to cook your noodle a bit more, what about those times when you not only need to run in an administrative context but you also need to check permissions for the current user? So much for simple.
Thankfully, there is a relatively straightforward way to achieve both objectives. By combining a bit of RevertToSelf() with AppDomains and SPRoleCollections, we can run securely in an administrative context without hard-coding account details, force SharePoint to use the credentials we give it, and verify that users have access to the content being displayed. In fact, this is the only way I’ve found to enforce impersonation in all instances.
I must admit to not being the world’s greatest programmer, so I spent quite a bit of time flipping between each code snippet trying to piece together a solution (code wizards like Simser and Tielens can do this stuff in their sleep but I seem to be lacking an equivalent concentration of grey matter). To save you from the same headaches, here’s the code in full with a complete description afterwards.
using System;
using System.Data;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Configuration;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security;
using System.Security.Policy;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebControls;
namespace MyNameSpace
{
public class MyClass : System.Web.UI.Page
{
[DllImport("advapi32.dll")]
static extern bool RevertToSelf();
private void Page_Load(object sender, System.EventArgs e)
{
WindowsIdentity objOriginalUser = WindowsIdentity.GetCurrent();
RevertToSelf();
WindowsIdentity.GetCurrent().Impersonate();
try
{
AppDomainSetup objAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;
Evidence objEvidence = AppDomain.CurrentDomain.Evidence;
AppDomain objMyAppDomain = AppDomain.CreateDomain("MyAppDomain", objEvidence, objAppDomainSetup);
SPSite site = new SPSite(<portal URL>);
site.CatchAccessDeniedException = false;
SPWeb web = site.AllWebs[<site URL>];
SPPermissionCollection perms = web.Permissions;
SPUserCollection users = web.Users;
foreach (SPUser user in users)
{
if (objOriginalUser.Name.ToLower() == user.LoginName.ToLower())
{
SPRoleCollection roles = user.Roles;
SPListCollection lists = web.Lists;
foreach (SPRole role in roles)
{
foreach (SPPermission perm in perms)
{
if (role.ID == perm.Member.ID)
{
if
(role.PermissionMask.ToString().IndexOf("ViewPages") != -1 role.PermissionMask.ToString().IndexOf("FullMask") != -1)
{
// TODO: Insert your code here.
}
}
}
}
}
}
}
catch(System.UnauthorizedAccessException ex)
{
Response.Write(ex.Message.ToString());
}
}
AppDomain.Unload(objMyAppDomain);
}
}
Before we go any further, allow me to point out that the code sample is probably causing real .NET developers to pull their hair out – for one, I don’t know all their code optimization techniques, and for another, I’m trying to keep it simple so this post doesn’t run off the end of the page, spill over the keyboard, and end up in the reader’s lap. So have some mercy and don’t flame me too badly but do post any tips that will help make the code better. Also, note that throughout this post I’ve tried to use the same variable names and syntax as the original articles to avoid confusion.
Now on to business. In the above example, we are instantiating a new SPWeb object in the Administrator context and checking the permissions for the actual logged-in user for the target site. The first step is to make sure we have the proper references so be sure to add the proper DLL’s to your project and add all the required ‘using’ statements (you may need more or less depending upon what your application does).
using System;
using System.Data;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Configuration;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security;
using System.Security.Policy;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebControls;
Next, we need to import the ‘advapi32.dll’ in order to facilitate impersonation of user accounts.
[DllImport("advapi32.dll")]
static extern bool RevertToSelf();
Once we have the proper resources, we can impersonate the administrative user account. The RevertToSelf() function returns the user credentials of the account under which the SharePoint application pool runs in IIS (MSSharePointPortalAppPool in a default installation). If you are running your portal under a different context, now would be a good time to change it to a local admin account that is also a SharePoint administrator. The biggest advantage of this method is that it does not required any sensitive information (such as passwords) to be stored in clear text; the credentials are specified in the IIS settings for the virtual server.
WindowsIdentity objOriginalUser = WindowsIdentity.GetCurrent();
RevertToSelf();
WindowsIdentity.GetCurrent().Impersonate();
The first line gets the current user account for later use. The second calls the Application Pool credentials and the third sets the impersonation context to that account (omitting it will generate an Access Denied error).
Once we have the proper credentials in place, it’s necessary to create a unique application domain to run our code in. SharePoint has a nasty quirk wherein it insists on using the currently logged in user credentials no matter how many times you tell it otherwise. The fix for this is to create an application domain wrapper for the subsequent code, in effect fooling the SharePoint object model into thinking your web application is actually a console application (see Maurice’s post for a full explanation).
AppDomainSetup objAppDomainSetup = AppDomain.CurrentDomain.SetupInformation;
Evidence objEvidence = AppDomain.CurrentDomain.Evidence;
AppDomain objMyAppDomain = AppDomain.CreateDomain("MyAppDomain", objEvidence, objAppDomainSetup);
Now that we have the proper credentials and domain in place we can move on to the actual SharePoint code. For this very basic example, we first instantiate a new site based on the root portal, then create an SPWeb object for a site so we can get its user and permission collections. Note the ‘CatchAccessDeniedException() method – this prevents authentication dialog boxes from appearing if the login somehow fails.
SPSite site = new SPSite(<portal URL>);
site.CatchAccessDeniedException = false;
SPWeb web = site.AllWebs[<site URL>];
SPPermissionCollection perms = web.Permissions;
SPUserCollection users = web.Users;
Earlier we captured the original user credentials before impersonating the admin account. We can now use that information to compare against the list of site users to see if the currently logged-in user has permissions to access the site. First, we’ll check to see that the user names match:
if (objOriginalUser.Name.ToLower() == user.LoginName.ToLower())
Then we’ll get the security role collection for the site:
SPRoleCollection roles = user.Roles;
Then we loop through the security roles and permission collections to see if the role ID for the current user matches the Member ID in the permissions table for the site. If it does, then we check to see if the user has either the ViewPages right or, if the user is an administrator, the FullMask right (rights are cumulative for Web Designers, Contributors, Readers and all other roles except Administrators, who simply have the ‘FullMask’ permission).
foreach (SPRole role in roles)
{
foreach (SPPermission perm in perms)
{
if (role.ID == perm.Member.ID)
{
if
(role.PermissionMask.ToString().IndexOf("ViewPages") != -1 role.PermissionMask.ToString().IndexOf("FullMask") != -1)
Assuming the user credentials are accepted, you may now execute whatever code you desire with confidence that the user has sufficient permissions to view the specified content. Once the code has finished executing it’s a good idea to destroy the application domain you created and, if you wish, revert back to the original user context.
AppDomain.Unload(objMyAppDomain);
objOriginalUser.Impersonate();
Of all the methods that are available for running code under an administrative context in SharePoint, this combination of impersonation and application domains is the only one that I’ve found works in every situation I’ve tried.
Saturday, March 18, 2006
SharingPoint Update
During this time I’ve been doing A LOT of custom development in .NET – web parts, object model code, server controls, custom lists, the whole nine yards. As most people know I’m not the greatest programmer in the world and only know enough C# to be really dangerous so it’s been quite the learning experience. Here’s a sample of what I’ve been up to:
- Dynamic drop-down portal area navigation server control
- Custom list query/XML rendering administration pages
- Visio design template for SPS/WSS deployments
- Custom site navigation control/web part with security trimming
- Advanced customization best practices document
- Document library event handlers (not started yet)
- List search pages
Wednesday, March 01, 2006
Extreme SharePoint Design: Modifying 'Grouped By' Headers, Part 2
Creating a grouped list in SharePoint is easy – just Modify Settings and Columns, Create View, and select the column(s) to group on; however, getting the grouping to sort and display the way you want isn’t quite so simple. By default, SharePoint sorts alphabetically and there aren’t any options to hide group headers, style them individually, or change the layout.
One of the most common requirements for grouped listings is to create a manual sort mechanism to circumvent the built-in alphabetical sort. This is easily done by adding a new field with a numerical value and assigning each list entry to the appropriate identifier. When a new view is created, the list is first grouped by the sort field and next by whatever content field is appropriate. The problem is that the user sees the sort field in the first group header and there’s no way to turn it off in the list settings.
There are two ways to solve this problem. You can modify the SCHEMA.XML file for the list type and change the CAML to render the groupings the way you want them (read this post to learn how). The issue with this method is that it is a global change; all lists of the same type in the site definition will be changed. Alternatively, you can override the default styles using a web part which hides or alters the appearance of the stock group header on a page by page basis.
Each header level is contained within a table row that inherits the ‘ms-gb’ (first group header) or ‘ms-gb2’ (second group header) style from OWS.CSS. To override these styles, add a Content Editor Web Part (CEWP) to the page, open the Source Editor in the tool pane, and add your own stylesheet entry. For example, to hide the first group header row entirely (dark gray bar in default theme), enter the following:
<style>
.ms-gb{display:none}
</style>
Disable the CEWP’s ‘Display on page’ setting (under ‘Layout’ in the tool pane) to hide the web part, save it and the group header disappears. Because the CEWP is rendered after the default stylesheet references in the site definition page, it overrides the settings in OWS.CSS following the cascading rules of CSS (last reference takes precedence). Technically, the header still exists, users just can’t see it. Use the same method to apply font colors, background images, margins, and any other CSS style properties.
Unfortunately, this method doesn’t work as well for altering the actual text of the header entry (i.e. removing the ‘[Group Name] :’ prefix). You can use JavaScript to achieve this effect by altering the innerHTML property of each header and using the getElementById method, but you’ll have to apply it individually to each header ID and they’re not always the same from page to page. Plus, JavaScript in CEWP’s often causes unpredictable results (such as preventing web parts from being saved or disabling tool bar functions). If you do plan to use JavaScript on a web part page, it’s always best to store the script in a document library as a .js file or as part of an HTML page then call that page using a Page Viewer Web Part.
Monday, February 13, 2006
Extreme SharePoint Design: Dynamic Style Sheets
Both WSS and SPS provide mechanisms for altering the presentation of pages using custom style sheets. While WSS provides a relatively simple template structure for improving the stock look and feel on a site-by-site basis, SPS customizations must be applied on a portal-wide basis (using the custom style sheet field in Site Settings) or for each individual site definition. If you employ user controls in your site definitions to create custom headers, navigation elements, etc. this means that each site definition must reference it's own separate control. This makes code maintenance difficult and time consuming.
But perhaps the most glaring deficiency in employing custom styles is the lack of style continuity in administration pages. No matter how eye-catching your design is, as soon as the user attempts to manage portal content, change settings, create a list, or do one of a hundred other administrative tasks, the styles revert back to the boring out-of-the-box template (this holds true for both WSS sites and SPS portal areas).
In order to overcome this limitation, a method of dynamically switching styles for sites, areas, and administration pages is needed Fortunately, the SharePoint object model provides several functions that can be leveraged to create a style switcher based on site definition, site template, user id, group, or any number of other parameters depending upon your requirements.
To begin with, if you are not already employing user controls in your custom site definitions, you should consider doing so as soon as possible. They are flexible, reusable, and easy to maintain. Perhaps most importantly, they also provide access to server-side scripting which is disabled on standard ASPX pages within SharePoint. Creating a user control is simple - modify a base portal page (such as the default page for a cloned site definition) until you have the desired look and feel, then extract that code into a blank HTML page with all <HTML>, <HEAD> and <BODY> tags removed. Add the proper registrations to the top of the page (see example below) and save it into a centrally-accessible directory (such as /bin or /_layouts/1033) with an .ASCX extension. In the default page, add a page registration for the file and a code reference in the body of the page and you're done. You can now reference the user control from any portal page - changes made within the control will be viewable on all pages which reference it. Now you only one page to maintain instead of dozens.
Here's an example of a user control (header.ascx) that creates a custom portal header:
<%@ Control Language="c#" AutoEventWireup="false" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<table width="100%" border="0" cellpadding="0" cellspacing="0" ID="Table2">
<tr>
<td width="100%" coldiv="4" height="14" class="spsHeader_Banner"></td>
</tr>
<tr>
<td width="40" height="80" align="center" valign="middle" class="spsHeader_Logo"><img src="/_layouts/images/Header_Left.gif"></td>
<td width="100" valign="middle" class="spsHeader_Name"><SPSWC:WebProperty Property="SiteTitle" runat="server" /></td>
<td width="99%" valign="bottom" class="spsHeader_Title">Home</td>
<td width="350" valign="bottom" class="spsHeader_Actions">
<table height="100%" width="100%" border="0" cellpadding="0" cellspacing="0" ID="Table3">
<tr>
<td coldiv="2" height="99%" valign="top" align="right" class="spsHeader_MySite" nowrap ><SPSWC:PageHeader id="PageHeaderID" runat="server" PageContext="SitePage" ShowTitle="false" Mode="LinksOnly" /></td>
</tr>
<tr>
<td width="75" valign="middle" align="right" class="spsHeader_Search">Search</td>
<td valign="middle" width="99%" align="right" class="spsHeader_SearchBox"><SPSWC:RightBodySectionSearchBox id="RightBodySectionSearchBoxID" runat="server" SearchResultPageURL="SEARCH_HOME" FrameType="None" /></td>
</tr>
</table>
</td>
</tr>
<tr>
<td coldiv="4" width="100%">
<div id="spsHeader_NavMenu">
<SPSWC:CategoryNavigationWebPart runat="server" id="HorizontalNavBar" />
</div>
</td>
</tr>
<tr>
<td height="1" coldiv="4" width="100%" class="spsHeader_Separator"></td>
</tr>
</table>
Note the page registrations at the top of the file - these provide access to SharePoint's built-in functions and the object model. On the default page, add a reference to the control like this:
<%@ Register Tagprefix="CUSTOMUC" Tagname="Header" src="/bin/Header.ascx" %>
Then insert the control reference in the body of the page, like so:
<CUSTOMUC:Header id="Header" runat="server"></CUSTOMUC:Header>
Once you have the control in place, you can add code to dynamically change style sheets. For example, let's assume that you have a style sheet for each standard portal definition - custom_sps.css, custom_msite.css, custom_topic.css, custom_news.css, and so on. In order for the header to have a different look for each area, the style sheet reference in the file must change based on which site definition the page being viewed is derived from. To make this happen, add the following code to the top of the control, just after the page registrations:
<%
if (SPControl.GetContextWeb(Context).WebTemplate == "SPS")
{
Response.Write("<link rel='stylesheet' type='text/css' href='/_layouts/" +
System.Threading.Thread.CurrentThread.CurrentUICulture.LCID + "/styles/custom_SPS.css'>");
}
else if (SPControl.GetContextWeb(Context).WebTemplate == "SPSMSITE")
{
Response.Write("<link rel='stylesheet' type='text/css' href='/_layouts/" +
System.Threading.Thread.CurrentThread.CurrentUICulture.LCID + "/styles/custom_msite.css'>");
}
else if (SPControl.GetContextWeb(Context).WebTemplate == "SPSTOPIC")
{
Response.Write("<link rel='stylesheet' type='text/css' href='/_layouts/" +
System.Threading.Thread.CurrentThread.CurrentUICulture.LCID + "/styles/custom_spstopic.css'>");
}
else if (SPControl.GetContextWeb(Context).WebTemplate == "SPSNEWS")
{
Response.Write("<link rel='stylesheet' type='text/css' href='/_layouts/" +
System.Threading.Thread.CurrentThread.CurrentUICulture.LCID + "/styles/custom_news.css'>");
}
else
{
Response.Write("");
}
%>
To add additional site definitions, insert an extra "else if" statement for each definition name (WSS sites use the STS definition). The key to this script is the value of GetContextweb(Context).WebTemplate method. This method returns the site definition name for the page being viewed. For individual WSS site collections, you can also use GetContextWeb(Context).Site.RootWeb method. What makes this method even more useful is that the site definition/root web value persists on administration pages (see Extreme SharePoint Design: Customizing SharePoint Administration Pages for detailed instructions on how to employ user controls on administration pages).
Pages which call the new header will inherit the base styles, the custom styles applied at the portal level, and the definition-specific file referenced in the dynamic style sheet script. Due to the cascading principle of CSS, the last-referenced file takes precedence, insuring that the proper style is displayed. By utlizing dynamic style sheet switching you will create a more consistent user experience and reduce the amount of maintenance required for custom site definitions.
Thursday, January 26, 2006
Extreme SharePoint Design: Customizing SharePoint Administration Pages
Administration pages in SharePoint are often the Achilles heel of a customized portal design – they exist outside of the site definition framework, they don’t inherit templates, and they implement a confusing array of shared code structures. This can create usability issues as users switch from a cohesive design on default and list pages to the default GUI and navigation when trying to create a new list or add an alert.
Depending on your design, customizing administration pages can quickly become a nightmare – if just modifying the header is not enough then you’re in for a bumpy road ahead. Here are a few tips that can help you get from stock to custom without the need for a morphine I.V. drip:
Primary Page Structures
There are 215 .ASPX files in the /layouts/1033 directory. Seventy-eight percent of these fall into one of two primary code structures. For simplicity, we’ll call them the ‘SPS’ style and ‘WSS’ style. The SPS style relies upon a server-control structure to render page content; the WSS style uses more traditional HTML code to display content.
The following code renders the page header in the SPS style:
This is how the same header is retrieved in the WSS style:
The most important difference between the two files is the WSS style’s utilization of the AlternateHeader property. The code first checks to see if an alternate header exists – either PORTALHEADER.ASPX or ALTERNATEHEADER.ASPX – and, if so, retrieves the code from the file and renders it; if not, it renders the code between the if…else blocks. This is a useful mechanism because the alternate header files function the same way as a user control – a central file that can be modified to match the new portal design scheme and is accessed from many pages – but it’s only applicable to the WSS style. How do we apply this same concept to the SPS style?
User Controls
Directly modifying the SPS style pages to replace the header is pretty straightforward – just replace the contents between the SPSWC:PageHeader tags (if you aren’t going to use the SPSWC:CategoryNavigationWebPart control in your code, be sure to leave it in place and comment it out). The problem is that there are 67 of these files so maintaining the customized code can become an issue. The solution is to implement a user control to replace the default code and duplicate the alternate header functionality. But there is a catch (naturally).
Pages in the /layouts/1033 directory can utilize external code files but only if they reside in the same directory. So, unlike site defintion files, for which user controls are normally stored in the virtual server /bin directory, any controls created for administration pages must be stored in /layouts/1033; otherwise, the process is the same. Create the replacement code, save it in an .ASCX file in the /layouts/1033 directory, and reference it from each administration page.
First, remember that when creating a user control you need the correct page registrations for any code contained within the file. The standard registrations are:
<%@ Control Language="c#" AutoEventWireup="false" TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>;
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Register Tagprefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
You’ll also need to register the new user control at the top of each administration page, like this:
<%@ Register Tagprefix="CUSTOMUC" Tagname="spsPortalHeader" src="spsPortalHeader.ascx"%>
You can then call the code from within the page by placing the user control reference between the SPSWC:TopPageSection tags. For example:
<CUSTOMUC:spsPortalHeader id="spsPortalHeader" runat="server"></CUSTOMUC:spsPortalHeader>
Of course, you still have to modify all 67 SPS style pages but by implementing user controls you only have to do this once; after that, you make changes only to the .ASCX file and they will be propagated automatically.
Beyond Headers
Sadly, replacing headers is the easiest part of administration file customization. Beyond that you enter into the realm of painful line-by-line replacement. WSS style pages are a bit easier as you have direct access to the page body code, which can be edited easily enough (user controls can also save you some headaches here), but watch out – the structures change slightly from page to page and there are often hard-coded elements (such as page titles) so be careful how much editing you do. The same can’t be said for SPS style pages – they use nothing but server controls to render code.
The good news on both fronts is that creative manipulation of style sheets can overcome most design challenges, as long as you’re willing to live with the boxy and boring standard white content area. The primary styles in play here are ms-nav, ms-navframe, ms-titlearea, ms-titleareaframe, ms-titlearealine, ms-verticaldots, ms-colspace, ms-pageidi, ms-pageidpt, and ms-pageidta. You’ll also have to deal with the stock images at the top of the left column by moving them around or making them disappear altogether. Assuming you’ve already modified the styles for the navigation frame on the left and the associated menus, you should be well on your way to improving the bland look of those finicky administration pages.
Happy SharePointing!
UPDATE: Need to get rid of that pesky image at the top of the left column in the SPS style? Try Mike Rundle's text-hiding method. Add this to your style sheet:
.ms-pageidi
{ text-align: -100px }
Image be gone! (Note: it's still there, the text-align setting just moves it off the page where it can't be seen)
Tuesday, January 24, 2006
SharePoint UK User Group First Meeting
Nick Swan has done a tremendous job putting together the SharePoint UK User Group. They’ve got a great site with some really good info for UK SharePointers. Their first meeting is being held Monday, February 13th. I know how much work goes into organizing these events so hats off to Nick and his team. If you live anywhere close to Thames Valley Park in Reading you should make every effort to attend (there are also some great pubs nearby so it’s definitely worth the trip).
Go here for all the information.
I do a fair amount of business in the UK and they really have a thriving SharePoint community. It’s great to see them getting support from Microsoft. Unfortunately I’m on a project Stateside all through February or I would be there to support the group. Have a pint of Abbott Ale for me, mates!
Wednesday, January 18, 2006
SharePoint Communities
Lately, there’s been a great deal of debate about forming communities to centralize/aggregate/organize SharePoint-related content. I think every one of us who pontificates on SharePoint has lamented the lack of a distinct community structure at some point. There is no doubt that it’s frustrating, especially for new users, to find SharePoint content, tips, tricks, advice, code, best practices, and the like. There are blogs, aggregators, community sites, MSD2D, newsgroups – the list goes on and on. Now that’s the definition of confusion.
No one can really agree on how best to approach this problem – although we’re all aware that there is, indeed, a problem – but there seem to be three main approaches:
1. Centralization
Proponents of this approach believe that the only way to tame the beast is to bring all of the various resources together under one roof (which usually means THEIR roof) to provide a one-stop-shopping approach. While this initially sounds like an attractive proposition, there are all kinds of issues, like: Who controls the platform? Who determines format? Who maintains the directory? Who has final say on contributions and content? Is somebody profiting disproportionately from it? How are contributors engaged? Who gets the AdSense revenue?
One very important shortcoming of a centralized resource is that a structured framework by its very nature is anathema to creativity. Most community contributors see their sites/blogs as labors of love and means of expression – they don’t want to use somebody else’s template or format or rules. Centralization is their kryptonite – they work under too many restrictions from day to day and free expression of their ideas is a constructive outlet. The overall community may not need yet another WSS site with a fancy template and a bit of original content but the person who built it needs it for their own personal satisfaction and edification. This is a powerful factor that cannot be overlooked (and too often is).
And let’s not be so blind as to think that there is not a profit motive behind many of these efforts; often times somebody, somewhere, wants to get paid (which is neither good nor bad, it’s just reality). There’s a natural level of distrust between the community and the people who run commercial enterprises; they assume, whether right or wrong, that the business owner wants their content for the sole purpose of enriching themselves. In truth, that’s probably a rare occurrence (I can’t think of one example in the SharePoint community where this is absolutely the case), but the perception still exists. This is the true Achilles heel of centralization.
Then, of course, there is Microsoft’s centralization effort, which you would think lends itself most to the profit motive argument but in reality is probably the least likely to support that hypothesis (Microsoft is going to sell lots of CAL’s regardless of what all of us post on the web). There’s lots of distrust here too but for a different reason – people are worried that Microsoft will police their contributions, stifling dissent and revoking it’s favor (like MVP status) for those that don’t toe the line. I don’t know if this will really happen or not but I will admit that it concerns me – how long do you think my Extreme SharePoint Design series of posts would last on an MS sponsored site? They’d never make it past the editors, I would imagine, because they show users how to solve problems or overcome inbuilt limitations that don’t necessarily jive with what MS defines as a ‘supported’ solution.
Having said all that, what are the advantages of centralization? Ease of use, for one, especially for new users. Efficiency as well, in that we would all spend less time searching for what we need to know. I suppose a certain level of consistency would also be achieved along with elimination of duplication if everyone is aware of what other contributors are saying. There’s probably a bit of esprit de corps that would go along with being on the same team and enthusiasm, as we all know, is infectious. And let’s not forget that together we could probably achieve more in one form or fashion than we could each working on our own.
So are the benefits enough to outweigh the disadvantages? I suppose the community at large will decide, or they may choose…
2. Decentralization
This model leaves things just as they are. Lots of different content islands floating around the SharePoint ocean with no direct links between them. Users leverage existing tools like Google, MSN, Yahoo!, Technorati, Feedster, and RSS feed readers/aggregators to find what they’re looking for. Convenient? No. Efficient? Not even close. Effective? Hardly. But it is easy, as no one has to build or maintain a centralized resource, and it doesn’t require users to do anything different than what they already do when looking for other types of information.
This model is already in place and, if it were working well, I imagine that fewer people would be decrying the current state of affairs, so something is rotten in Denmark (my express apologies to our Danish friends for that turn of phrase). I know how confusing it is for new SharePointers – I hear from my clients all the time about how hard it is to find good information. Frankly, there’s just too much out there for them to catalog and synthesize. It’s second nature to us because we live and breathe this stuff every day but they have real jobs with other responsibilities. And I know lots of people want access to more resources because the most popular feature on my web site is my blogroll (I wish it were the description of my service offerings or my contact page but sadly it is not to be).
It’s also very hard to discover new content in a decentralized model. When all the information is pulled instead of pushed you have to know exactly what you’re looking for. Free association is no strong point here – if you’re looking for needles, that’s what you’re going to get, even if a match stick might work better. It’s no secret that you always find what you were looking for yesterday when you’re looking for something else today. By way of example, Heather has one of the best resources on the web for SharePoint customization but people still post questions to the newsgroups that she has long since answered in-depth. I wish more people would use SharePoint BlogSearch but they don’t – it’s mostly a handy tool for me to find postings from other bloggers without wading through Technorati’s endless parade of irrelevant results. And just a few months ago I had a user point me to one of Bil Simser’s posts on automating list metadata from Word documents that I was unaware of – and I read Bil’s blog religiously! Talk about an embarrassing moment; if I don’t know these things off the top of my head, how on earth is Joe SharePoint Administrator supposed to cope? There’s just too much out there for anyone to get a good handle on.
Which leads us to…
3. Affiliation
This is a middle-of-the-road approach that places more importance on the connections between contributors than it does on command and control. A Federalist solution, this model encompasses as many content islands as everyone can build so long as there are good links between the islands, perhaps with a DNS-root-server type of directory listing hosted somewhere. This is in line with the organic growth model of most online communities and eschews any sort of governance other than mutual accountability.
As with any group of city-states, the overall economy of the region is only as good as the trade routes between destinations. In other words, if we can’t all agree on some uniform method of linking to each other and cross-publishing then the whole garment will unravel. We know the ‘web ring’ concept doesn’t really work, so we’ll have to come up with a better method of maintaining the roadways between our islands. And there still has to be a unified directory somewhere, which opens up the same control issues as complete centralization.
This method does seem, at least on the face of it, to blend the best of both the centralized and decentralized approaches. Yes, we all have to agree on some way to keep traffic flowing between us, but it would sure make the task of finding information easier. And contributors would be free to exercise their creative talents. The directory is a problem, if for no other reason than someone is going to have the task of maintaining it, and it will have to contain real or near real-time information. No easy task, to be sure.
So what’s the final solution? I don’t know any more than you do. Personally, I think the Affiliation model works the best but I could be wrong – I know several of my clients would much prefer Centralization because it makes their work lives easier but many community members are staunch adherents to the Decentralization metaphor. The two constituencies seem to be at opposite ends of the spectrum. Judging by the community reaction to any proposition for centralization, I doubt we will see this happen any time soon so it may become the de facto loser, with decentralization winning by default since it requires no additional effort on our part (I sure hope that’s not the reason but we can’t rule out general sloth as a factor). One thing’s for sure – spirited debate on the subject isn’t about to die any time soon!
Tuesday, January 17, 2006
SharePoint BlogSearch Update
I've also added a BlogSearch form to the main page; just go to The eGroup Web Site and you'll see it on the right side of the page. If you'd like to use this form on your web or blog site, copy and paste the following code into your HTML (style references are included so you can add them to your default CSS and change them accordingly):
<form method="get" action="http://www.theegroup.net/search.aspx" target="_top">
<table width="250" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="left" valign="bottom" style="padding-right: 3px"><img src="http://www.theegroup.net/_layouts/images/SPSSearch2.gif"></td>
<td align="left" valign="bottom" style="font-weight: bold; font-family: verdana, arial, helvetica, sans-serif; color: #1B6E89; padding-bottom: 3px; font-size: 8pt;">SharePoint BlogSearch</td>
<td></td>
</tr>
<tr>
<td></td>
<td align="left" valign="bottom" style="font-family: verdana, arial, helvetica, sans-serif; font-size: 8pt"><input type="text" name="k" size="35"/><input type="hidden" name="s" value="SharePoint%20Blogs"/></td>
<td align="left" valign="middle" style="padding-left: 3px"><input type="image" name="Submit" src="http://www.theegroup.net/_layouts/images/icongo02.gif"/></td>
</tr>
</table>
</form>
As always, let me know if a blog is missing that should be on the list or if you would like your blog excluded from the index.
Happy blogsearching!
Thursday, January 12, 2006
SharePoint Forge Opens Its Doors
Many of us have complained in the past that there is no central repository for SharePoint knowledge, code, web parts, techniques, etc. Well, those days are over. Bil Simser's SharePointForge is now open for business.
Go check it out and let Bil know how much you appreciate his hard work.
Wednesday, January 11, 2006
An Open Letter to SharePoint Recruiters
I get a lot of emails from recruiters looking for SharePoint resources. I welcome every message and try my best to connect the people I know with each opportunity that comes my way. One thing that I see over and over again is a job requirement that demands top-notch coding skills alongside advanced design/customization/deployment/project management knowledge. This is like asking the guy at GM who designed your SUV to also be the mechanic - they are two completely separate disciplines and anyone who claims to be good at both is going to be horrible at each one.
When was the last time you saw an architect framing a house and doing the brickwork? Or a graphic designer building a server farm? Or a nurse doing brain surgery?
Software architects and software developers have completely different skill sets. Yes, some skills overlap to a small degree, and knowledge of one discipline can make you better at the other, but to be really good at something you have to focus on that skill to the detriment of all others. I don’t write code not because I can’t but because there are people out there who can program circles around me with their eyes closed - but ask those same superstar developers to manage an end-to-end portal implementation or do all the design and customization work to take a project from mock-up to production and you’ll get that deer-in-the-headlights look. They’ll tell you straight up that’s not what they do, that’s what architects and designers do. And if they don’t then they are a) lying and b) bad developers. Don’t hire them. Period.
So please, to every recruiter out there, stop trying to shoe-horn two bodies into a single position - it won’t work and your client will definitely not be happy with the result. If you get a requisition with this type of ridiculous stipulation it’s your job to tell the hiring manager that he/she needs two experts, not one, and you’ll be happy to go find them both. Let people do what they do best - good personnel placement is about fitting round pegs into round holes not drilling out bigger holes to make room for triangles, squares and octagons.
</rant>