Creating a workflow for SharePoint Foundation 2010 to automatically copy new documents to organized archive

As I’ve mentioned in my previous post, currently I’m doing some work with SharePoint 2010 and SharePoint Learning Kit. One of the tasks I had to do is to create an archive of all documents (reports) learners had submitted. If we were using SharePoint Server, I could use one of the new SharePoint 2010 Server features – Virtual Folders based on metadata . Problem is that this feature isn’t supported on Foundation version of SharePoint 2010 therefore I had to implement something similar myself.

After doing some searching, I’ve decided to workflows for that task. After some trying I’ve saw that I won’t be able to do that using SharePoint Designer. I needed to write a custom workflow using Visual Studio.

Since this was the first SharePoint extension I’ve wrote, at first I though that event such small task I wanted to do might be difficult to implement for me. But actually it wasn’t so hard. The only place that required a lot of time for me was keeping original “Created”, “Modified”, “Author” and “Modified By” properties after adding item to the archive. After some searching and experimenting I found out that I should use UpdateOverwriteVersion() method to save those properties.

So this is the source code for my workflow:

/// <summary>
/// WorkFlow to add documents to archive
/// </summary>
public sealed partial class Workflow1 : SequentialWorkflowActivity
{
    /// <summary>
    /// Class constructor
    /// </summary>
    public Workflow1()
    {
        InitializeComponent();
    }

    /// <summary>
    /// Library/list to archive to
    /// </summary>
    private const string DESTINATION_LIBRARY = "Archive";

    /// <summary>
    /// Format of the archive
    /// </summary>
    private const string DESTINATION_FORMAT = "YEAR/MONTH";

    /// <summary>
    /// Sets if copy or move document. If true, moves a document, if false - copies it only
    /// </summary>
    private const bool MOVE_DOCUMENT = true;

    /// <summary>
    /// Sets if overwrite a document if it already exists on the library
    /// </summary>
    private const bool OVERWRITE_DOCUMENT = false;

    // ReSharper disable InconsistentNaming

    /// <summary>
    /// GUID of the workflow
    /// </summary>
    public Guid workflowId = default(Guid);

    /// <summary>
    /// Properties of the workflow
    /// </summary>
    public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();

    /// <summary>
    /// Destination path where the file will be archived
    /// </summary>
    private string destinationPath;

    /// <summary>
    /// Destination library for archive
    /// </summary>
    private SPDocumentLibrary destinationLibrary;

    // ReSharper restore InconsistentNaming

    /// <summary>
    /// Workflow execution
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void CodeActivity1ExecuteCode(object sender, EventArgs e)
    {
        // Get current item:
        var currentItem = workflowProperties.Item;
        // Work with the current site only:
        using (var currentSite = SPContext.Current.Web)
        {
            // Get document library:
            destinationLibrary = (SPDocumentLibrary)currentSite.Lists[DESTINATION_LIBRARY];
            // Create folder structure:
            CreateFolders(new Queue(DESTINATION_FORMAT.Split('/')), destinationLibrary.ParentWeb.Site.Url + destinationLibrary.RootFolder.ServerRelativeUrl);
            // Archive the item:
            ArchiveItem(currentItem, destinationPath);
        }
    }

    /// <summary>
    /// Creates folder hierarchy
    /// </summary>
    /// <param name="folders">Stack of folders</param>
    /// <param name="location">Location to create folder to</param>
    private void CreateFolders(Queue folders, string location)
    {
        // If we already created all folders, we can stop now:
        if (folders.Count == 0)
        {
            destinationPath = location;
            return;
        }
        // Get name of folder to create:
        var currentFolder = GetMetadata(workflowProperties.Item, folders.Dequeue().ToString());
        var newLocation = (location + "/" + currentFolder).Trim('/');
        // Work with the current site only:
        using (var currentSite = SPContext.Current.Web)
        {
            // We only need to create folder if it doesn't exist already
            if (!currentSite.GetFolder(newLocation).Exists)
            {
                // Create the folder:
                var createdFolder = destinationLibrary.Items.Add(location, SPFileSystemObjectType.Folder, currentFolder);
                createdFolder.Update();
            }
        }
        // Go to create next folder:
        CreateFolders(folders, newLocation);
    }

