Skip to content

Instantly share code, notes, and snippets.

@brimfulofashar
Last active August 24, 2016 12:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brimfulofashar/0a460433f6c110c835ed84e684e9bd56 to your computer and use it in GitHub Desktop.
Save brimfulofashar/0a460433f6c110c835ed84e684e9bd56 to your computer and use it in GitHub Desktop.
Sitecore 0 downtime deployments
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore>
<pipelines>
<initialize>
<processor type="MsmqWatcher, ASSEMBLY" patch:after="processor[last()]">
<MsmqName>.\private$\QUEUE_NAME</MsmqName>
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost>
<TestPort>81</TestPort>
</processor>
</initialize>
</pipelines>
</sitecore>
</configuration>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
<sitecore>
<events>
<event name="item:saved">
<handler type="CLASS, ASSEMBLY" method="OnItemSaved">
<database>master</database>
<TestPort>81</TestPort>
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost>
<messagequeuepath>.\private$\QUEUE_NAME</messagequeuepath>
</handler>
</event>
<event name="item:deleting">
<handler type="CLASS, ASSEMBLY" method="OnItemDeleting">
<database>master</database>
<TestPort>81</TestPort>
<TestHost>HOSTNAME_OF_NEW_SITE</TestHost>
<messagequeuepath>.\private$\QUEUE_NAME</messagequeuepath>
</handler>
</event>
</events>
</sitecore>
</configuration>
public delegate void MessageReceivedEventHandler(object sender, MessageEventArgs args);
public class MsmqWatcher
{
private bool _listen;
private MessageQueue _queue;
public string MsmqName { get; set; }
public string TestHost { get; set; }
public int TestPort { get; set; }
public Type[] FormatterTypes { get; set; }
private object Lock = new object();
public void Process(PipelineArgs args)
{
Sitecore.Diagnostics.Log.Info("MsmqWatcher starting for path: " + MsmqName + " for host " + TestHost + " on " + TestPort, this);
_queue = new MessageQueue(MsmqName);
if (WatchQueue())
{
StartJob();
}
else
{
Sitecore.Diagnostics.Log.Error("MsmqWatcher not watching queue: " + MsmqName, this);
}
}
private bool WatchQueue()
{
try
{
using (var serverManager = new ServerManager())
{
var sitename = HostingEnvironment.SiteName;
// for msmqwatcher to be watching, the following need to match:
// the site which is currently running has to have a binding for TestBinding on port 81 and also match on the sitename.
// this gets around the issue of 2 sites being on port 81 with same binding but one being on and other being off.
Site targetSite = serverManager.Sites.FirstOrDefault(site => site.Bindings.Any(x => x.Host == TestHost && x.EndPoint.Port == TestPort) && site.Name == sitename);
return targetSite != null;
}
}
catch (Exception ex)
{
Sitecore.Diagnostics.Log.Error("MsmqWatcher error: ", ex, this);
}
return false;
}
public void StartJob()
{
var jobOptions = new JobOptions(
"MsmqWatcherJob",
"MsmqWatcher",
"whatever",
this,
"Start",
null);
Sitecore.Diagnostics.Log.Info("MSMQWatcher job starting", this);
JobManager.Start(jobOptions);
}
public event MessageReceivedEventHandler MessageReceived;
public void Start()
{
_listen = true;
if (FormatterTypes != null && FormatterTypes.Length > 0)
{
// Using only the XmlMessageFormatter. You can use other formatters as well
_queue.Formatter = new XmlMessageFormatter(FormatterTypes);
}
_queue.PeekCompleted += OnPeekCompleted;
_queue.ReceiveCompleted += OnReceiveCompleted;
Sitecore.Diagnostics.Log.Info("MSMQWatcher job started", this);
StartListening();
}
public void Stop()
{
Sitecore.Diagnostics.Log.Info("MSMQWatcher job stopped", this);
_listen = false;
_queue.PeekCompleted -= OnPeekCompleted;
_queue.ReceiveCompleted -= OnReceiveCompleted;
}
private void StartListening()
{
if (!_listen)
{
Sitecore.Diagnostics.Log.Info("MSMQWatcher not listening", this);
return;
}
// The MSMQ class does not have a BeginRecieve method that can take in a
// MSMQ transaction object. This is a workaround – we do a BeginPeek and then
// recieve the message synchronously in a transaction.
// Check documentation for more details
if (_queue.Transactional)
{
_queue.BeginPeek();
}
else
{
_queue.BeginReceive();
}
}
private void OnPeekCompleted(object sender, PeekCompletedEventArgs e)
{
Sitecore.Diagnostics.Log.Info("MSMQWatcher OnPeekCompleted Triggered", this);
_queue.EndPeek(e.AsyncResult);
var trans = new MessageQueueTransaction();
try
{
trans.Begin();
var msg = _queue.Receive(trans);
trans.Commit();
msg.Formatter = new XmlMessageFormatter(new[] {"System.String,mscorlib"});
FireRecieveEvent(msg.Label, msg.Body.ToString());
StartListening();
}
catch (Exception ex)
{
Sitecore.Diagnostics.Log.Error("MSMQWatcher OnPeekCompleted Error", ex, this);
trans.Abort();
}
}
private void FireRecieveEvent(string label, string body)
{
lock (Lock)
{
if (label.EndsWith(".deleted"))
{
DropItem(body);
}
else if (label.EndsWith(".item"))
{
LoadItem(body);
}
}
}
private void OnReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
{
Sitecore.Diagnostics.Log.Info("MSMQWatcher OnReceiveCompleted Triggered", this);
var msg = _queue.EndReceive(e.AsyncResult);
Sitecore.Diagnostics.Log.Info("MSMQWatcher Processing: " + msg.Label, this);
msg.Formatter = new XmlMessageFormatter(new[] {"System.String,mscorlib"});
FireRecieveEvent(msg.Label, msg.Body.ToString());
StartListening();
}
public void DropItem(string body)
{
using (new SecurityDisabler())
{
using (TextReader reader = new StringReader(body))
{
var syncItem = SyncItem.ReadItem(new Tokenizer(reader));
var item = Database.GetDatabase("master").GetItem(syncItem.ID);
if (item != null)
{
item.DeleteChildren();
item.Delete();
}
}
}
}
public void LoadItem(string body)
{
using (new SecurityDisabler())
{
var loadOptions = new LoadOptions(Database.GetDatabase("master"))
{
DisableEvents = true,
ForceUpdate = true
};
using (TextReader tw = new StringReader(body))
{
ItemSynchronization.ReadItem(tw, loadOptions, false);
}
}
}
}
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(object label, object body)
{
MessageLabel = label;
MessageBody = body;
}
public object MessageBody { get; private set; }
public object MessageLabel { get; private set; }
}
public class SerialiseContent
{
public string Database { get; set; }
public int TestPort { get; set; }
public string TestHost { get; set; }
public string MessageQueuePath { get; set; }
public void OnItemSaved(object sender, EventArgs args)
{
var eventArgs = args as SitecoreEventArgs;
Assert.IsNotNull(eventArgs, "eventArgs");
var item = eventArgs.Parameters[0] as Item;
Assert.IsNotNull(item, "item");
if (item.Database != null && item.Database.Name == Database)
{
SerializeItem(item, false);
}
}
public void OnItemDeleting(object sender, EventArgs args)
{
var eventArgs = args as SitecoreEventArgs;
Assert.IsNotNull(eventArgs, "eventArgs");
var item = eventArgs.Parameters[0] as Item;
Assert.IsNotNull(item, "item");
if (item.Database != null && item.Database.Name == Database)
{
SerializeItem(item, true);
}
}
private bool DoSerialisation()
{
// this will only allow serialisation if another site with a test host (uel) on a test port (81) is found. This tells the current live sitecore to allow content serialisation.
using (var serverManager = new ServerManager())
{
var targetSite =
serverManager.Sites.FirstOrDefault(
site => site.Bindings.Any(x => x.Host == TestHost && x.EndPoint.Port == TestPort));
return targetSite != null && HostingEnvironment.SiteName != targetSite.Name;
}
}
private void SerializeItem(Item item, bool itemDeleted)
{
if (item != null && DoSerialisation())
{
var siteInfoList = Factory.GetSiteInfoList();
if (siteInfoList.Any(x => item.Paths.FullPath.Contains(x.RootPath)))
{
using (MessageQueue messageQueue = new MessageQueue(MessageQueuePath))
{
using (MemoryStream ms = new MemoryStream())
{
using (TextWriter tw = new StreamWriter(ms))
{
ItemSynchronization.BuildSyncItem(item).Serialize(tw);
tw.Flush();
var serialisedItem = Encoding.UTF8.GetString(ms.ToArray());
Message itemMessage = new Message
{
Label = item.Paths.ParentPath + "/" + item.Name + (itemDeleted ? ".item.deleted" : ".item"),
Body = serialisedItem
};
messageQueue.Send(itemMessage);
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment