20 April 2014

Serialization of Content Tree prior installing packages


One of my colleague asked me a question about how could you get the data from a server and restore it into his local machine. Obviously I told him take a DB backup...Dough.... Well the issue was that he did not have access to the DBs so needed to request a backup from the Dba, not convenient. So I was starting on with Serialization. I show him how to serialise items from the tree and deserialise them on his local. I kind of love those discussion as it always start on a topic then extend to something more useful. In this case we ended up on: could we serialise the content tree as backup before installing any packages.

So what we were thinking about was:

1- Serialising the tree: well that is the easy bit - for this code sample I will take the entire tree:

        string SitecoreRootItem = "{11111111-1111-1111-1111-111111111111}";

        protected void Page_Load(object sender, EventArgs e)
        {
            BackupItemTree(SitecoreRootItem);
        }

        public void BackupItemTree(string id)
        {
            Database db = Sitecore.Configuration.Factory.GetDatabase("master");
            Item itm = db.GetItem(id);

            Sitecore.Data.Serialization.Manager.DumpTree(itm);
        }

2- Events.. Events... Events
Now I am sure you are familiar with Events in sitecore, well guess what there is an event for package installation:
      < event name="packageinstall:starting" />
      < event name="packageinstall:items:starting" />
      < event name="packageinstall:items:ended" />
      < event name="packageinstall:files:starting" />
      < event name="packageinstall:files:ended" />
      < event name="packageinstall:ended" />

So easy to hook up our code on the packageinstall:starting...
configuration:
      < event name="packageinstall:starting">
        < handler method="OnPackageInstall" type="MyProject.Business.Sitecore.Events.PackageInstall.PackageInstallEventHandler, MyProject.Business">
      < /handler>
< /event>

for the Handler:
using Sitecore.Data;
using Sitecore.Data.Items;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SC = Sitecore;

namespace MyProject.Business.Sitecore.Events.PackageInstall
{
    public class PackageInstallEventHandler
    {
        protected void OnPackageInstall(object sender, EventArgs args)
        {
            string SitecoreRootItem = "{11111111-1111-1111-1111-111111111111}";
            BackupItemTree(SitecoreRootItem);
        }

        private void BackupItemTree(string id)
        {
            Database db = SC.Configuration.Factory.GetDatabase("master");
            Item itm = db.GetItem(id);

            SC.Data.Serialization.Manager.DumpTree(itm);
        }
    }
}

Now if we clear the serialisation foder:


Now let's install a folder:


Now let's check the serialisation folder again:



Now you have a backup automatically before installing packages... Please note that this was just to show what you could do with event before installing packages... Obviously, if your content tree is really big, you could have some timing out issues...

5 April 2014

Create your own pipeline


Here is a topic I wanted to post about as I loved the Pipeline concept. In sitecore, the pipeline is a succession of processors which gets executed in the order defined. Each rocessor is doing a specific action. If the processor encounter an error or do not validate whatever condition you want then you can abort the pipeline and exit.

The pipelines in sitecore are great so we can inject processors that can do any action. So if you want to intercept the HttpRequest, you can use the HttpRequestBegin pipeline and inject your own Item resolver or Language resolver...

Now this post is not intended to show you how to use the existing pipeline, as I am sure you would already have done a few custom processor. The idea I wanted to show here is how you could create a pipeline for your own code.

1- Create a config file for your pipeline

your configuration file will start by defining your Custom Pipeline with the different processor:


  
    
      
        
        
      
    
  



2- Classes for your pipeline processor.
When you have multiple processors, please separate your classes in different files. :)
The first processor will: Check for any .update files in a folder and install them into Sitecore:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Update.Installer;
using Sitecore.Update.Installer.Exceptions;
using Sitecore.Update.Installer.Installer.Utils;
using Sitecore.Update.Installer.Utils;
using Sitecore.Update.Utils;
using System.IO;
using System.Linq;
using System.Web;
using SC = Sitecore;
using Sitecore.SecurityModel;
using Sitecore.Data.Proxies;
using Sitecore.Data.Engines;
using log4net;
using Sitecore.Update;
using MyProject.Business.Sitecore.Pipelines;

