Thursday, October 13, 2011

Using Client Object Model, Uploading a new document to a document library which supports different type of Content Types

Sometimes we need to upload new document whose metadata is coming from a csv file. Consider that if the document library supports custom content types then before adding the item we need to know which field columns from the csv file are need to be added to the item. The csv file contains the information of Content Type and all possible Fields as columns in the csv file. Then we need to get the corresponding content type object from the content type string present in the csv file and get all the fields that belong to that content type. Then add the data to those fields only.
Below is the sample code.
We are passing  the parameters
  • Metadat List ,
  • Binary content of the file in byte array,
  • DataRow which contains the metadata information of that document.We are getting this datarow from the DataTable which is built from the csv file,
  •  Item Url :Docuemt file name in our case its a download link from another sharepoint server.
  • Contet Type.
I have written another blog post  related to this blog post that contains some of the methods descriptions like GetMappedListItem
/// <summary>
/// Uploads the document to the document library with metadata
/// </summary>
/// <param name="metaDataList"></param>
/// <param name="content"></param>
/// <param name="dataRow"></param>
/// <param name="itemURL"></param>
/// <param name="contentType"></param>
private void UploadDocument(FieldCollection metaDataList,byte[] content,DataRow dataRow,string itemURL,string contentType)
{
  ClientContext ctx = new ClientContext(targetSiteUrl);
  Web web = ctx.Web;
  FileCreationInformation newFile = new FileCreationInformation();
  newFile.Content = content;
  newFile.Url = itemURL.Substring(itemURL.LastIndexOf("/") + 1);
  List docs = web.Lists.GetByTitle(targetListName);
  ctx.Load(docs);
  ctx.ExecuteQuery();
  Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
  ctx.Load(uploadFile);
  ctx.ExecuteQuery();
  ListItem item = uploadFile.ListItemAllFields;
  //Set the metadata     
  item["Title"] = newFile.Url.Substring(0,newFile.Url.IndexOf("."));

  ContentType targetDocumentSetContentType = GetContentType(ctx,docs,contentType);
  item["ContentTypeId"] = targetDocumentSetContentType.Id.ToString();
  item.Update();
  ctx.ExecuteQuery();
           
  foreach (Field field in metaDataList)
  {
   if (!field.Hidden && !field.ReadOnlyField)
      {
        if (field.InternalName != "FileLeafRef" && field.InternalName != "ContentType" && !field.ReadOnlyField && field.Title != "Title")
          {
           if(field.FieldTypeKind==Microsoft.SharePoint.Client.FieldType.Lookup)
             {
               FieldLookup lookupField =field as FieldLookup;
               ListItem mappedListItem = null;

              if (lookupField != null)
              {
                mappedListItem = GetMappedListItem(lookupField, dataRow[field.Title].ToString());
              }
              if (mappedListItem != null)
              {
               FieldLookupValue lookupValue = new FieldLookupValue();
               lookupValue.LookupId = mappedListItem.Id;
               item[field.InternalName] = lookupValue;
              }
           }
           else
             item[field.InternalName] = dataRow[field.Title];
        }
    }
  }
           
 item.Update();
 ctx.ExecuteQuery();
 ctx.Dispose();
 }

Below is the code to Get the content types

/// <summary>
/// Gets the Content Type Object of the given content type string
/// </summary>
/// <param name="ctx"></param>
/// <param name="docs"></param>
/// <param name="contentType"></param>
/// <returns></returns>
private ContentType GetContentType(ClientContext ctx,List docs,string contentType)
{
  ContentTypeCollection listContentTypes = docs.ContentTypes;
  ctx.Load(listContentTypes, types => types.Include
           (type => type.Id, type => type.Name,
             type => type.Parent));
  var result = ctx.LoadQuery(listContentTypes.Where(c => c.Name == contentType));
  ctx.ExecuteQuery();
  ContentType targetDocumentSetContentType = result.FirstOrDefault();
  return targetDocumentSetContentType;
}

Adding a new document Item whose metadata contains lookup fields using Client Object Model

It is easy to add new document item using client object model. In our case we are having all metadata corresponding to the item in a csv file and the csv file contains the look up filed value.
So we have the metadata including the lookup value, but whenever you try to execute the below code you will end up with an exception "Invalid data has been used to update the list item. The field you are trying to update may be read only".

