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:
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:
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:
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?
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...
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.
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...
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.
↑ Back to top