namespace MyProject.Business.Sitecore.Pipelines
{
    public class InstallPackages
    {
        public void Process(MyPipelineArgs args)
        {
            args.NeedToPublish = Install();
        }

        private bool Install()
        {
            var files = Directory.GetFiles(SC.MainUtil.MapPath("/sitecore/admin/Automated_Packages"), "*.update", SearchOption.AllDirectories);
            SC.Context.SetActiveSite("shell");

            bool needToPublish = false;

            using (new SecurityDisabler())
            {
                using (new ProxyDisabler())
                {
                    using (new SyncOperationContext())
                    {
                        foreach (var file in files)
                        {
                            needToPublish = true;
                            try
                            {
                                Install(file);
                                SC.Diagnostics.Log.Info("Automated Deployment success for file " + file, this);

                                File.Delete(file);
                            }
                            catch (Exception ex)
                            {
                                SC.Diagnostics.Log.Error("Automated Deployment error " + ex.StackTrace, this);
                            }
                        }
                    }
                }
            }

            return needToPublish;
        }

        protected string Install(string package){}...
    }
}


The second processor will publish items: Templates, layouts, renderings:
using Sitecore.Data;
using Sitecore.Data.Managers;
using Sitecore.Publishing;
using Sitecore.SecurityModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SC = Sitecore;

namespace MyProject.Business.Sitecore.Pipelines
{
    public class PublishAfterInstall
    {
        public void Process(MyPipelineArgs args)
        {
            if(args.NeedToPublish)
                args.Result = Publish();
        }

        private string Publish()
        {

            string message = string.Empty;

            SC.Context.SetActiveSite("shell");
            using (new SecurityDisabler())
            {
                DateTime publishDate = DateTime.Now;
                SC.Data.Database master = SC.Configuration.Factory.GetDatabase("master");
                SC.Data.Database web = SC.Configuration.Factory.GetDatabase("web");

                // publish specific section - Need to  put those in settings
                ID templateFolderID = new ID("{3C1715FE-6A13-4FCF-845F-DE308BA9741D}");
                ID layoutFolderID = new ID("{EB2E4FFD-2761-4653-B052-26A64D385227}");
                ID systemFolderID = new ID("{13D6D6C6-C50B-4BBD-B331-2B04F1A58F21}");

                try
                {
                    PublishManager.PublishItem(master.GetItem(templateFolderID), new Database[] { web }, LanguageManager.GetLanguages(master).ToArray(), true, false);
                    PublishManager.PublishItem(master.GetItem(layoutFolderID), new Database[] { web }, LanguageManager.GetLanguages(master).ToArray(), true, false);
                    PublishManager.PublishItem(master.GetItem(systemFolderID), new Database[] { web }, LanguageManager.GetLanguages(master).ToArray(), true, false);

                    message = "Success!!!";
                }
                catch (Exception ex)
                {
                    SC.Diagnostics.Log.Info("Publishing failed", this);
                    message = "Failed!!!";
                }

                return message;
            }
        }
    }
}


3- Arguments

Now the question you all have is: well this is great, you will have a succession of processors but how do you pass data from one to the other? Well, as yo may have noticed, the process method for the processors are using a custom Argument which is passed from one processor to the next one!!!! So lets code this custom class as well.
using Sitecore.Pipelines;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyProject.Business.Sitecore.Pipelines
{
    public class MyPipelineArgs : PipelineArgs
    {
        public bool NeedToPublish { get; set; }
        public string Result { get; set; }
    }
}

4- Execute the pipeline from code
To trigger the pipeline, I just created an aspx in the solution so I can go to the URL and debug...
using MyProject.Business.Sitecore.Pipelines;
using Sitecore.Diagnostics;
using Sitecore.Pipelines;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyProject.sitecore_modules.MyProject
{
    public partial class InstallPackages : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Initialise your Arguments
            var pipelineArgs = new MyPipelineArgs();

            // Execute your pipeline, passing your custom argument
            CorePipeline.Run("MyPipeline", pipelineArgs);

            // After the package installer ran and published the site you can log the results
            Log.Info(pipelineArgs.Result, this);
        }
    }
}

Quick last note: since this is an aspx, you could setup the schedul agent to trigger this aspx once a day or more to automate package installation...