This blog has moved to http://jermdavis.wordpress.com/

Saturday 29 March 2014

Moving to Wordpress...

After a few weeks of banging my head against Blogger, trying to write posts with code in them that don't look terrible, I have decided to move to Wordpress. Sorry Blogger, but you're just not cutting the mustard.

All future posts will be made to http://jermdavis.wordpress.com

Friday 21 March 2014

Multiple data source locations in the “Select Associated Content” dialog box

Working on some components for a client’s site recently, it struck me that there were circumstances where it would be helpful to editors to allow the “Select Associated Content” dialog to have multiple options without just showing the whole content tree. Scenarios like having both a global shared content folder and a sub-site specific shared content folder that editors could choose between, for example. Something that looked like this:

image

More than one root item displayed in the selection list allows editors to choose an appropriate location without the option to put their data in places you didn’t want them to…

When I started looking into whether this was possible or not, I initially assumed it would end up being quite a complicated modification – involving modifying this dialog, and the pipelines and commands which are used for this bit of UI. But after a bit of digging through the code, it turns out that it’s actually much simpler than this, since the dialog already supports the idea of multiple roots. What we need to provide is the right user interface to configure the roots and a bit of code to ensure this config is passed through to the dialog. Here’s what I came up with, after a bit of hacking:

So what happens when you click the Associated Content button in the UI? Well looking at the commands configuration for the Page Editor, we find that the UI triggers the command Sitecore.Shell.Applications.WebEdit.Commands.SetDatasource. Digging through that class with Reflector, it seems that the Execute() method gets the client page to call the Run() method. This method does all sorts of stuff, but the interesting bit is that it calls a static method called CreatePipelineArgs() to set up the data for the rest of the pipeline. And this is where the interesting stuff happens:

private static GetRenderingDatasourceArgs CreatePipelineArgs(
ClientPipelineArgs args, Item renderingItem)
{
Item clientContentItem =
WebEditUtil.GetClientContentItem(Client.ContentDatabase);
GetRenderingDatasourceArgs getRenderingDatasourceArgs =
new GetRenderingDatasourceArgs(renderingItem)
{
FallbackDatasourceRoots = new List<item>
{
Client.ContentDatabase.GetRootItem()
},
ContentLanguage = (clientContentItem != null) ?
clientContentItem.Language : null,
ContextItemPath = (clientContentItem != null) ?
clientContentItem.Paths.FullPath : string.Empty,
ShowDialogIfDatasourceSetOnRenderingItem = true
};
LayoutDefinition currentLayoutDefinition =
SetDatasource.GetCurrentLayoutDefinition();
ID clientDeviceId = WebEditUtil.GetClientDeviceId();
string uniqueId = args.Parameters["uniqueId"];
if (currentLayoutDefinition != null && !ID.IsNullOrEmpty(clientDeviceId))
{
RenderingDefinition renderingByUniqueId = currentLayoutDefinition
.GetDevice(clientDeviceId.ToString()).GetRenderingByUniqueId(uniqueId);
if (renderingByUniqueId != null)
{
getRenderingDatasourceArgs.CurrentDatasource =
renderingByUniqueId.Datasource;
}
}
return getRenderingDatasourceArgs;
}

The critical bit for what we’re trying to achieve is where the FallbackDatasourceRoots property is assigned a list of items. If we follow this through the code for the rest of the display of the dialog, this property is used to set the contents of the tree view. So this is the bit of code we need to modify to deal with the Datasource Location field of a sublayout or rendering having multiple items assigned.

Slightly annoyingly, the class defining this method doesn’t make life easy for modifying it. In fact due to the use of static methods, we’re pretty much stuck with the idea that we need to decompile the whole class and copy it into our own codebase in order to modify it. Once we’ve done that, we need some code that can process the Datasource Location field and generate a List<Item> that contains whatever items it finds.

If we assume that the data is going to be formatted using the standards for a multi-select field, then we could write a method something like this:

