Late to the Post-Step party…

I was creating a quick package to transfer some content between Sitecore instances the other day, and happened to scroll down to the bottom of the Metadata page in Package Designer. Not for any real reason – just some over-enthusiastic scroll-wheel action. But when I looked at the dialog, it struck me that there were two fields here I’d never paid any attention to before:

PackageDesigner

What are these System fields for Post Step and Attributes? I made full use of my 15 years experience as a software developer and Googled it… Turns out other than a brief reference at the end of Sitecore’s Package Designer Admin Guide document, there’s not much information about. And that piqued my interest.

According to the documents, the “Post Step” field can optionally contain a .Net type description in the usual “Namespace.TypeName,AssemblyName” form. When your package has been installed, the Sitecore framework will try to create an instance of your class and call it, in order to do some post-installation work for you. For this to work you need to either implement the Sitecore.Install.Framework.IPostStep interface or just have a method in your class with the signature public void RunPostStep(). So for a trivial example you could stick the following code into your site binary:

namespace Testing
{
    public class PackagePostStep : Sitecore.Install.Framework.IPostStep
    {
        public void Run(Sitecore.Install.Framework.ITaskOutput output, System.Collections.Specialized.NameValueCollection metaData)
        {
            output.Alert("My step ran!");
        }
    }
}

And set the Post Step field to contain “Testing.PackagePostStep,Testing“. And when you install this package, you see:

Dialog

Looking at the definition of the ITaskOutput interface, it looks like there are a few common scenarios that Sitecore were expecting us to make use of here:

namespace Sitecore.Install.Framework
{
	public interface ITaskOutput
	{
		void Execute(ThreadStart callback);
		object Execute(Callback callback);
		object RunPipeline(PipelineCallback callback);
		void Alert(string message);
		string Confirm(string message);
	}
}

We can show some UI, we can run some code or we can run a pipeline.

The second of the two fields, “Custom Attributes” takes a set of name and value pairs. It lets you pass settings through to your code if you use the IPostStep interface. They are accessed via the metaData parameter of your Run() method. If you fill in some parameters like so:

Dialog

You can access them via

string val = metaData["Attributes"];

and the value you get passed back is:

attr1=One|attr2=Two|

Hence it needs parsing to be useful…

So looking at this got me thinking about scenarios where Post Steps might be of help in my projects. Here are a few ideas that came to mind. I’ll bet there are loads more, and (as the title of this suggests) I’m probably a bit late to the party in discovering this feature. But maybe these ideas might be of use in your projects too?

The package needs to ask the user a question and do something with the response

Looking at the ITaskOutput interface above, my immediate thought was that this must be how Web Forms for Marketers asks you which placeholders forms should be allowed in. If you’ve ever installed WFfM you’ll have noticed how after the package is installed, it shows a custom dialog which asks you to pick the valid placeholders for forms. But it turns out it’s doing a bit more than this under the surface.

Since package files are just Zip files, you can browse the “installer” folder inside the package and find a text file called “project”. This contains the XML definition of the package. And that includes a <PostStep> element which points to Sitecore.Form.Core.Configuration.Installation, Sitecore.Forms.Core. Via your favourite decompiler, you can see that this:

public void Run(ITaskOutput output, NameValueCollection metaData)
{
	SecurityPostAction securityPostAction = new SecurityPostAction();
	securityPostAction.Run(output, metaData);
	JobContext.SendMessage("forms:selectplaceholders");
}