    /// <summary>
    /// Adds item to archive
    /// </summary>
    /// <param name="item">Item to add</param>
    /// <param name="destination">Archive path</param>
    /// <returns>Result of arhivation process</returns>
    private static void ArchiveItem(SPListItem item, string destination)
    {
        // Save main meta information for later use:
        var author = item.File.Author;
        var modifiedBy = item.File.ModifiedBy;
        var modified = item.File.TimeLastModified;
        var created = item.File.TimeCreated;

        // Get destination filename:
        var destinationFile = destination + "/" + item.File.Name;

        // Work with the current site only:
        using (var currentSite = SPContext.Current.Web)
        {

            // Copy the item and set properties:
            var coppiedFile = currentSite.GetFolder(destination).Files.Add(destinationFile, item.File.OpenBinary(), new Hashtable(), author, modifiedBy, created, modified, OVERWRITE_DOCUMENT);
            coppiedFile.Item["Created"] = created;
            coppiedFile.Item["Modified"] = modified;
            // Save changes:
            coppiedFile.Item.UpdateOverwriteVersion();
            // If moving is enabled, delete original item:
            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (MOVE_DOCUMENT)
                item.Delete();
            // ReSharper restore ConditionIsAlwaysTrueOrFalse

        }
    }

    /// <summary>
    /// Get required metadata from the item
    /// </summary>
    /// <param name="item">Item to get metadata from</param>
    /// <param name="field">Field to get</param>
    /// <returns>MetaData</returns>
    private static string GetMetadata(SPListItem item, string field)
    {
        string value;
        // Get required field:
        switch (field.ToUpper())
        {
            case "YEAR":
                value = item.File.TimeCreated.Year.ToString();
                break;
            case "MONTH":
                value = item.File.TimeCreated.Month.ToString();
                break;
            case "DAY":
                value = item.File.TimeCreated.Day.ToString();
                break;
            case "USER":
                value = item.File.Author.Name;
                break;
            default:
                value = "Misc";
                break;
        }
        return value;
    }
}

So what this code does is creating folder structure I define (via DESTINATION_FORMAT) using metadata of the document and them copies or moves (if MOVE_DOCUMENT is set to true) to the specified archive library (DESTINATION_LIBRARY). Currently I’ve added only year, month, date and user fields as possible library format options just to see how’s everything working. I’m planning to extend GetMetadata() method so I could archive learners reports by subject or faculty.

By the way, this workflow should be set “Start at Item Creation” if you want to automatically add every new document to archive. Also, a specific list should be selected for this workflow. It can be done by setting “Target List” property of workflow or set while creating new “Sequential Workflow” project in Visual Studio 2010.

Social

6 comments on the post

  1. Can I publish my MSAccess database application via SharePoint 2010 / WSS 3.0.

    Is it possible that client computer can view and update the database without having to install an MSAccess software?

    I’d been told that choosing SharePoint view for my file, after its publishing would allow this, is this true?

    thanx,
    Jo.

  2. Hi,

    How who I Sharepoint 2010 Admin use the above code..
    I have no developers at my disposal..

    Do I import into SharePoint Designer 2010.
    Must it be deployed as a wsp and activated..

    Appreciate any assistance..

  3. You should not dispose the Context object
    using (var currentSite = SPContext.Current.Web)
    {}
    That line is wrong. With the context object, you can open a new object and dispose it.

    -http://Praveenbattula.blogspot.com

  4. This is one of the best articles so far I have read online. No crap, just useful information. Very well presented.
    Check out this helpful link too its also having a wonderful explanation on Create a Document in SharePoint and add folder in SharePoint Document using C#. …
    http://mindstick.com/Articles/19b8cf75-6d2e-4504-8840-718717bd53ba/?Create%20a%20Document%20in%20SharePoint%20and%20add%20folder%20in%20SharePoint%20Document%20using%20C#

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *