How to create an adapter for the TFS Integration Platform - Part V: Items (IMigrationItem & IMigrationItemSerializer)

Note: This post is part of a series and you can find the rest of the parts in the series index.

An important class in your implementation is your item implementation, which is used to identify what an item (be it a file, directory, list item, bug, etc.) is. This class must implement IMigrationItem. This class is more important for VC than WIT, but in both cases, you need to include all the properties you need to know about. You must also ensure that all properties are serializable by the default XML serializer in .NET.

Power Tip: VC stands for Version Control—this refers to an adapter that works with the source control aspects of the system. WI (work items) and WIT (work item tracking) are the same thing. File attachments in WIs are not regarded as VC and must be handled by your WI adapter.

IMigrationItem

WIT

The WIT adapter is slightly smaller than the VC one since it is just a set of properties. I do not prioritize the inherited DisplayName property here, and the Download method is just logging. The only interesting part is the SimpleDictionary<T, V> I use. SimpleDictionary<T, V> is simply to store key/value pairs of column information from SharePoint (because you may have customized the columns in SharePoint, and I cannot hard-code them). The reason I use this instead of the Dictionary<T, V> provided by .NET is that the latter cannot be serialized using the default XML serializer. I will cover SimpleDictionary<T, V> in a later post.

public class SharePointListItem : IMigrationItem
{
    public SharePointListItem()
    {
        this.Columns = new SimpleDictionary<string, object>();
    }

    public string Id { get; set; }
    public DateTime ModifiedOn { get; set; }
    public string AuthorId { get; set; }
    public SimpleDictionary<string, object> Columns { get; set; }
    public string DisplayName { get; set; }

    public void Download(string localPath)
    {
        TraceManager.TraceInformation("WSSWIT:MI:Download - {0}", localPath);
    }
}

VC

The VC adapter is a bit more complex, with more properties, but the key difference is the DisplayName property, which returns the filename. Even more critical is the Download method, which retrieves the actual file or folder to a specified location (provided by the localPath parameter) on disk for the platform to use.

Power Tip: When downloading files in IMigrationItem, you are responsible for creating the path. Make sure you create directories and check if they exist.

string IMigrationItem.DisplayName
{
    get { return Filename; }
}

void IMigrationItem.Download(string localPath)
{
    TraceManager.TraceInformation("WSSVC:Item:Download:From {0} to {1}", this.AbsoluteURL, localPath);
    if (this.ItemType == SharePointItemType.File)
    {
        TraceManager.TraceInformation("\tType is file");
        string targetDir = Path.GetDirectoryName(localPath);
        if (!Directory.Exists(targetDir))
        {
            TraceManager.TraceInformation("\tCreating directory for file - {0}", targetDir);
            Directory.CreateDirectory(targetDir);
        }

        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(this.AbsoluteURL);
        webRequest.Credentials = this.Credentials;
        using (Stream responseStream = webRequest.GetResponse().GetResponseStream())
        {
            using (FileStream fileStream = new FileStream(localPath, FileMode.CreateNew, FileAccess.ReadWrite))
            {
                byte[] buffer = new byte[1024];
                int bytesRead;
                do
                {
                    // Read data (up to 1k) from the stream
                    bytesRead = responseStream.Read(buffer, 0, buffer.Length);

                    // Write the data to the local file
                    fileStream.Write(buffer, 0, bytesRead);
                } while (bytesRead > 0);
            }
        }

        TraceManager.TraceInformation("\tFile downloaded successfully");
    }

    if (this.ItemType == SharePointItemType.Directory)
    {
        TraceManager.TraceInformation("\tType is directory");
        if (!Directory.Exists(localPath))
        {
            TraceManager.TraceInformation("\tCreating directory - {0}", localPath);
            Directory.CreateDirectory(localPath);
        }
    }
}

IMigrationItemSerializer

The migration item serializer is simply a way to convert your item to XML using .NET serialization. My implementation of this is identical for both VC and WIT.

public class SharePointWITMigrationItemSerializer : IMigrationItemSerializer
{
    public IMigrationItem LoadItem(string itemBlob, ChangeGroupManager manager)
    {
        TraceManager.TraceInformation("WSSWIT:S:LoadItem");
        if (manager == null)
        {
            throw new ArgumentNullException(nameof(manager));
        }

        if (string.IsNullOrEmpty(itemBlob))
        {
            throw new ArgumentNullException(nameof(itemBlob));
        }

        XmlSerializer serializer = new XmlSerializer(typeof(SharePointListItem));

        using (StringReader itemBlobStringReader = new StringReader(itemBlob))
        {
            using (XmlReader itemBlobXmlReader = XmlReader.Create(itemBlobStringReader))
            {
                return (SharePointListItem)serializer.Deserialize(itemBlobXmlReader);
            }
        }
    }

    public string SerializeItem(IMigrationItem item)
    {
        if (item == null)
        {
            throw new ArgumentNullException(nameof(item));
        }

        TraceManager.TraceInformation("WSSWIT:S:SerializeItem - {0}", item.DisplayName);

        XmlSerializer sharePointTaskSerializer = new XmlSerializer(item.GetType());

        using (MemoryStream memoryStream = new MemoryStream())
        {
            sharePointTaskSerializer.Serialize(memoryStream, item);
            memoryStream.Seek(0, SeekOrigin.Begin);
            using (StreamReader streamReader = new StreamReader(memoryStream))
            {
                return streamReader.ReadToEnd();
            }
        }
    }
}