private static List<item> fetchDatasourceRoots(Item renderingItem)
{
List<item> roots = new List<item>();

string itemIDs = renderingItem.Fields["Datasource Location"].Value;

if (string.IsNullOrWhiteSpace(itemIDs))
{
roots.Add(Client.ContentDatabase.GetRootItem());
return roots;
}

string[] ids = itemIDs.Split('|');

if (ids.Length > 0)
{
foreach (string id in ids)
{
roots.Add(Client.ContentDatabase.GetItem(id));
}
}
else
{
roots.Add(Client.ContentDatabase.GetRootItem());
}

return roots;
}

We take the item that represents the Rendering or Sublayout and we extract the value for the Datasource Location field. If it’s empty then we return the default data – the same data the original code used. If it’s not empty then we split it into individual IDs and add each of the items these represent to the collection we return.

And with that, we can modify the code of the CreatePipelineArgs() to initialise the FallbackDatasourceRoots property using our new method instead of the original code.

Two more things need we need to do for this to work. First, we need Sitecore to use our replacement custom SetDatasource class. That just needs a quick config patch, along the lines of:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<commands>
<command name="webedit:setdatasource">
<patch:attribute name="type">
Testing.CustomSetDataSource, Testing
</patch:attribute>
</command>
</commands>
</sitecore>
</configuration>

The “patch:attribute” element here tells the configuration system to replace the value of the specified attribute of the context element with the new value we provide.

The second thing is that the Datasource Location field of the Rendering and Sublayout templates needs to become a multi-select type of field. The TreeListEx control seems appropriate. That field lives at /sitecore/templates/System/Layout/Sections/Rendering Options/Editor Options/Datasource Location in the content tree, so we can change the type:

image

That will change the type of this field for all the renderings and sublayouts. And with that, you can now set multiple items as the Datasource Location:

image

And now both of those items will show up as roots in the Set Associated Content dialog.

Now, there are a few caveats with this modification as it stands. One is that it’s not compatible with the relative data source locations code from a previous post, because it stores GUIDs in the Datasource Location field rather than a string. It also means that any pre-existing data in the Datasource Location field is now of the wrong type – so you will have to reset any values set before this change. And thirdly, because we’ve had to copy and replace an entire command class any future modifications to this in the Sitecore codebase won’t make it into our solution without us doing it manually. So borrow this with a bit of caution...

Tuesday 18 March 2014

Bonus chatter: Getting the Web Forms for Marketers connection string

I hit an issue recently where it was necessary to get direct access to the Web Forms for Marketers database. The code needed to do some custom processing of previous form submissions in a Sitecore website, and it needed to process so much data that the public APIs for WFfM were too slow to use. Slightly annoyingly, the connection string for WFfM is not added to the the standard .Net <connectionStrings/> config element, but is stored in the custom provider configuration in WFfM’s forms.config file:

<formsDataProvider type="Sitecore.Forms.Data.DataProviders.WFMDataProvider,
Sitecore.Forms.Core">
<param desc="connection string">user id=usr;password=pwd;
Data Source=localhost;Database=WebFormsForMarketers</param>
</formsDataProvider>

Not ideal. This is compounded by the fact that the data provider that is loaded via this configuration does not expose the connection string either. So there’s no easy way to extract it via the provider model.

There are two ways to tackle this. The first that springs to mind is that since the forms.config file is just XML, it wouldn’t be hard to write some code that loads and parses it to get the data. The alternative is to use a bit of Reflection on the WFfM provider objects, as that lets us get access to non-public data inside the provider classes.

Intuitively, the XML solution sounds like it’ll make heavier use of CPU and memory to me. It’s a fairly big config file to load just to parse out a single string. So I decided to give the Reflection approach a try and see how that worked out.

NOTE: Whenever you write code that uses Reflection to get at things that are not part of the public APIs of someone else’s code you’re taking  a risk. Inherently this approach makes your code more fragile. There’s no guarantee that the internals you’re accessing will still be there in the next release – and that will lead to errors. Also since there’s a provider model here there’s no guarantee that the internals you’re trying to access will be inside any concrete classes that are configured. Reflection is a useful tool, but don’t forget the risk you’re taking. Please remember that if you choose to borrow from this code.

And with that public service announcement out of the way, lets get to some code…
(As usual, you’d want more error handling in production code)

The public entry point for accessing data from Web Forms for Markerters APIs is the Sitecore.Forms.Data.DataManager class. This is a static class which provides a facade on top of the internal logic for finding the real forms data API objects. When it’s initialised, this class creates an instance of the protected Sitecore.Forms.Data.DataProvider class. And in turn, that type uses Sitecore’s configuration factory classes to get an instance of the concrete class for accessing the WFfM data.

So the first task is to get access to this DataProvider class. There are two ways to go here: Use reflection to create an instance of it, or use Reflection to extract the instance created by Sitecore itself. I decided to go with the second one.

var managerType = typeof(Sitecore.Forms.Data.DataManager);
var managerField = managerType.GetField("provider",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic);
var provider = managerField.GetValue(managerType)
as Sitecore.Forms.Data.DataProvider;

The code gets the System.Type for the manager class, then uses that type to extract a reference to the private static field “provider”, which holds a reference to the object we want. Note that since this is a static type, you call GetValue() on the type object itself, rather than on an instance of the type.

Now, looking at the innards of that DataProvider class, it is a bit more helpful. Once it’s loaded the concrete data provider class from configuration, it exposes it in a public property called “InnerProvider”. So we can take that and cast it to its real type:

var innerProvider = provider.InnerProvider
as Sitecore.Forms.Data.DataProviders.WFMDataProvider;

Now note that this is one of the places where this could all go to pot in real life. What if the instance of WFfM you run this code against is using the Oracle data provider, or some other provider type? Well the answer is probably a big messy crash because the cast above will return null. So it makes sense to check if you got the right type back or not. If the cast above returns null then you know this instance of WFfM isn’t configured the way you expect. What you choose to do about that is dependent on what you need this code to do – you could fail gracefully, throw an “unexpected configuration” exception or use an alternative path through the rest of this code that knows about other providers.

But for the sake of blog post simplicity, lets assume that cast worked…

The internals of the WFMDataProvider class include a private field called “shortConnection” which stores the connection string value extracted from the WFfM configuration. So that’s what we need to try and extract:

