Strongly Typed URLs in SharePoint

So it seems that the current flurry of advancement in our SP framework at work is leading me to write another topical blog post. For the longest time (at least a year), I’d been wanting to improve the way we handle URLs during site collection authoring – I’d go as far as to say the user experience for content authors was even a little hostile. For something as conceptually simple as pointing a web part at a list, the content author workflow would be:

  1. Edit the page on which the web part resides
  2. Encounter a web part property which has the words ‘Site Collection Relative List URL’ in the name.
  3. Call upon jedi training (the product training manual we present to them) and recollect the format of a site collection URL and how a list URL can be obtained.
  4. … Find the list in question, go to its Settings page and snag the list URL (in absolute form) from there.
  5. Take the URL, manually make it site relative, and paste that into the web part property textbox.

So when more than one content author complained about this process, I had the perfect excuse to go off and spend time making it right. And it sure did take time, because what I had to do was fairly obvious and simple in idea, but painful in execution (yay, SharePoint!). I developed an EditorPart (a custom authoring UI for web parts) as well as a custom SP field, both of which present a similar UI which allows content authors to either enter an absolute URL or to select a site collection resource (e.g. a list, web, page, picture etc.) via the OOTB asset picker. These ideas are not new and have been implemented by others in the past with varying degrees of success. But it’s not those I want to write about- I want to write about the infrastructure behind them, the URL classes which back them.

Normally in web development, one is concerned with absolute and server relative URLs and not much else. But in SharePoint land, it often makes sense to have the notion of a site collection relative URL; a URL like ~/MyWeb/Pages/default.aspx which refers to a resource in a site collection and is portable across site collections, regardless of whether they’re root site collections or mounted on a managed path (e.g. /sites/sc). Previously we represented all URLs as strings from the moment they’re born (at the point of user input or in code) until the moment they’re used to accomplish some data access or are reified through rendering. We generally had little use for the cruddy .NET URI class.

As part of the above tasks, I went off and created a bunch of URL classes to represent all URL types and input schemes. As one might expect, this included types like AbsoluteUrl, SiteRelativeUrl and ServerRelativeUrl, but also input schemes like:

  • AbsoluteOrSiteRelativeUrl – for the scenario described above, where the user can enter one or the other.
  • AbsoluteOrServerRelativeUrl – useful for dealing with many SP controls like the publishing image field, which return URLs which are absolute (if external) or server relative (if the user has picked an image in the site collection)

Each is an immutable type which takes a string in its constructor and balks if it’s malformed. The idea is that you create these from string as soon as possible and reify them as late as possible. Having URLs which are strongly rather than stringly typed is advantageous for these reasons:

  • You instantly know what type of URL you’re dealing with – there’s no repeated dodgy detection code which is invoked on magic strings.
  • One can write succinct well-defined conversion methods between different URL types. Furthermore, these methods (as well as your URL utility methods) are simply defined as instance or extension methods on the types, so they’re easy to find.
  • The concept of nullity is unambiguous. E.g. if a SiteRelativeUrl object is null, it’s conceptually null, if it’s not null, it’s conceptually non-null. C.f. a string which represents the same thing. A null string obviously corresponds to a null URL, but what is an empty string? Does it signify the absence of a URL? Or is it a URL which corresponds to the root of the site collection or the root of the current context?
  • Variable names become less ambiguous/confusing/verbose – the C# type keeps track of the URL type rather than the variable name… so rather than an absoluteOrSiteRelativeWebUrl of type string, you have a webUrl of type AbsoluteOrSiteRelativeUrl
  • The type system stops you from attempting nonsensical conversions and assignments.
  • The value of the URL is handled by code. Previously we had defensive code like siteRelativeUrl.EnsureStartsWith(“~”) scattered everywhere. This existed firstly, to correct content authors’ input and coerce it into the expected form, but also because five layers deep in code when passing around magic strings, it wasn’t always clear whether the URL had already been properly defined/sanitised at that point. It’s easy to forget or not know, especially when you consider that one can arrive at a point via multiple code paths which may or may not do their own checking. So it became easier to be defensive absolutely everywhere to be safe. This sort of fuzziness and lack of precision disappears when you use types which automatically verify integrity for you.

Before embarking upon writing these URL classes (and converting our entire sprawling codebase to use them), I was a little skeptical as to how much it might achieve and whether it would buy us enough to justify its existence. I’m generally a minimalist and am not inclined to vomit forth a plethora of sugar-coated OOP classes or impose design patterns which unnecessarily complicate the required mental model of a developer reading my code. But I think the benefits are manifold, and as this re-architecture has only recently come to fruition, I’m interested to see how my fellow devs find it.

About Nathan Pitman

Undergrad software engineer at the University of Auckland
This entry was posted in ASP .NET, C#, SharePoint, Web and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *