Backing up Document Types

by Aaron Powell 11. June 2010 03:20

Something I’ve heard a number of people say is that they want a way in which they can store the DocumentType in their source control system.

This is obviously a bit of a problem since they are actually stored in the database, not on the file system. Hmmm…

Then yesterday I was talking to Tatham Oddie about it and how you could go about CI with Umbraco. Then after bouncing a few ideas of Shannon we had a great idea, that when you say a DocumentType it would just dump it to the file system. You can then check this file into your source control system and you have a backup.

Sounds pretty simple, and in fact, Umbraco has all the stuff you’d need for this, it’s just a matter of doing it. So while waiting for a rather large project to check out of source control I decided to just write it.

Please note, the following code is not tested, it’s just a POC, when I get some time I do plan on actually testing it :P

How do go about it

It’s actually quite simple, you just need to tie into the Umbraco event model for a DocumentType and use the built in XML export feature.

I’ve also done the code so you can either dump to a single file or to multiple files (depending which is easiest in your solution.

It doesn’t check the files out for you, so if you’re using something like TFS you’ll have a problem, but I have put in handlers for read-only files.

Also, there’s no error checking, like I said, this is POC code :P.

Code baby!

using System.Linq;
using System.IO;
using System.Web;
using System.Xml.Linq;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.web;

namespace AaronPowell.Umbraco
{
    public class DocumentTypeSerializer : ApplicationBase
    {
        public DocumentTypeSerializer()
        {
            DocumentType.AfterSave += new DocumentType.SaveEventHandler(DocumentType_AfterSave);
            DocumentType.AfterDelete += new DocumentType.DeleteEventHandler(DocumentType_AfterDelete);
        }

        void DocumentType_AfterDelete(DocumentType sender, umbraco.cms.businesslogic.DeleteEventArgs e)
        {
            DumpDocumentTypes(false);
        }

        void DocumentType_AfterSave(DocumentType sender, umbraco.cms.businesslogic.SaveEventArgs e)
        {
            DumpDocumentTypes(false);
        }

        private static void DumpDocumentTypes(bool useSingleFile)
        {
            var allDocTypes = DocumentType.GetAllAsList();
			var storageFolder = GlobalSettings.StorageDirectory + "/";
			System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
			
			if(useSingleFile)
			{
				var xdoc = new XDocument(new XElement("DocumentTypes"));

				foreach (var dt in allDocTypes)
					xdoc.Root.Add(XElement.Parse(dt.ToXml(xmlDoc).InnerXml));

				var file = storageFolder + "DocumentTypes.config";
				var fileOnFileSystem = new FileInfo(HttpContext.Current.Server.MapPath(file));
				if (fileOnFileSystem.Exists)
				{
					if (fileOnFileSystem.Attributes == FileAttributes.ReadOnly)
						fileOnFileSystem.Attributes &= ~FileAttributes.ReadOnly;
						
					fileOnFileSystem.Delete();
				}

				xdoc.Save(fileOnFileSystem.FullName);
			}
			else
			{
				storageFolder += "DocumentTypes/";
				if(!Directory.Exists(storageFolder))
				{
					Directory.Create(storageFolder);
				}
				else
				{
					var di = new DirectoryInfo(storageFolder);
					var files = di.GetFiles();
					foreach(var file in files) 
					{
						if (file.Exists)
						{
							if (file.Attributes == FileAttributes.ReadOnly)
								file.Attributes &= ~FileAttributes.ReadOnly;
								
							file.Delete();
						}
					}
				}				
				
				foreach(var dt in allDocTypes) 
				{
					var xdoc = XDocument.Parse(dt.ToXml(xmlDoc).ToString());
					
					var file = storageFolder + dt.Alias + ".config";
					
					var fileOnFileSystem = new FileInfo(HttpContext.Current.Server.MapPath(file));
					if (fileOnFileSystem.Exists)
					{
						if (fileOnFileSystem.Attributes == FileAttributes.ReadOnly)
							fileOnFileSystem.Attributes &= ~FileAttributes.ReadOnly;
							
						fileOnFileSystem.Delete();
					}

					xdoc.Save(fileOnFileSystem.FullName);
				}
			}
        }
    }
}

I'll look at cleaning this up and testing it soon and releasing it as an actual Umbraco package, but in the mean time feel free to have a play around with it.

Categories: .Net | Umbraco

Comments

6/11/2010 3:39:50 AM #

This is an awesome idea! Presumably you could use it to enforce DT modification only in your dev/staging environment (and cancel the events if the server is in your live environment?), and then write a document-type importer from Dev/Staging. So, any changes to the document types are version controlled and can be promoted from version control (& dev database). That's really the missing piece in our CI architecture.

Pete Miller United Kingdom

6/11/2010 3:41:24 AM #

Cool.  I was just thinking about something like this a few days ago (to include Macros too).

Rob Gray Australia

6/11/2010 3:48:33 AM #

@Pete:
I don't think you can cancel the save even in Umbraco Document Types properly, I think they suffer from the same quirk I described about Documents here: www.aaron-powell.com/the-great-umbraco-api-misconception

But with the XML you could then use CI to to an import, either manual importing, generating a package or have some automated process to upload them into Umbraco.


@Rob:
Yes, you can do the same thing with Macros, in fact we do something similar for Snapshot macro support.

AaronPowell Australia

6/11/2010 10:22:36 AM #

Pingback from topsy.com

Twitter Trackbacks for
        
        FARMCode.org | Backing up Document Types
        [farmcode.org]
        on Topsy.com

topsy.com

6/11/2010 10:48:47 AM #

I built a package that was on demand but exported all to the file system then offered a zip download for you to save locally and add to source control. Doing this on save as per your POC is a great idea.

Simon Dingley United Kingdom

6/11/2010 12:37:26 PM #

Aaron,

This is brilliant will look to incorporate into our setup

Regards

Ismail

Ismail Mayat United Kingdom

6/11/2010 4:48:59 PM #

Where has this been for the last 10 months! :)

Right looks like all we need now is a filewatcher to make sure when the file changes its contents are checked against the Doc Type in the current setup and if differs prompts you to sync it up. Could be fun.

Pete

Pete Duncanson United Kingdom

6/25/2010 8:02:20 PM #

Great idea.

I hope that V5 will use config files for doctypes, datatypes and macros, It would make it that much easier use your own editors, source control, share stuff.

/jesper

Jesper Ordrup Denmark

6/30/2010 4:02:44 AM #

Finally got round to implementing this in a site.  I found that it didn't work for me (Umbraco version 4.0.3.1).  I was getting XElement.Parse trying to Parse 'System.Xml.XmlElement' and failing.

I assume the Aaron posted does work, so I'm wondering why it does work for me. Anyway, here's the changes I had to make to get the document type to export.

foreach (var dt in allDocTypes) {                  
XmlElement element = dt.ToXml(xmlDoc);
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.ImportNode(element, true));                                                            xdoc.Root.Add(XElement.Parse(doc.InnerXml));      
}

The same change would have to be made to 'file per doc type branch'.

Rob Gray Australia