var innerProviderType = innerProvider.GetType();
var field = innerProviderType.GetField("shortConnection",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
string connectionString = field.GetValue(innerProvider) as string;

And magically, now we have a connection string that can be used with standard SQL connections.

As an aside, having written the code above it turns out that if you want to take a more supportable approach to having access to this connection data and you are able to change the forms.config setup slightly, then WFfM can be configured to get its connection string from the <connectionStrings/> element.

Monday 17 March 2014

Delete aliases along with their items

Last week I looked at how to visualise aliases in the Content Editor, based on a requirement from one of my clients. The second part of the work I was considering here was how you can automatically remove any aliases related to an item when you remove that item. What I wanted to achieve was having the system prompt you to ask if you want the aliases removed whenever you delete an item that has aliases attached. Something like:

image

I bit of digging through docs and Reflector shows that when you delete an item via the UI, Sitecore runs the “uiDeleteItems” pipeline. This performs the series of steps required to verity and then perform the deletion:

<uiDeleteItems>
<processor mode="on" method="CheckPermissions" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="Confirm" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="CheckTemplateLinks" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="CheckCloneLinks" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="CheckLinks" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="CheckLanguage" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="UncloneItems" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="Execute" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
<processor mode="on" method="PostAction" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel"/>
</uiDeleteItems>

Looking at the code behind these components, it seems sensible that if we’re going to extend this pipeline, the “are you sure you want to delete aliases” component needs to go between the “Confirm” step (which prompts to confirm whether you really want to delete the item) and the “CheckTemplateLinks” step. That would allow us to prompt before Sitecore starts the process of deletion. Now as you can probably tell from the config above, the original Sitecore code breaks this sort of “confirm” and “perform” behaviour into multiple pipeline steps. But for the purposes of this example we’ll stick to just the one for simplicity.

So, as we’ve seen with other customisations, we need to start with a basic extension class which will provide our new pipeline step. For this particular pipeline, that class looks like:

public class DeleteExtensions : ItemOperation
{
public void CheckAndDeleteAliases(ClientPipelineArgs args)
{
}
}

and it gets configured with a configuration patch like so:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<processors>
<uiDeleteItems>
<processor patch:after="*[@method='Confirm']" mode="on"
type="Testing.DeleteExtensions ,Testing"
method="CheckAndDeleteAliases"/>
</uiDeleteItems>
</processors>
</sitecore>
</configuration>

Confirming an operation and then optionally performing it in a pipeline requires  pattern of code that we saw in the previous post about confirming commands in the Sitecore UI – the code needs a two step approach where initially we present the UI and the the second time the code is run we detect the results from the UI dialog and act on them. So we need a clause to detect the user saying “no” to alias removal, a clause to detect the user saying “yes” and the code to generate the confirmation dialog. Now interestingly when we have multiple dialogs in a pipeline it turns out we can’t make use of args.IsPostBack directly because it will already be true due to the previous dialog. But the args.Result property is correct when our code runs. Hence we can work around this issue by structuring the code a bit differently here:

    public class DeleteItems : ItemOperation
{
private static Sitecore.Data.ID aliasID =
new Sitecore.Data.ID("{54BCFFB7-8F46-4948-AE74-DA5B6B5AFA86}");

public void CheckAndDeleteAliases(ClientPipelineArgs args)
{
Assert.ArgumentNotNull(args, "args");

//
// Check for postback data from our dialog
//

// User clicked no - abort the whole deletion
if (args.Result == "no")
{
args.AbortPipeline();
return;
}

// Both further steps require these bits of context data
ListString items = new ListString(args.Parameters["items"], '|');
Database db = getDatabase(args);

// User clicked yes - ok to delete
if (args.Result == "yes")
{
args.Result = string.Empty;
removeAliases(items, db);
return;
}

//
// If we're not handling a postback, check for aliases and present UI
//

// Count the aliases for any items we have as parameters
int aliases = countAliases(items, db);

// If we got any aliases, ask the UI to show a confirm dialog for us
if (aliases > 0)
{
string message;

if(items.Count == 1)
{
Item item = db.GetItem(items[0]);

message = string.Format(
"The item \"{0}\" has {1} alias{2} which will also be {3}." +
" Are you sure?",
item.DisplayName,
aliases,
aliases != 1 ? "es" : string.Empty,
Settings.RecycleBinActive ? "recycled" : "deleted"
);
}
else
{
message = string.Format(
"These {0} items have {1} alias{2} which will also be {3}." +
" Are you sure?",
items.Count,
aliases,
aliases != 1 ? "es" : string.Empty,
Settings.RecycleBinActive ? "recycled" : "deleted"
);
}

Context.ClientPage.ClientResponse.Confirm(message);
args.WaitForPostBack();
}
}

private Database getDatabase(ClientPipelineArgs args)
{
Assert.ArgumentNotNull(args, "args");
Database database = Factory.GetDatabase(args.Parameters["database"]);
Assert.IsNotNull(database, typeof(Database), "Name: {0}", new object[]
{
args.Parameters["database"]
});
return Assert.ResultNotNull<database>(database);
}

private int countAliases(ListString items, Database db)
{
int aliases = 0;

try
{
using (new TaskContext("DeleteItems pipeline - count aliases"))
{
using (new SecurityDisabler())
{
foreach (string item in items)
{
Item itm = db.GetItem(item);
if (itm != null)
{
// Count this one
aliases += countAliases(itm);

// And process any descendent items too
foreach(var descendantItm in itm.Axes.GetDescendants())
{
aliases += countAliases(descendantItm);
}
}
}
}
}
}
catch (Exception ex)
{
Log.Error("Error while counting aliases for items", ex, this);
HttpUnhandledException ex2 =
new HttpUnhandledException(ex.Message, ex);
string htmlErrorMessage = ex2.GetHtmlErrorMessage();
UrlString urlString =
new UrlString("/sitecore/shell/controls/error.htm");
Context.ClientPage.ClientResponse.ShowModalDialog(urlString.ToString(),
htmlErrorMessage);
}

return aliases;
}

private int countAliases(Item item)
{
int aliases = 0;

try
{
aliases = Sitecore.Globals.LinkDatabase.GetReferrers(item)
.Select(l => l.GetSourceItem())
.Where(s => s.TemplateID == aliasID)
.Count();
}
catch (Exception)
{
// this should always succeed - exceptions seem to come
// if link database is out of date, so they can be discarded
}

return aliases;
}
}

So the first time the code runs we count the aliases that might need deletion. This uses a two step process because the parameters for the pipeline give us a list of strings representing the items. So the code goes through those strings, loads each item in turn and counts up the aliases for each one (using a similar query to the one we used in the Alias gutter rendering). One interesting question here is what security context to do the counting with. Ideally we’d do it as the current user – but what happens if the user didn’t create the Alias? For the purposes of this demo code I’m just making use of SecurityDisabler here – but in production code it would be sensible to work out the correct security context to do this under. Though the right answer to that question may well depend on the business rules for your particular site.

If we get more than one alias across all the items we render a confirmation dialog. There’s a bit of code to format the message correctly – depending on whether we have one or more aliases to delete and whether Sitecore is configured to use the Recycle Bin or not. Once we’ve shown the message box we call args.WaitForPostback() to tell the UI that we need to wait for a response.

When the code gets called a second time we check the results. If the result says the user doesn’t want to continue the deletion then we abort the pipeline with a call to args.AbortPipeline(). If the result says that the user says yes to the deletion then we remove the aliases. This code follows a similar pattern to the alias counting code:

    private void removeAliases(ListString items, Database db)
{
try
{
using (new TaskContext("DeleteItems pipeline - remove aliases"))
{
using (new SecurityDisabler())
{
foreach (string item in items)
{
Item itm = db.GetItem(item);
if (itm != null)
{
// process this item
removeAliases(itm);

// And process any descendent items too
foreach (var descendantItm in itm.Axes.GetDescendants())
{
removeAliases(descendantItm);
}
}
}
}
}
}
catch (Exception ex)
{
Log.Error("Error while removing aliases for items", ex, this);
HttpUnhandledException ex2 =
new HttpUnhandledException(ex.Message, ex);
string htmlErrorMessage = ex2.GetHtmlErrorMessage();
UrlString urlString =
new UrlString("/sitecore/shell/controls/error.htm");
Context.ClientPage.ClientResponse.ShowModalDialog(urlString.ToString(),
htmlErrorMessage);
}
}

private void removeAliases(Item item)
{
var aliases = Sitecore.Globals.LinkDatabase.GetReferrers(item)
.Select(l => l.GetSourceItem())
.Where(s => s.TemplateID == aliasID);

foreach (var alias in aliases)
{
if (Settings.RecycleBinActive)
{
Log.Audit(this, "Recycle alias {0} of item {1}",
AuditFormatter.FormatItem(alias),
AuditFormatter.FormatItem(item));
alias.Recycle();
}
else
{
Log.Audit(this, "Delete alias {0} of item: {1}",
AuditFormatter.FormatItem(alias),
AuditFormatter.FormatItem(item));
alias.Delete();
}
}
}

However here, instead of counting the aliases, this code removes them. As mentioned above, this code pays attention to the configuration for whether Sitecore uses the Recycle Bin approach to deletion or not, in the same way the original item deletion code in Sitecore does.

Testing this gives us the dialog box shown in the image at the top of the page, and if we approve the deletion, removes the appropriate aliases. Job’s a good ‘un.

Now whenever I come across blocks of code as similar as the removeAliases(ListString items, Database db) and countAliases(ListString items, Database db) methods I start thinking about how to reduce that down to one method. But I’ll save that for a future discussion...

Monday 10 March 2014

Visualising Aliases

Recently a client I work for came to me with an interesting question. Their Sitecore-based website makes heavy use of Aliases to set up shortened URLs for publicity purposes, but they were finding it difficult to manage the large number of aliases they were creating. Their key issues were remembering which pages had aliases defined, and remembering to delete aliases when they removed the pages they were related to.

Sitecore provides a helpful dialog for managing aliases, in the Presentation ribbon tab:

image

However you still have to remember to click on it – and that’s one of the things the client was hoping for help with.

Having had a bit of a think about what could be done here, I came up with two ideas – visualising which items have aliases defined, and automating that deletion. Lets have a look at visualisation first.

Having done a bit of reading around, it turns out that you are able to easily customise the “gutter” in the Sitecore Content Editor. This is the left hand column of the content tree. Out of the box, right-clicking the gutter gives you a series of optional information icons to enable:

image

When you select one of these options, the system will render extra icons in the gutter for any item which meets the criteria defined by this custom gutter control – so lets see what’s involved in adding a gutter rendering to show when content items have aliases defined.

Custom gutter renderings are classes based on the Sitecore.Shell.Applications.ContentEditor.Gutters.GutterRenderer class. All you have to provide is an implementation for the GetIconDescriptor() method:

public class AliasGutterRenderer : GutterRenderer
{
protected override GutterIconDescriptor
GetIconDescriptor(Sitecore.Data.Items.Item item)
{
return null;
}
}

If we want to visualise Aliases, we need to write some sort of query to detect if the current item has any Aliases attached to it. There are a couple of ways you can go about this. One way would be to write a query for children of the /sitecore/system/Aliases item, and look for references to the current item’s ID in the Linked Item field. But Sitecore can make our life easier than this – since it maintains something called the Link Database for us. Inside the Sitecore database, it maintains references between items. Whenever you fill in a field which stores a link between two or more items this relationship is stored in the Link Database to make it easy to query these relationships later. You can see this data yourself when you click the Links button in the Navigate tab of the ribbon:

image

You’ll notice that this example page has lots of links in the database table – it references things like workflow, templates, sublayouts etc. But it also shows that it is referred to by an Alias item. And that shows us that we can use the Links Database to do the query we need.

So we can write a bit of code to count the number of Aliases related to the current item with a simple Linq query:

public class AliasGutterRenderer : GutterRenderer
{
private static Sitecore.Data.ID aliasID =
new Sitecore.Data.ID("{54BCFFB7-8F46-4948-AE74-DA5B6B5AFA86}");

protected override GutterIconDescriptor
GetIconDescriptor(Sitecore.Data.Items.Item item)
{
int aliases = Sitecore.Globals.LinkDatabase.GetReferrers(item)
.Select(l => l.GetSourceItem())
.Where(s => s.TemplateID == aliasID)
.Count();

return null;
}
}

Every time Sitecore processes an item in the content tree it passes that item into our method – and we count the Aliases. The GetReferrers() method here fetches the list of items which refer to the item in question. We then use a select clause to change the data we get back from IDs into items. And then we filter those items by Template ID to ensure we have a list of only referring Aliases. Finally calling Count() tells us how many items matched our criteria.

Having done a bit of testing, it turns out there are some scenarios where the data in the Link Database can be outdated. And that can cause this query to fail in certain circumstances. The most common being after the deployment of a package which affects the items and their aliases. This issue is resolved with the “Rebuild Link Database” option in the Control Panel – but to prevent issues, for demo purposes we'll catch any exceptions thrown by this query. In real-world code we should do somehting like log errors or generate a custom icon that can trigger the link database rebuild if necessary – but for the purposes of this example we’ll just discard the error.

The next job is generating the custom icon to display – and this is where the return type of the GetIconDescriptor() method comes  in. The return type of the method is the GutterIconDescriptor class – and we fill in and return an instance of this to tell the UI what to render. It’s simple enough to fill in – you can provide a path to an icon, a tooltip string and an on-click event to trigger. And when you return this data, the UI renders it in the gutter.

Putting all that together gives us:

public class AliasGutterRenderer : GutterRenderer
{
private static Sitecore.Data.ID aliasID =
new Sitecore.Data.ID("{54BCFFB7-8F46-4948-AE74-DA5B6B5AFA86}");

protected override GutterIconDescriptor
GetIconDescriptor(Sitecore.Data.Items.Item item)
{
GutterIconDescriptor gid = null;
int aliases = 0;

try
{
aliases = Sitecore.Globals.LinkDatabase.GetReferrers(item)
.Select(l => l.GetSourceItem())
.Where(s => s.TemplateID == aliasID)
.Count();
}
catch (Exception)
{
// this should always succeed - exceptions seem to come
// if link database is out of date
// In production code we'd do something cleverer than
// this and try to prevent rather than catch the exception
}

if (aliases > 0)
{
gid = new GutterIconDescriptor();
gid.Icon = "Network/32x32/spy.png";
gid.Tooltip = string.Format("This item has {0} alias{1}",
aliases, aliases > 1 ? "es" : "");
gid.Click = "item:setaliases(id=" + item.ID + ")";
}

return gid;
}
}

Here we’ve set the gutter icon to be the same as the icon used by the Aliases button in the ribbon for consistency. And we’ve set the tooltip to show how many aliases the current item has. Finally, the Click event has been set to the same command used by the Aliases button in the ribbon – a call to the “item:setaliases” command, passing the ID of the current item.

So there’s the code – what do we need to do to make this custom gutter rendering available for users? The data for configuring the Content Editor UI lives in the Core database, under /sitecore/content/Applications/Content Editor/Gutters and configuring a new Gutter Renderer is as simple as adding a new item here based on the “Gutter Renderer” template:

image

The “Header” field here contains the text displayed in the right-click context menu we saw back at the beginning of this post. The “Type” field contains the fully qualified .Net type descriptor for the custom rendering class we defined. As usual for configuring extension types, this is formatted as “<fully qualified type name>, <assembly name>”.

And once you have configured that for your custom class, you can go back to the Master database and enable the new renderer by right-clicking the gutter:

image

Our new custom gutter render is now visible, and its selected state will be remembered between user sessions. Once it’s selected, we start to see the alias icon in the gutter for any item which has Aliases defined:

image

And clicking the icon will launch the Aliases dialog.

Next week we’ll have a look at how we might remove Aliases automatically when the item they refer to is deleted.

Wednesday 5 March 2014

Bonus chatter: New ways to browse the .Net Framework source

For a while now it’s been possible to configure Visual Studio to allow you to step into the publicly available source code for the .Net Framework, but a post on Channel 9 today provides information on some new ways to get at this fascinating information MS are releasing.

The one that caught my attention is that MS have released an online browsing tool to allow you to look through the public source in your browser. Perfect for those “why does it do that?” moments you hit every so often when developing.

Take a look for yourself at http://referencesource.microsoft.com/

There’s lots of information about all the features they’re releasing, plans for the future and the ways it can be used on the .Net blog at http://blogs.msdn.com/b/dotnet/archive/2014/02/24/a-new-look-for-net-reference-source.aspx

I’m off for a read…

Tuesday 4 March 2014

Bonus chatter: My favourite proposed feature for C# v6

Having been away on holiday recently, I’ve been doing a bit of catching up on the backlog of RSS-based reading that a week out of the office has generated. And one item stuck out at me as particularly interesting: some of the new features being discussed for the v6 release of the C# compiler. I came across a couple of articles discussing presentations given by Mads Torgersen (the C# Programme Manager) at the end of last year discussing some interesting feature proposals

One thing stuck out at me as being particularly useful to the code I find myself dealing with in Sitecore solutions – something described as “Monadic Null Checking”. A rather technical name for a simple but useful syntax improvement.

When you’re using Model classes for your Sitecore templates, via a mapping tool like Glass, you often find yourself creating models that might look a bit like:

public class ExampleModel
{
    public virtual string Title { get; set; }
    public virtual Image SummaryImage { get; set; }
}

The mapping framework can fill this class in using the data from Sitecore Items – but what happens when that Image field defined here is not filled in by your content editors? Up until now you’ve generally ended up writing code that’s looked a bit like this:

public void DoSomething(ExampleModel model)
{
    if (model.SummaryImage != null)
    {
        // do stuff with the data. For example:
        myImageControl.AlternatText = model.SummaryImage.Alt;
    }
}

You end up littering your code with if() clauses to make sure that the properties of the object are not null before you make use of them. And it’s a common QA issue to find that when you test new code with the sort of edge-case data which content editors sometimes create, you get Null Reference exceptions in places where these guard clauses have been forgotten.

But in the future this could be simpler with a new approach to null-checking. The new “?.” operator is translated by the compiler into exactly the sort of null test we’ve been writing above. So our code becomes something like:

public void DoSomething(ExampleModel model)
{
    // do stuff with the data. For example:
    myImageControl.AlternatText = model.SummaryImage?.Alt;
}

If the value of SummaryImage is null, then the compiler never references the Alt child property – hence preventing the Null Reference issue without the extra code.

While you still have to remember to use this alternative syntax, it’s much shorter and (to me at least) the shorter code is easier to read and understand. Especially where you get into more complex scenarios with nested objects. And it's much less hassle to remember to type "?." every time you access a property than it is to write the whole guard.