I have written another blog post  related to this blog post that contains some more information
ClientContext ctx = new ClientContext(targetSiteUrl);
Web web = ctx.Web;
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = content;
newFile.Url = itemURL.Substring(itemURL.LastIndexOf("/") + 1);
List docs = web.Lists.GetByTitle(targetListName);
ctx.Load(docs);
ctx.ExecuteQuery();
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
ctx.Load(uploadFile);
ctx.ExecuteQuery();
ListItem item = uploadFile.ListItemAllFields;
//Set the metadata     
item["Title"] = newFile.Url.Substring(0,newFile.Url.IndexOf("."));
item["Lookup Field"]=someLookupValue;  //this is the cause of the error.     

ContentType targetDocumentSetContentType = GetContentType(ctx,docs,contentType);
item["ContentTypeId"] = targetDocumentSetContentType.Id.ToString();
item.Update();
ctx.ExecuteQuery();
           

Then you don’t know what to do and which is causing the error. As shown in the below code, the highlighted line is causing the error. We can only set  FieldLookupValue
Object to the Fields of type Lookup Field. Instead of setting the FieldLookupValue if we set the text filed to it, it will throw the above error. So the solution is to set the  FieldLookupValue object to the lookup field.

The code follows below.
if (lookupField != null)
{
      mappedListItem = GetMappedListItem(lookupField, dataRow[field.Title].ToString());
}
if (mappedListItem != null)
{
     FieldLookupValue lookupValue = new FieldLookupValue();
     lookupValue.LookupId = mappedListItem.Id;
     item[field.InternalName] = lookupValue;
}


If you observe the above code, we are setting the LookupId only, we cannot set the LookupValue field as it is read only filed. But I have Lookup Value with me in the csv file. Then how can I get the ID value of the corresponding lookup value from the base list. The below code solves our problem.
Steps included in the code are  
  1.  Get the Web Object based on the LookupWebId present in the FieldLookup object.
  2.  Get the Look up list from the web based on the List ID present in the FieldLookup object.
  3.  Get the List item with its id based on the Lookup Value we have. 
 /// <summary>
        /// Gets corresponding Lookup field's base list item
        /// </summary>
        /// <param name="lookupField"></param>
        /// <param name="lookupValue"></param>
        /// <returns></returns>
        private ListItem GetMappedListItem(FieldLookup lookupField,string lookupValue)
        {
            string mappingField = lookupField.LookupField;

            ClientContext mappedWebContext = new ClientContext(tbRootWebUrl.Text);

            Web web = mappedWebContext.Web;
            Web mappedWeb = GetLookupWeb(lookupField, web);
            //if(mappedWeb.Id==lookupField.LookupWebId)
            if (mappedWeb == null)
                return null;
            mappedWebContext.Load(mappedWeb, p => p.Id, p => p.Title);
            List lookupList = mappedWeb.Lists.GetById(new Guid(lookupField.LookupList));
            mappedWebContext.Load(lookupList);
            mappedWebContext.ExecuteQuery();

            ListItemCollection libListItems = lookupList.GetItems(CamlQuery.CreateAllItemsQuery());

            mappedWebContext.Load(libListItems, items => items.Include(
                    itemlookup => itemlookup.Id,
                    itemlookup => itemlookup[mappingField]));
            mappedWebContext.ExecuteQuery();
            foreach (ListItem mappedItem in libListItems)
            {
                if (mappedItem[mappingField].ToString() == lookupValue)
                {
                    mappedWebContext.Dispose();
                    return mappedItem;
                }
            }
            mappedWebContext.Dispose();
            return null;
        }

        private Web GetLookupWeb(FieldLookup lookupField,Web web)
        {
            ClientContext mappedWebContext = web.Context as ClientContext;

            Web mappedWeb = null;
            mappedWebContext.Load(web, webitm => webitm.Id, webitm => webitm.Title, webitm => webitm.ServerRelativeUrl);
            mappedWebContext.ExecuteQuery();
            if (web.Id == lookupField.LookupWebId)
            {
                return web;
            }
            WebCollection webCollection = web.Webs;
            mappedWebContext.Load(webCollection, webitems => webitems.Include(itemweb => itemweb.Title, itemweb => itemweb.Id,itemweb=>itemweb.ServerRelativeUrl));
            mappedWebContext.ExecuteQuery();
            foreach (Web webitem in webCollection)
            {
                if (webitem.Id == lookupField.LookupWebId)
                    return webitem;
                mappedWeb=GetLookupWeb(lookupField,webitem);
                if (mappedWeb != null)
                    break;
            }
            return mappedWeb;
        }