Resolve issues while updating People Picker Column of a List in Event Handler

Let’s say, you have a List with a People Picker column (Person or Group) on your site. Now, you need to add an event handler for new item add/update. Normally, if you go by the conventional way, you would get the following error:

Invalid data has been used to update the list item . The field you are trying to update may be read only.

I tried to figure out what exactly caused this error while I trying to set a people picker column property in my event handler programmatically. Afterwards, I found out that People Picker Column in a List is a read-only field. By Default, the read-only mode of this field remains true while we try to add/update it for a list item. So, how to overcome this problem? Well, it’s not that difficult. You just need to set the read-only mode of the people picker column to be “False” at first and after that,  update the property of the people picker. After finishing everything, set the read-only mode of the field again to “True”. Here is an example how you can achieve this:


using (SPSite mySite = new SPSite(properties.SiteId))
 {
 using (SPWeb myWeb = mySite.OpenWeb(properties.RelativeWebUrl))
 {
 SPList currentList = myWeb.Lists[properties.ListId];
 SPListItem currentitem = currentList.GetItemById(properties.ListItem.ID);

 myWeb.AllowUnsafeUpdates = true;
 ServerContext ospServerContext = ServerContext.GetContext(mySite);

//Create a new UserProfileManager
 UserProfileManager pManager = new UserProfileManager(ospServerContext);

//Get the User Profile for the current user
 UserProfile uProfile = pManager.GetUserProfile(currentitem["Title"].ToString());

 //Set the read only mode of people picker column to FALSE
 currentitem.Fields["Direct Supervisor"].ReadOnlyField = false;

 if (uProfile["Manager"].Value != null)
 {
 string temp = @""+uProfile["Manager"].Value.ToString();
 string temp1=temp.Replace("\\",@"\");
 //Setting the property of people picker column
 currentitem["Direct Supervisor"] = myWeb.AllUsers[temp1].ID;

 }

//Update List Item
 currentitem.Update();
 //Update List
 currentList.Update();

//Set the read only mode to TRUE
 currentitem.Fields["Direct Supervisor"].ReadOnlyField = true;

//Update the List item again
 currentitem.Update();
 //update the List again
 currentList.Update();

}
}

Inside the above code, you can see that first of all, I set the property of current site’s allowUnsafeUpdates to True which will allow you to change the read-only mode of a field. I have used the User Profile service to set the value of People Picker Control (in this case, the field name is “Direct Supervisor”). After setting the value of the field, I updated the List item and the list before changing back the read-only mode to be “True” again. Afterwards, I updated the current item and current list once again which ensures that the update will take place without any such error which happened previously.


			
Advertisements

Create a Dynamic User Control using Silverlight Client Object Model –upload Image, Add, Create, Delete both List and List items and populate current items [Part 2]

In my last post, I talked about how we can create new list, delete a list using Client Object Model. In this part, I am going to talk about how to create new List Items, retrieve those, update and delete items. I will also talk about how to upload an image directly to an Image Column of a list from the same Silverlight user control.

Retrieving List Items:

If you read the previous post, I had one column called “category” from the six fields that I created for the List “TechnoProducts”. In order to populate the drop down box “Category”, in this particular example, I click on “Get List Items” button to populate my user control fields. If there is no existing item in the list, it will only populate the drop down box with some predefined categories that I mentioned while creating the list.

Here, I wanted to show the titles of each List Items once you click “Get List Items” button and then after clicking on each item, I wanted to show the details.

For this to happen, I added the following code inside my event handler for clicking on Get Items Button.

private void txtGetItems_Click(object sender, RoutedEventArgs e)
{
    if (listProducts.Items.IsReadOnly)
        listProducts.ItemsSource = null;
    else
        listProducts.Items.Clear();
    CreateProgressBar("Getting Product Items..");
    GetItems();
}

Here, as you can see first of all, I am clearing the existing values of Products List and then creating a progress bar to let the user know about what is going to happen and then I am calling a method to get the items which is as below:

private void GetItems()
{
    var web = context.Web;
    getList = web.Lists.GetByTitle("TechnoProducts");
    context.Load(getList);

    techItems = getList.GetItems(CamlQuery.CreateAllItemsQuery());
    context.Load(techItems);
    context.ExecuteQueryAsync(getItemSuccess, failedCallback);
}
private void getItemSuccess(object sender, ClientRequestSucceededEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        foreach (var item in techItems)
        {
            item.Tag = item["Title"].ToString();
        }
      
        if (listProducts.Items.IsReadOnly)
            listProducts.ItemsSource = null;
        else
            listProducts.Items.Clear();
      
        listProducts.ItemsSource = techItems;
        cbCategory.ItemsSource = new string[] { "Electronics", "Desktop Pc", "Laptop", "Mobile", "PC Accessories" };
        
    });
}

