Sharepoint: How to upload files with metadata including Taxonothe fields through web services

lookupsharepointtaxonomyupload

Being very new to SharePoint coding I have been assigned the task to create a prototype code to upload a file and setting the field values for that file that will show up when opening the sharepoint page with the file.

This has to be done from a remote machine and not the Sharepoint server itself so using the .Net objects for Sharepoint is out the question.

I quickly found out how to upload a file through the Sharepoint Web Service Copy.asmx:

void UploadTestFile() {
        var file = @"C:\Temp\TestFile.doc";
        string destinationUrl = "http://mysharepointserver/Documents/"
                                 + Path.GetFileName(file);
        string[] destinationUrls = { destinationUrl };

        var CopyWS = new Copy.Copy();
        CopyWS.UseDefaultCredentials = true;
        CopyWS.Url = "http://mysharepointserver/_vti_bin/copy.asmx";

        CopyResult[] result;
        byte[] data = File.ReadAllBytes(file);

        FieldInformation mf1 = new FieldInformation {
            DisplayName = "title",
            InternalName = "title",
            Type = FieldType.Text,
            Value = "Dummy text"
        };

        FieldInformation mf2 = new FieldInformation {
            DisplayName = "MyTermSet",
            InternalName = "MyTermSet",
            Type = FieldType.Note,
            Value = "Test; Unit;"
        };

        CopyWS.CopyIntoItems(
                "+", 
                destinationUrls, 
                new FieldInformation[] { mf1, mf2 }, 
                data,
                out result);
}

This code easily uploads any file to the target site but only fills the "title" field with info. The field MyTermSet in which I have added 3 terms allready – Test, Unit and Page – will not update with the values "Test;" and "Unit;".
Being very new to Sharepoint and me not grasping all the basics googling has told me that updating "File", "Computed" or "Lookup" fields does not work with the CopyIntoItems method, and MyTermSet being a Taxonomy field is – if I am correct – a Lookup field.

So how do I get MyTermSet updated with the values "Test;" and "Unit;" ?

I would really prefer If someone has a sample code on this. I have followed several hint-links but I am none the wiser. I have found no sample-code on this at all.

Have anyone made one single method that wraps it all? Or another method that takes in the destinationUrl from the file upload and updates the Term Set/Taxonomy field.

Best Answer

Puzzling together what I have found so far, I am now able to do as I wanted. But I would really like to be able to get the Taxonomy field GUIDs dynamically and NOT having to explicitly set them myself:

