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 1]

Today, I am going to discuss about using the Silverlight Client Object Model in SharePoint 2010. You can actually develop scintillating user controls using Silverlight in SharePoint 2010. I am going to discuss the development work in two phases. In this part, I am going to show you how you can create and delete a List.

Alright, to begin with, let me clarify few things: Silverlight Client Object Model almost works in the same way as .NET Managed mode except in few areas, one of them is executing the request. In .NET Managed mode, the actual request and response happens synchronously whereas in Silverlight, it is Asynchronous. Therefore, we have to use ExecuteQueryAsync instead of ExecuteQuery. ExecuteQueryAsync must have two event handlers as parameters: one for a successful callback and another for a failed callback. Another big difference is that in .NET Managed mode, you code set the Authentication mode to default, form based, windows. But, here you do not have such option. We have two options to test our Silverlight user control in COM:

First method: To upload the build "XAP” file in SharePoint’s document library and then add a Silverlight Web Part and define the URL there.

Second method: To test the user control right from the VS2010, we need to create an XML file inside the Web Application Root (inside Intepub\wwwroot\…[Your Port No. for Web App] Directory] and name that file as ClientAccessPolicy.xml as by default SharePoint does not allow cross domain access [When you test your user control using VS2010, it uses localhost to test and it is considered as Cross Domain against your SharePoint web app].

Let me begin with explaining the demo:

First of all, I created a new Silverlight Project from Visual Studio 2010. While creating the project, VS will prompt you whether you want to add an ASP.NET test web page to test your user control; choose ‘Yes’ and always remember to select ASP.NET 3.5 version as SharePoint does not support framework 4 as of now. After that, your solution explorer should look like this:

image

You can see that I named my project as “DynamicListSilver”. The upper one shows my XAML file and at the bottom you can see the Test Page with a web.config which was automatically created by VS.

Now, I have designed my User Control as below:

image

Here, at the top, you can see some buttons which are self-explanatory. Beneath those buttons, I have one List Box to show my current List items and a dynamic Progress Bar. At the right side, the details of a particular list item will be shown once an item is selected from the List Box. Below the Product Image label, there is a status bar label which will show the messages from successful and failed callback and also display the actual happening of a list item and the list. Beneath that, right above the Save button, I have added an Image control which will be displayed for the current Item if any image exists for that and after selecting an Image locally to upload using “Select” button which will open up a File Browser Dialogue.

Here is the code for this particular user control:

<UserControl x:Class="DynamicListSilver.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008&quot;
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
    mc:Ignorable="d"
    d:DesignHeight="483" d:DesignWidth="604" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" Loaded="UserControl_Loaded">

    <Grid x:Name="LayoutRoot" Background="White" Height="488" Width="577">
        <StackPanel Height="485" HorizontalAlignment="Left" Margin="10,0,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="561"></StackPanel>
        <Canvas Name="canvas1" Margin="10,0,0,12" HorizontalAlignment="Left" Width="555">
            <Button Content="Get List Items" Height="23" Name="txtGetItems" Width="87" IsTabStop="True" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Center" IsEnabled="True" IsHitTestVisible="True" Canvas.Left="81" Canvas.Top="0" Click="txtGetItems_Click" />
            <Button Content="Create List" Height="23" Name="txtCreate" Width="75" Canvas.Left="0" Canvas.Top="0" Click="txtCreate_Click" />
            <Button Canvas.Left="383" Canvas.Top="0" Content="Delete List" Height="23" Name="btnDelete" Width="75" Click="btnDelete_Click" />
            <ListBox Canvas.Left="6" Canvas.Top="29" Height="401" Name="listProducts" ItemsSource="{Binding}" Width="162" SelectionChanged="listProducts_SelectionChanged" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Tag}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>

            </ListBox>
            <dataInput:Label Canvas.Left="259" Canvas.Top="29" Height="26" Name="label1" Width="139" Content="List item Details" FontWeight="ExtraBlack" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="62" Height="24" Name="label2" Width="66" Content="Title" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="92" Height="24" Name="label3" Width="66" Content="Category" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="122" Height="24" Name="label4" Width="66" Content="Unit Price" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="155" Height="24" Name="label5" Width="66" Content="Stock" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="214" Height="24" Name="label6" Width="74" Content="Discontinued" />
            <TextBox Canvas.Left="259" Canvas.Top="62" Height="23" Name="txtTitle" Width="189" />
            <ComboBox Canvas.Left="261" Canvas.Top="92" Height="23" Name="cbCategory" Width="137" />
            <TextBox Canvas.Left="261" Canvas.Top="122" Height="23" Name="txtUnitPrice" Width="120" />
            <TextBox Canvas.Left="261" Canvas.Top="151" Height="23" Name="txtStock" Width="89" />
            <CheckBox Canvas.Left="261" Canvas.Top="214" Content="Yes/No" Height="24" Name="chkDiscontinued" Width="69" />
            <Button Canvas.Left="261" Canvas.Top="436" Content="Save" Height="23" Name="btnSave" Width="75" Click="btnSave_Click" />
            <Button Canvas.Left="174" Canvas.Top="0" Content="Add Item" Height="23" Name="txtAddItems" Width="104" Click="txtAddItems_Click" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="241" Height="20" Name="label7" Width="84" Content="Product Image" />
            <Button Canvas.Left="261" Canvas.Top="241" Content="Select.." Height="23" Name="btnSelect" Width="75" Click="btnSelect_Click" />
            <Image Canvas.Left="259" Canvas.Top="293" Height="137" Name="productImg" Stretch="Fill" Width="237" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="185" Height="24" Name="label8" Width="74" Content="Model No." />
            <TextBox Canvas.Left="261" Canvas.Top="185" Height="23" Name="txtModel" Width="120" />
            <dataInput:Label Canvas.Left="174" Canvas.Top="267" Content="" Height="20" Name="lblImageStatus" Width="375" />
            <Button Canvas.Left="283" Canvas.Top="0" Content="Delete Item" Height="23" Name="btnDeleteItem" Width="94" Click="btnDeleteItem_Click" />
        </Canvas>
    </Grid>