In this above code, I have called my List and then stored the values of the List items in a ListItemCollection object which I declared globally. Inside the successfulcallback event handler, at first, I have stored the Title of each list item inside the Tag property which I used to display the names inside Products ListBox. I set the data source of my ListBox as this ListItemCollection object and populated the drop down box with the string array.

Displaying the details of each List Item:

After retrieving the list items, I now wanted to show the details of each items. So if there is any list item, I am going to show it inside the user control. I will talk about how I added these items shortly after this.

   private void listProducts_SelectionChanged(object sender, SelectionChangedEventArgs e)
   {
       lblImageStatus.Content = string.Empty;
       if (listProducts.SelectedIndex == -1)
       {
           txtTitle.Text = string.Empty;
           txtModel.Text = string.Empty;
           txtStock.Text = string.Empty;
           txtUnitPrice.Text = string.Empty;
           cbCategory.SelectedIndex = 0;
           chkDiscontinued.IsChecked = false;
           productImg.Source = null;
           txtTitle.Focus();
       }
       else
       {
           var item = (ListItem)listProducts.SelectedItem;
           txtTitle.Text = item["Title"].ToString();
           txtModel.Text = item["ModelNo"].ToString();
           txtStock.Text = item["InStock"].ToString();
           txtUnitPrice.Text = item["UnitPrice"].ToString();
           var chk = (bool)item["Discontinued"];
           chkDiscontinued.IsChecked = chk;
           
           BitmapImage img = new BitmapImage();
           var url=(FieldUrlValue)item["Image"];
           if (url != null)
           {
               string urlvalue = url.Url.ToString();
               productImgUrl = urlvalue;

               Uri uri = new Uri(urlvalue);
               img.UriSource = uri;
           }
           if (img != null)
               productImg.Source = img;
       
           var cbItems = cbCategory.Items;
           for (int i = 0; i < cbItems.Count; i++)
           {
               string cbItem = cbItems[i].ToString();
               if (cbItem == item["Category"].ToString())
               {
                   cbCategory.SelectedIndex = i;
                   break;
               }
           }

       }

   }

I added the code inside the Selection Changed Event handler and what I did was checked the Mode of the current state whether it is in Add Items state or not. If not, then I went with showing each property of a selected list item. Notice that, to show the Image of an specific item, I created a BitmapImage object to get the property and display inside my user control where I set the source as current item’s Image. Lastly, I set the current category for that item which I tried to match using a for loop (There are better ways to do it though!).

This is a screenshot of how it looks like once I selected a List item from the ListBox:

image

Adding a New List Item:

For adding a new item, I wanted to clear out my user control, so I added the following code:

private void txtAddItems_Click(object sender, RoutedEventArgs e)
{
    listProducts.SelectedIndex = -1;
    productImgUrl = string.Empty;
}

And after filling out each field inside the user control and clicking the save button, I get the following screen:

image

After adding an item, I also added a notification message whether or not the item was added in the list. Now, here comes the big part: To save the list item based on its mode whether adding or editing an item.

Saving and Updating a List Item:

Below is the code for event handler of Save button:

private void btnSave_Click(object sender, RoutedEventArgs e)
{
    ListItem item = null;
    if (listProducts.SelectedIndex == -1)
    {                        
        ListItemCreationInformation lci = new ListItemCreationInformation();
        item = getList.AddItem(lci);
    }
    else
    {       
        item = (ListItem)listProducts.SelectedItem;   
    }
   
    if (imgFile != null)
    {
        context.Load(web);
        List picLib = web.Lists.GetByTitle("RadiantProducts");
        context.Load(picLib);

        byte[] bFile = ReadFully(imgFile.OpenRead());
        FileCreationInformation file = new FileCreationInformation();
        file.Content = bFile;
        string picUrl = imgFile.Name.ToString();
        file.Url = "http://www.teamradiant.net/RadiantProducts/&quot; + picUrl;
        productImgUrl = file.Url;
        file.Overwrite = true;
        Microsoft.SharePoint.Client.File newFile = picLib.RootFolder.Files.Add(file);
        context.Load(newFile);
        context.ExecuteQueryAsync(succeedCallback, failedCallback);
    }

    item["Title"] = txtTitle.Text;
    item["Category"] = cbCategory.SelectedItem;
    item["ModelNo"] = txtModel.Text;
    item["UnitPrice"] = txtUnitPrice.Text;
    item["InStock"] = txtStock.Text;
    item["Discontinued"] = chkDiscontinued.IsChecked;
    item["Image"] = productImgUrl;
    item.Update();
    if(listProducts.SelectedIndex == -1)
        CreateProgressBar("Adding Product Item..");
    else
        CreateProgressBar("Updating Product..");
    context.ExecuteQueryAsync(saveSuccess, failedCallback);

}
private void saveSuccess(object sender, ClientRequestSucceededEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        GetItems();
        lblImageStatus.Content = "Saved Successfully";
    });
}