void UploadTestFile(string FileName, string DocLib, Dictionary<string, string> Fields = null) {

    //Upload the file to the target Sharepoint doc lib 
    string destinationUrl = DocLib + Path.GetFileName(FileName);
    string[] destinationUrls = { destinationUrl };

    var CopyWS = new Copy.Copy();
    CopyWS.UseDefaultCredentials = true;
    CopyWS.Url = new Uri(new Uri(DocLib), "/_vti_bin/copy.asmx").ToString();

    CopyResult[] result;
    var data = File.ReadAllBytes(FileName);
    CopyWS.CopyIntoItems(
            "+",
            destinationUrls,
            new FieldInformation[0],
            data,
            out result);

    if (Fields == null) return; //Done uploading 

    //Get the ID and metadata information of the fields 
    var list = new ListsWS.Lists();
    list.UseDefaultCredentials = true;
    var localpath = new Uri(DocLib).LocalPath.TrimEnd('/');
    var site = localpath.Substring(0, localpath.LastIndexOf("/")); //Get the site of the URL
    list.Url = new Uri(new Uri(DocLib), site + "/_vti_bin/lists.asmx").ToString(); //Lists on the right site

    FieldInformation[] fiOut;
    byte[] filedata;

    var get = CopyWS.GetItem(destinationUrl, out fiOut, out filedata);
    if (data.Length != filedata.Length) throw new Exception("Failed on uploading the document.");

    //Dictionary on name and display name
    var fieldInfos = fiOut.ToDictionary(x => x.InternalName, x => x);
    var fieldInfosByName = new Dictionary<string, FieldInformation>();
    foreach (var item in fiOut) {
        if (!fieldInfosByName.ContainsKey(item.DisplayName)) {
            fieldInfosByName.Add(item.DisplayName, item);
        }
    }

    //Update the document with fielddata - this one can be extended for more than Text and Note fields.  
    if (!fieldInfos.ContainsKey("ID")) throw new Exception("Could not get the ID of the upload.");

    var ID = fieldInfos["ID"].Value; //The ID of the document we just uploaded 

    XDocument doc = new XDocument(); //Creating XML with updates we need 
    doc.Add(XElement.Parse("<Batch OnError='Continue' ListVersion='1' ViewName=''/>"));
    doc.Element("Batch").Add(XElement.Parse("<Method ID='1' Cmd='Update'/>"));
    var methNode = doc.Element("Batch").Element("Method");

    //Add ID 
    var fNode = new XElement("Field");
    fNode.SetAttributeValue("Name", "ID");
    fNode.Value = ID;
    methNode.Add(fNode);

    //Loop each field and add each Field 
    foreach (var field in Fields) {

        //Get the field object from name or display name
        FieldInformation fi = null;
        if (fieldInfos.ContainsKey(field.Key)) {
            fi = fieldInfos[field.Key];
        }
        else if (fieldInfosByName.ContainsKey(field.Key)) {
            fi = fieldInfosByName[field.Key];
        }

        if (fi != null) {

            //Fix for taxonomy fields - find the correct field to update
            if (fi.Type == FieldType.Invalid && fieldInfos.ContainsKey(field.Key + "TaxHTField0")) {
                fi = fieldInfos[field.Key + "TaxHTField0"];
            }
            else if (fi.Type == FieldType.Invalid && fieldInfosByName.ContainsKey(field.Key + "_0")) {
                fi = fieldInfosByName[field.Key + "_0"];
            }


            fNode = new XElement("Field");
            fNode.SetAttributeValue("Name", fi.InternalName);
            switch (fi.Type) {
                case FieldType.Lookup:
                    fNode.Value = "-1;#" + field.Value;
                    break;
                case FieldType.Choice:
                case FieldType.Text:
                    fNode.Value = field.Value;
                    break;
                case FieldType.Note: //TermSet's  
                    var termsetval = "";

                    var terms = field.Value.Split(';');
                    foreach (var term in terms) {
                        termsetval += "-1;#" + term + ";";
                    }
                    fNode.Value = termsetval.TrimEnd(';');
                    break;
                default:
                    //..Unhandled type. Implement if needed.
                    break;
            }
            methNode.Add(fNode); //Adds the field to the XML 
        }
        else {
            //Field does not exist. No use in uploading. 
        }
    }

    //Gets the listname (not sure if it is the full path or just the folder name) 
    var listname = new Uri(DocLib).LocalPath;
    var listcol = list.GetListCollection(); //Get the lists of the site

    listname = (from XmlNode x 
                in listcol.ChildNodes 
                where x.Attributes["DefaultViewUrl"].InnerText.StartsWith(listname, StringComparison.InvariantCultureIgnoreCase) 
                select x.Attributes["ID"].InnerText).DefaultIfEmpty(listname).First();


    //Convert the XML to XmlNode and upload the data 
    var xmldoc = new XmlDocument();
    xmldoc.LoadXml(doc.ToString());
    list.UpdateListItems(listname, xmldoc.DocumentElement);
} 

Then I call it like this:

var fields = new Dictionary<string, string>();
fields.Add("Test", "Dummy Text");
fields.Add("MrTermSet", "Page|a4ba29c1-3ed5-47e9-b43f-36bc59c0ea5c;Unit|4237dfbe-22a2-4d90-bd08-09f4a8dd0ada");
UploadTestFile(@"C:\Temp\TestFile2.doc", @"http://mysharepointserver/Documents/", fields);

I would however prefer to call it like this:

var fields = new Dictionary<string, string>();
fields.Add("Test", "Dummy Text");
fields.Add("MrTermSet", "Page;Unit");
UploadTestFile(@"C:\Temp\TestFile2.doc", @"http://mysharepointserver/Documents/", fields);