</UserControl>

Let me now go through with each segment of this solution:

CREATING A LIST

To create a list in Client Object Model, there is few differences than Server Object Model as we define the List Columns and fields inside XML directly. In this demo, on my User Control Load event, I added the following code the load the current Client Context:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            context = ClientContext.Current;
            if (context == null)
                context = new ClientContext("http://www.teamradiant.net/&quot;); ;
            web = context.Web;
        }

Obviously, context and web objects were declared globally to access it from any method. As you can see, the code here is exactly as we write in .NET Managed mode. Now, when I wanted to create the list in the event handler of the Button “Create List” as below:

Creating List Snippet
listProducts.Items.Clear();
CreateProgressBar("List is getting created");

var lci = new ListCreationInformation();
lci.Title = "TechnoProducts";
lci.Description = "A List of Electronics & Techno Products";
lci.QuickLaunchOption = QuickLaunchOptions.On;
lci.TemplateType = (int)ListTemplateType.GenericList;
list=web.Lists.Add(lci);
var fields = list.Fields;

var titleField = fields.GetByTitle("Title");
titleField.Title = "ProductName";
titleField.Update();

As you can see from the above code that first of all, I cleared the List Box (on the left side). Then, I created a Progress Bar which will be populated dynamically inside the list box.

When you create a custom List in SharePoint, by default, it creates the “Title” field automatically. So here, I changed the column name “Title” to “ProductName”. Now, updating the field won’t actually happen unless we call the executequeryasync method. I have not done that yet as I need to some more fields for my List.

Here is the code for the progress bar:

public void CreateProgressBar(string progressContent)
{
    
    if (listProducts.Items.IsReadOnly)
        listProducts.ItemsSource = null;
    else
        listProducts.Items.Clear();

    listProgress = new ProgressBar();
    listProgress.Width = 100;
    listProgress.Height = 15;
    listProgress.Value = 10;
    listProgress.Maximum = 100;
    listProgress.Background = new SolidColorBrush(Colors.Gray);
    listProgress.Foreground = new SolidColorBrush(Colors.White);
    _timer.Duration = TimeSpan.FromMilliseconds(10);
    _timer.Completed += new EventHandler(_timer_Completed);
    _timer.Begin();

    Label lblProgress = new Label();
    lblProgress.Content = progressContent;
    listProducts.Items.Add(lblProgress);
    listProducts.Items.Add(listProgress);
}

And, here is the code for the Timer Event Handler:

void _timer_Completed(object sender, EventArgs e)
      {
          if (listProgress.Value < listProgress.Maximum)
          {
              listProgress.Value++;
              _timer.Begin();
          }
      }

As I used the progress bar on various occasions, I declared the following objects globally:

Storyboard _timer = new Storyboard();
 ProgressBar listProgress;

Next, I need to add the other columns or fields for my list. Now, to do that I will also need to pass a schema for each field that I add. As the schema is pretty big and hard to memorize, what I did was created a copy of exact same list in SharePoint web front end and got the values for schema using Server Explorer. [Open Server Explorer-> Go to your Web App-> Site Collection –> Site –> Fields and from the properties window, get the schema xml]

Then, I added the fields as below:

var categoryField = fields.AddFieldAsXml(@"<Field Type='Choice' DisplayName='Category' Required='TRUE' EnforceUniqueValues='FALSE' Indexed='FALSE' Format='Dropdown' FillInChoice='FALSE' StaticName='Category' Name='Category'><Default>Electronics</Default><CHOICES><CHOICE>Electronics</CHOICE><CHOICE>Desktop PC</CHOICE><CHOICE>Laptop</CHOICE><CHOICE>Mobile</CHOICE><CHOICE>PC Accessories</CHOICE></CHOICES></Field>", true, AddFieldOptions.DefaultValue);
var unitPriceField = fields.AddFieldAsXml(@"<Field Type='Currency' DisplayName='UnitPrice' Required='TRUE' EnforceUniqueValues='FALSE' Indexed='FALSE' Decimals='2' LCID='1033' StaticName='UnitPrice' Name='UnitPrice' />", true, AddFieldOptions.DefaultValue);
var stockField = fields.AddFieldAsXml(@"<Field Type='Number' DisplayName='InStock' Required='FALSE' EnforceUniqueValues='FALSE' Indexed='FALSE' Decimals='0' StaticName='InStock' Name='InStock' />", true, AddFieldOptions.DefaultValue);
var modelField = fields.AddFieldAsXml(@"<Field Type='Text' DisplayName='ModelNo' Required='TRUE' EnforceUniqueValues='FALSE' Indexed='FALSE' MaxLength='255' StaticName='ModelNo' Name='ModelNo' />", true, AddFieldOptions.DefaultValue);
var discontinuedField = fields.AddFieldAsXml(@"<Field Type='Boolean' DisplayName='Discontinued' EnforceUniqueValues='FALSE' Indexed='FALSE' StaticName='Discontinued' Name='Discontinued'><Default>0</Default></Field>", true, AddFieldOptions.DefaultValue);
var imageField = fields.AddFieldAsXml(@"<Field Type='URL' DisplayName='Image' Required='FALSE' EnforceUniqueValues='FALSE' Indexed='FALSE' Format='Image' StaticName='Image' Name='Image' />", true, AddFieldOptions.DefaultValue);
context.ExecuteQueryAsync(listcreatesuccess, failedCallback);

Now, when you add the Schema XML from the existing fields of a List there are some extra things which you should delete (generated IDs) and keep stuff clean which looks similar to the above code. I have added 6 more fields in this list which are: Category (Drop Down Choice Type), Unit Price (Currency type), InStock (Number type), ModelNo (Number Type), Discontinued (Yes/No Checkbox type) and an Image (Image type) which will hold the Image url for list item.

As you can see at the bottom of the code, I have called ExecuteQueryAsync which will call the server asynchronously and also passed two event handlers as parameters. Below is the code for successful callback event handler:

private void listcreatesuccess(object sender, ClientRequestSucceededEventArgs e)
 {
     this.Dispatcher.BeginInvoke(() =>
     {

         listProducts.Items.Clear();
         var msg = string.Format("{0} List was created at: {1}", list.Title, DateTime.Now.ToLongTimeString());
         lblImageStatus.Content = msg;
     });
 }

and here is for the failed callback:

private void failedCallback(object sender, ClientRequestFailedEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        var msg = "Error: " + e.Exception.ToString();
        listProducts.Items.Clear();
        listProducts.ItemsSource = null;
        listProducts.Items.Add(msg);

    });
}

That’s it. Now, if you click on “Create List”, your list should be created and you should see the progress bar and a message whether or not the list was created successfully with current time.

listcreated

Above one is a screenshot while creating the list and after completion, a message will be displayed if the creation was successful.

Let me now go through with deleting this list when I will click on “Delete List”.

The code for deleting this list is much simpler than creating it. Here it is:

private void btnDelete_Click(object sender, RoutedEventArgs e)
{
    CreateProgressBar("Deleting List…");
    context.Load(web, w => w.Title);
    context.Load(web.Lists);
    deleteList = web.Lists.GetByTitle("TechnoProducts");
    deleteList.DeleteObject();
    context.ExecuteQueryAsync(listdeletesuccess, listdeletefailure);
}
private void listdeletesuccess(object sender, ClientRequestSucceededEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        listProducts.Items.Clear();
        var msg = string.Format("List was Deleted at: {0}", DateTime.Now.ToLongTimeString());
        lblImageStatus.Content = msg;
    });
}
private void listdeletefailure(object sender, ClientRequestFailedEventArgs e)
{
    this.Dispatcher.BeginInvoke(() =>
    {
        listProducts.Items.Clear();
        var msg = "Error: " + e.Exception.ToString();
        lblImageStatus.Content = msg;

    });
}

As you can see I just called the List using web.Lists.GetByTitle  and then deleted the object from the List deleteList which I declared previously.

Here is a screenshot for deleting this list:

deletelist

You can see the message here after the successful callback for deleting the List. That’s it for this part. In the next part, I am going to discuss about adding, modifying and deleting a List item and also adding an Image directly to this List.  [To Be Continued…]

Advertisements