As you can see here, I checked the mode whether I am adding or updating an Item. If it is in Add mode, I created a ListItemCreationInformation object and adding this info to my existing List getList which I got from calling GetItems method and assigned it to a new List item object.

To add a new image, I declared a FileInfo variable imgFile which I used to check the mode of adding or updating. To add an image directly to my List TechnoProducts, I have created an Picture Library List named RadiantProducts. As you might know that, in SharePoint, we can only add an existing Image URL to an Image Column of a list; so, behind the scene, first of all, I added the selected image using an Open File Window, read the file as Bytes and after that, I set the URL of this image using the name of the Image File. Then, I stored the image directly to the root folder of the Picture Library. Here is the code to read the image as a binary file:

public static byte[] ReadFully(Stream input)
  {
      byte[] buffer = new byte[16 * 1024];
      using (MemoryStream ms = new MemoryStream())
      {
          int read;
          while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
          {
              ms.Write(buffer, 0, read);
          }
          return ms.ToArray();
      }
  }

After that, it is pretty simple as I just assigned the current property of the List item with the values from the user control and updated it. Notice that, for the image I now can set the Image Url as I stored it beforehand.

Here is the code for selecting an image which opens up a Open File Dialog window:

private void btnSelect_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Filter = "JPEG Files (*.jpg)|*.jpg|PNG Files (*.png)|*.png";
    ofd.Multiselect = false;
    ofd.ShowDialog();
    imgFile = ofd.File as FileInfo;
    if (imgFile != null)
    {
        image = new BitmapImage();
        image.SetSource(imgFile.OpenRead());
        productImg.Source = image;
    }
    
}

In this above code, it will just open up a window to select a File of JPG/PNG type and after selecting it will display in the Image control as I set the source after reading the file. After saving a list item, I called the previously defined GetItems method to retrieve the updated List where it will also show if there is any newly added item.

Deleting a List Item:

Following is the code by which I can delete a selected list item after clicking on “Delete Item”:

private void btnDeleteItem_Click(object sender, RoutedEventArgs e)
{
    var web = context.Web;
    var item1 = (ListItem)listProducts.SelectedItem;
    string delTitle = item1["Title"].ToString();
    var delList = web.Lists.GetByTitle("TechnoProducts");
    var query=new CamlQuery();
    query.ViewXml = "<View>" +
        "<Query>" +
        "<Where><Eq>" +
        "<FieldRef Name='Title'/>" +
        "<Value Type='Text'>" + delTitle + "</Value>" +
        "</Eq></Where>" +
        "</Query>" +
        "<ViewFields>" +
        "<FieldRef Name='ID'/>" +
        "<FieldRef Name='Title'/>" +
        "</ViewFields>" +
        "</View>";
     

     techItems = delList.GetItems(query);
     context.Load(techItems);
     context.ExecuteQueryAsync(delCallSuccess, failedCallback);
   
}

 

In this above code, I am getting the current web from current client context. Then, I created a variable to get Selected List Item from the List Box. After that, I stored the Title of that item in a string variable which I used inside the CAML query later. The CAML query will retrieve ID and Title of an item from the List which matches the Title of the selected item. I then stored in inside a globally defined ListItemCollection which I used before and then called ExecuteQueryAsync . Following is the code for the successful callback of this action:

private void delCallSuccess(object sender, ClientRequestSucceededEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        foreach (ListItem item in techItems)
        {
            item.DeleteObject();
        }
        context.ExecuteQueryAsync(deleteItemSuccess, failedCallback);
    });
}
private void deleteItemSuccess(object sender, ClientRequestSucceededEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        GetItems();
        lblImageStatus.Content = "Item Deleted Successfully";
    });
}

In this above code, I deleted the item using DeleteObject method from SharePoint’s Client Object Model API. As I am only deleting one item, the for loop here will only get executed once. After that, I have another callback where for the successful handler, I am calling GetItems  method to show the updated List Items with a message that the item was deleted successfully. Here is the screenshot:

image

Here, I am deleting the product Samsung and right after that I get the following screen I don’t see this item anymore inside my List Box.

image

As you can see, after deleting the item, I can see the updated items Title inside the List Box.

That’s all for today. Next time, I will talk about how you can use Client Object Model using Javascript/ECMAscript. Thanks for reading and I will always appreciate if you can come up with a better solution and technique.