I love passing the current SharePoint page's URL around, and using that information to get access the underlying SharePoint structure and supporting data objects. The most common scenario is this: you give me a page's URL, and I'll give you the associated SPListItem- without the overhead of using the SharePoint Publishing API. Or give me URL of an SPFolder that's nested beneath several other layers of folders, and I'll return the containing SPList. In any case, the current page's URL (or, any page's URL, for that matter) can give us a great deal of context.
Incidentally, the "U" in "URL" stands for universal. What this means is that, ideally, whichever resource is pointed to by a URL can always be depended upon to remain at that location. Indeed, a URL can be thought of as an identifier. This paradigm is what gave rise to REST-based technologies. Of course, the page can be deleted, the server be defenestrated, or the Internet can simply explode, leaving you with broken URLs, among other problems.
However, as far as dealing with problems that are within our control go, especially in SharePoint where URLs are very meaningful, I am comfortable treating URLs as Ids. Internally, will I always use the guid UniqueID (for an SPListItem) or ID (for an SPSite, SPWeb, or SPList) properties while filtering, slicing, and dicing my way though the SharePoint API? You betcha.
Will I EVER match on titles, names, or other string properties? Well, yeah, sometimes you just have to, but I always wash my hands immediately after doing so.
But as far as URLs go, we have a bit of a contradiction. I just said that they are valid identifiers. Then I said that I don't like matching on strings. What gives? Is a URL (especially when used to construct a Uri object) behaving like an Id or a string? And if it's a string, do we need to therefore worry about other string matching challenges, such as casing, whitespace trimming, URL special character encoding, etc?
Well, it depends on what you do with it. If you take the current request's URL and start doing substrings and character indexes on it, well, you're going to get yourself into trouble. But you are also asking for it if you iterate entire sub sites looking for an SPListItem that matches the given URL - this time in terms of expensive API calls rather than sketchy string manipulation.
At first, I went with the cleanest approach possible: new up a Uri with the URL, and use the properties on the this object to discern the current SharePoint site, web, list, and item. However, like I said, SharePoint URLs are very meaningful, in as much as they always follow the same pattern:
http[s]://site/web/[sub web... /...sub web/]list[/folder... /...folder]/page.aspx
Where the Uri idea fails is when we have (as denoted [poorly] above with brackets) nested webs and folders. I haven't dug through Microsoft.SharePoint.Utilities to see if there's something in there that does what I'm about to show you, but obviously the Uri object can't be expected to understand SharePoint site hierarchies.
So I wrote the algorithm myself as an extension method on a string. (Extending Uri might have been cleaner, but then I'm always instantiating these objects with the current page's URL just to turn around and call ToString on them.) You feed it four Guids: the site id, and three out ids to store the URL's web, list, and item. (I didn't implement a version of this to give us folder Ids, simply because I never needed them.)
And here we go:
There are some things to point out:
- Line 12: The reason I only wrap the instantiation of the SPSite object in the SPSecurity.RunWithElevatedPrivileges delegate (and not the entire method) is to ensure that the impersonation will work when this code is referenced in a SharePoint web service called from a Silverlight client. Since Silverlight calls are anonymous and SharePoint is NTLM, the WCF magic connecting the two can't quite get the context correct when running inside the delegate; any call to SPPersistedObject.Update will fail with a security error. So, when you're in this situation, make sure to grab what you need from SPContext first, then elevate to new up your SPSite. This will ensure your code will run and update in an elevated context, despite the call being ultimately anonymous.
- Line 26: SPWebCollection does not support Linq, so I found come cool extension methods to make this iteration easier here.
- Lines 57, 64, and 65: These are simple helper methods I created - nothing fancy.
- Line 67: This method finishes the job by "smartly" parsing out the URL to get the SPListItem's guid. Here is that method:
Why go through all this extra lifting? Simple: anything to avoid for-eaching through list items. Notice Line 5. I pass the same SPQuery object to reach recursive depth and update the folder property to the current folder. This way, my "query" is only at the current depth, and I avoid expensive iterating and kludgy string matching.
So next time you find yourself mindlessly searching your site for a given URL to, for example, pull the metadata for a page, consider using this method. Also, like I said, whenever you start performing string manipulation operations on a URL, something inside you should feel wrong; it should be like the feeling you have on the way to a party when you suddenly break into a cold sweat and wonder if you left your oven on.