So it’s running a bit of security-related code (which creates the sitecore\Sitecore Client Forms Author role for you, and then it’s triggering a command. From the config include in the package, that command triggers the class Sitecore.Forms.Core.Commands.RestrinctingPlaceholders. (note the plural of Forms – I got a bit confused initially because the Core DLL for WFfM includes both the Sitecore.Form and Sitecore.Forms namespaces) And that bit of code looks very much like the sort of “command displays some UI” implementation patterns we’ve discussed in earlier posts:

protected void Run(ClientPipelineArgs args)
{
	Item item = StaticSettings.ContextDatabase.GetItem(IDs.SettingsRoot);
	Assert.ArgumentNotNull(item, "item");
	if (args.IsPostBack)
	{
		if (args.HasResult)
		{
			string value = RestrinctingPlaceholders.GetValue();
			item.Editing.BeginEdit();
			item.Fields[FormIDs.ModuleSettingsID].Value = args.Result;
			item.Editing.EndEdit();
			this.UpdateAllowedRenderings(value, args.Result);
			Log.Audit(this, "Set the following restricting placeholders: {0}", new string[]
			{
				AuditFormatter.FormatItem(item)
			});
		}
		if (args.Properties["postAction"] != null)
		{
			new Installation().ChooseSQLiteVersionDll();
			return;
		}
	}
	else
	{
		UrlString urlString = new UrlString(UIUtil.GetUri("control:Forms.CustomizeTreeListDialog"));
		UrlHandle urlHandle = new UrlHandle();
		urlHandle["value"] = RestrinctingPlaceholders.GetValue();
		urlHandle["source"] = Constants.RestrinctingPlaceholders;
		urlHandle["language"] = item.Language.Name;
		urlHandle["includetemplatesforselection"] = "Placeholder";
		urlHandle["includetemplatesfordisplay"] = "Folder,Placeholder";
		urlHandle["includeitemsfordisplay"] = string.Empty;
		urlHandle["excludetemplatesforselection"] = "Folder";
		urlHandle["icon"] = "Business/32x32/table_selection_block.png";
		urlHandle["title"] = ResourceManager.Localize("RESTRINCTING_PLACEHOLDERS");
		urlHandle["text"] = ResourceManager.Localize("RESTRINCTING_PLACEHOLDERS_TEXT");
		urlHandle.Add(urlString);
		SheerResponse.ShowModalDialog(urlString.ToString(), "800px", "500px", string.Empty, true);
		args.WaitForPostBack();
	}
}

If it’s a postback the code is handling the results it was handed, and using them to update the placeholder settings values. And if it’s not a postback it’s setting up a bit of custom UI and then using the Sheer framework to display it

So with sufficient time and effort we should be able to do something similar…

I don’t want my users to forget to publish my new template

It seems like a bit of a trivial issue, but have you ever hit this scenario before: You create an update package for a client’s site which includes some extra pages and the modified or added templates needed for them. The client get focussed on writing the appropriate copy, and eventually publishes just the pages that they have been editing – never realising that they needed to publish the templates too. I come across the scenario before where editors won’t use “Publish Site” because they’re never quite sure if there are some non-workflowed changes that someone else is working on which should not go live yet…

So how about if we could trigger a publish for just our added/changed templates as part of the package in order to prevent this issue? Well the basic code is pretty simple:

    public class PackagePostStep : Sitecore.Install.Framework.IPostStep
    {
        public void Run(Sitecore.Install.Framework.ITaskOutput output, System.Collections.Specialized.NameValueCollection metaData)
        {
            Database master = Sitecore.Configuration.Factory.GetDatabase("master");

            Item myTemplate = master.GetItem("/sitecore/templates/Sample/Sample Item");

            Database[] targets = new Database[] { Sitecore.Configuration.Factory.GetDatabase("web") };
            Language[] languages = new Language[] { LanguageManager.GetLanguage("en") };

            Sitecore.Publishing.PublishManager.PublishItem(myTemplate, targets, languages, true, false);
        }
    }

But watch out for the fact that your post step code runs with the Core database as its context. If you want to work on items in Master, you need to remember to fetch that database explicitly.

This update needs a rebuild of the search indexes

If you’re automatically publishing stuff, it stands to reason that you might want to automatically update a search index? Particularly if you’re dealing with changes to templates, I’ve found in the past that it can be necessary to do a full build of an index after publishing that sort of change – the automatic updates that Sitecore tries to apply don’t always spot them. According to the search documentation from Sitecore you can call either:

Index idx = SearchManager.GetIndex("myIndex");
idx.Rebuild();

or

Database web = Sitecore.Configuration.Factory.GetDatabase("web");
web.Indexes["myIndex"].Rebuild(web);

depending on whether you’re using a “new” or an “old” style index.

If you take a peek through the code for the Sitecore.Shell.Applications.Search.RebuildSearchIndex.RebuildSearchIndexForm form (which is the bit of UI you trigger if you use Control Panel to rebuild search indexes) then you’ll see that they fire off their index builds as background jobs. I’m not entirely sure if that would be a useful strategy here or not. Further investigation required…

The code change in this package requires content changes to work correctly

Another common scenario I’ve encountered before is that you get a change request from a client, which involves adding a new field to a template, and your code now needs that value to work correctly. In the past I’ve tried both defensive programming (check if the field exists – if not, don’t try to use it) and manual instructions for tasks to be performed after package installation to try and get changes like this deployed. But neither of those is perfect.

But with a post install step in your package, you could write code to make the content updates automatically:

    public class PackagePostStep : Sitecore.Install.Framework.IPostStep
    {
        public void Run(Sitecore.Install.Framework.ITaskOutput output, System.Collections.Specialized.NameValueCollection metaData)
        {
            Database master = Sitecore.Configuration.Factory.GetDatabase("master");

            var itemsToUpdate = master.SelectItems("/sitecore/content/*[@@templateid = '{76036F5E-CBCE-46D1-AF0A-4143F9B557AA}']");

            foreach(Item item in itemsToUpdate)
            {
                item.Editing.BeginEdit();

                item.Fields["MyNewField"].Value = "test";

                item.Editing.EndEdit();
            }
        }
    }

For example, here the code finds all content items based on a specific template, and sets the value of their newly field. And you could potentially combine something like this with code to publish these changes.

So, there’s a few possibilities for how it might get used. I’ll be on the look-out for an opportunity to test this out in a project now…

PS – I note via a blog post from Sean Holmesby that you can do similar things with Upgrade packages as well.

Advertisements

2 thoughts on “Late to the Post-Step party…

  1. Pingback: Automatic packages from TFS: #3 – Pipelines and data transformation | Jeremy Davis

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s