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

Dealing with .NET Managed Code in Clinet Object Model

In SharePoint 2010, one of the major changes that Microsoft has brought in development is the Client Object Model which facilitates the developers to use SharePoint 2010 Client APIs and build client based applications such as Windows Application, Silverlight and JavaScript client apps. Today, I am going to show you a nice example of using Client Object Model in .NET Managed code.

For this, first of all, I have populated few tables (Product, Category) from Microsoft Northwind Database as SharePoint lists on my site. In this example, I will deal with two of those tables which are: Product and Category. These are the screenshots of the tables:

image

image

Note that, I have a Lookup Column named ‘Category’ in Product List which is coming from ‘CategoryName’ column of Category List.

After that, I have created a Windows Application in Visual Studio 2010 and designed the following UI:

image

You can clearly see that my intention is to get all the categories and populate them in a Combo box and Products List Box will display all the products for that particular Category and the selected Product will display Product Details inside the Product Details Grid View. I also want to enable selecting multiple products which will make my Grid View more Dynamic so that the number of products you will select from the Product List Box, the same number of Product Details information will be there in the Grid View.

To achieve my goal, I have added the following code inside my Form Load method to initialize the items of Category Combo Box:

Form Load
  context = new ClientContext("http://www.teamradiant.net/&quot;); //Creating New Client Context
  context.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication; //Setting the Authentication Mode
  FormsAuthenticationLoginInfo formLoginInfo=new FormsAuthenticationLoginInfo("morshed","mypassword"); //Providing credentials for Forms Auth
  context.FormsAuthenticationLoginInfo = formLoginInfo; //Passing the credentials to Current Client Context
  web = context.Web;
                  
  var list = web.Lists.GetByTitle("Category"); //Getting the 'Category' List
  var query = new CamlQuery(); //Create a new CAML query
  query.ViewXml = "<View>" +
                 "<ViewFields>" +
                 "<FieldRef Name='CategoryName' />" +
                 "<FieldRef Name='Title' />" +
                 "</ViewFields>" +
                 "</View>"; //CAML Query details
  var items = list.GetItems(query); //Receiving the items from CAML query
  context.Load(items); //Loading the items in Current Context
  context.ExecuteQuery(); //Calling the Client.svc to send the request to SharePoint server and receive the response

  var listData = new List<DictionaryEntry>(); //Creating a Dictionary Data Structure to hold the list items
  foreach (var item in items)
  {
      var data = new DictionaryEntry(item["CategoryName"], item["Title"]); //setting every list item's column value to Key and Value.
     
      listData.Add(data); //Adding the data inside the Dictionary Data Structure object
  }
  CategoryBox.DisplayMember = "Value"; //Set the Display name of Combo Box items to item["Title"]
  CategoryBox.ValueMember = "Key"; //Set the Value of Combo box items to item to item["Key"]
  CategoryBox.DataSource = listData; //Setting the Data Source of Combo Box

You can see from the above code that I am querying for two columns from Category List which are: CategoryName and Title. ‘CategoryName’ is the Lookup column  that I need to pass to the Product List to get the related products for the selected Category.

Now, I add the following code for selected index changed method of the Combo Box:

SelectedIndexChange (ComboBox)
 if (CategoryBox.SelectedIndex == -1)
     return; //If Nothing is selected
 var category = CategoryBox.SelectedValue; //Getting the Selected Value from Category Combo Box

 var list = web.Lists.GetByTitle("Product"); //Getting the 'Product' list from the current site
 var query = new CamlQuery();
 query.ViewXml = "<View>" +
                 "<Query>" +
                 "<Where><Eq>" +
                 "<FieldRef Name='Category'/>" +
                 "<Value Type='Lookup'>" + category+"</Value>" +
                 "</Eq></Where>" +
                 "</Query>" +
                 "<ViewFields>" +
                 "<FieldRef Name='ProductID'/>" +
                 "<FieldRef Name='ProductName'/>" +
                 "</ViewFields>" +
                 "</View>"; //CAML Query details to get 'ProductID' and 'ProductName' for the selected Category
 

 var items = list.GetItems(query); //Get all items from the CAML query

 context.Load(items); //Loading items
 context.ExecuteQuery(); //Calling Client.svc to send the request to the SharePoint server and get the response

 var listData = new List<DictionaryEntry>(); //Creating a Dictionary Data Structure to hold the list items
 foreach (var item in items)
 {
     var data = new DictionaryEntry(item["ProductID"], item["ProductName"]); //setting every list item's column value to Key and Value.
     listData.Add(data); //Adding the data inside the Dictionary Data Structure object
 }
 ProductListBox.DisplayMember = "Value"; //Set the Display name of List Box items to item["Title"]
 ProductListBox.ValueMember = "Key"; //Set the Value of List box items to item to item["Key"]
 ProductListBox.DataSource = listData; ////Setting the Data Source of the List box

Note that, in Client Object Model, unlike the Server Side Object, the actual Loading of list items or data does not happen until we call ExecuteQuery()  which is  synchronous for .NET Managed Code.

Now, To show multiple selected items from the Products list inside the Products Grid View, I have created the following Class:

public class Products
    {
        public string ProductID { get; set; }
        public string ProductName { get; set; }
        public string SupplierID { get; set; }
        public string QuantityPerUnit { get; set; }
    }

To show the selected Products Details in the grid view, I have added the following code block inside the Selected Index Changed method of the Products  List box:

SelectedIndexChange (ListBox)
  1. if (ProductListBox.SelectedIndex == -1)
  2.     return; //If Nothing is selected inside Product List box
  3. var product = ProductListBox.SelectedValue; //get the single selected value of Product List box
  4. string strInProduct = "";
  5.  
  6. var total = ProductListBox.Items.Count; //Total Items inside Product List Box
  7. for(int i=0; i<total ;i++)
  8. {
  9.      if(ProductListBox.SelectedItems.Contains(ProductListBox.Items[i]))
  10.     {
  11.         var product1 = new DictionaryEntry(); //Create a new Dictionary Data Object to hold multiple selected items
  12.         product1=(DictionaryEntry)ProductListBox.Items[i]; //Setting each selected item with Keys and Values
  13.         strInProduct += "<Value Type='Number'>" + product1.Key + "</Value>"; //Modifying the CAML query clause inside 'IN' to check a range of items
  14.     }
  15. }
  16.  
  17. var list = web.Lists.GetByTitle("Product"); //Calling the 'Product' list of current site
  18. var query = new CamlQuery();
  19. query.ViewXml = "<View>" +
  20.                 "<Query>" +
  21.                 "<Where><In>" +
  22.                 "<FieldRef Name='ProductID'/><Values>" +
  23.                 strInProduct +
  24.                 "</Values></In></Where>" +
  25.                 "</Query>" +
  26.                 "<ViewFields>" +
  27.                 "<FieldRef Name='ProductID'/>" +
  28.                 "<FieldRef Name='ProductName'/>" +
  29.                 "<FieldRef Name='SupplierID'/>"+
  30.                 "<FieldRef Name='QuantityPerUnit'/>"+
  31.                 "</ViewFields>" +
  32.                 "</View>"; //Selecting 'ProductID', 'ProductName', 'SupplierID' and 'QuantityPerUnit' for each Product which are IN the range of selected items
  33.  
  34. var items = list.GetItems(query); //Getting the items from CAML query result
  35.  
  36. context.Load(items); //Loading the items in Current Context
  37. context.ExecuteQuery(); //Calling the Client.svc to send request to SharePoint server and get the response
  38.  
  39. List<Products> spProducts = new List<Products>(); //Create a List of 'Products' class type which is already defined
  40. foreach (ListItem item in items)
  41. {
  42.     spProducts.Add(new Products
  43.     {
  44.         ProductID = item["ProductID"].ToString(),
  45.         ProductName=item["ProductName"].ToString(),
  46.         SupplierID=item["SupplierID"].ToString(),
  47.         QuantityPerUnit=item["QuantityPerUnit"].ToString() //Setting each property of 'spProducts' list which is 'Products' class type   
  48.     });
  49. }
  50. gvProductDetails.DataSource = spProducts; //Setting the Data Source for the Grid View with 'spProducts' list

That’s basically it. I have commented about each line of code explaining the details for your understanding.

Now, I will be able to run this application from any machine as this app is using Client Object Model. As long as you use two DLLs in your application (Microsoft.SharePoint.Client & Microsoft.SharePoint.Client.Runtime), you can get the advantage of Client Object Model APIs from any application. Here is a screenshot of the application I discussed until now:

image

As you can see that all the Products for a particular category is showing up inside the list box and the details of those products are being shown inside the Grid View.

Various ways of Customizing the Mobile redirection feature in SharePoint 2010

In SharePoint 2010, there is a great in-built feature for mobile redirection which redirects the users accessing your SharePoint site from any mobile device. Unfortunately, the page where SharePoint redirects is not pretty to look at and also in most cases at enterprise levels, people would not want the users to show the “All Site Content Page” which is the default behavior of mobile redirection in SharePoint 2010. Now, SharePoint 2010 uses Bi-level redirection for mobile device. Those are:

  • IsMobileDevice property inside your web application’s Compat.browser file (Located at C:\inetpub\wwwroot\wss\VirtualDirectories\[Port No. for your web app]\App_Browsers) which is by default set to true.
  • A Hidden Feature called MobilityRedirect which can be activated using PowerShell command:
stsadmn -o activatefeature -name MobilityRedirect -url http://[your site url]

Now, Above methods will result in the redirection of your site for mobile devices which you can also test out from the desktop browser by appending /m/ after your site URL or by appending ?mobile=1. Now, These two will redirect you to two different pages of Mobile View. The first one (/m/) will redirect you to the default.aspx page located at m folder under the All Files of your site and the second one will redirect you to one of the pages (e.g; mblwp.aspx, mblwiki.aspx depending on your site definition and your home page) located inside the following folder:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MOBILE

Let’s say that you don’t want your users to be redirect to the All Site Content Page which is located inside the above location. To do this, there are some tricks:

Firstly, you can grab the URL where the page is being redirected for mobile pages (?mobile=1) from most of the mobile devices and write a conditional statement to redirect the page to your customized homepage.

Secondly, you can write a custom HTTP handler and fool the SharePoint to change its behavior for your web application. (Remember, this will have effect on all the sites and site collections under that particular web application)

Thirdly, you can turn off the IsMobileDevice property inside the browser settings by setting its value to false.

Finally, you can turn off the hidden feature MobilityRedirect

First Method:

Now, First method is the quickest way to change the default behavior of your SharePoint site’s mobile redirection. To do this go the exact location where your site is being redirected which you can grab from the redirected URL from the mobile device or appending ?mobile=1 after your URL. Go inside the 14 hive\TEMPLATE\LAYOUTS\MOBILE and find out that particular aspx page. You will find the following method at the top of the page inside a script:

protected override Microsoft.SharePoint.SPBasePermissions RightsRequired

Change the code inside this method like the following:

get
    {
        string DevUrl = System.Web.HttpContext.Current.Request.Url.OriginalString.ToString();

        if(DevUrl=="http://dev-s2010:8800/" || DevUrl=="http://dev-s2010:8800/_layouts/mobile/mblwp.aspx?Url=%2Fdefault%2Easpx&mobile=1" || DevUrl=="http://dev-s2010:8800/_layouts/mobile/mblwp.aspx?Url=%2Fdefault%2Easpx")
        {
        Response.Redirect(http://www.customurl.com);   // your Custom URL for Mobile Redirection    
        }

        return base.RightsRequired | Microsoft.SharePoint.SPBasePermissions.ViewPages;
    }

Where http://dev-s2010 is my Site URL. In my case, I was being redirected to mblwp.aspx page.

After doing this, your site will be automatically redirected to the site mentioned inside redirection command. Now, remember, this cannot be a normal SharePoint site URL which will again redirect your user to the All Site Content Page.

Second Method:

As because the SharePoint mobile redirect is done at BeginRequest event (which is the first event in the Page Life Cycle of an ASP.NET page) handler in the SPRequestModule HTTP Module which is part of SP2010. You can write a custom HTTP module to mislead SharePoint making it think that you are using a non-mobile browser like below:

public class MobileRequestHttpModule : IHttpModule
{
    private HttpBrowserCapabilities browserCapabilities;
 
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    }
 
    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = sender as HttpApplication;
        browserCapabilities = application.Request.Browser;
        application.Request.Browser = new MobileBrowserCapabilities(browserCapabilities);
    }
 
    void context_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpApplication application = sender as HttpApplication;
        application.Request.Browser = browserCapabilities;
    }
 
    public void Dispose()
    {
    }
}

Using this module we can hook up to the BeginRequest event and temporarily substitute the browser information with our own. The substitute is defined by the MobileBrowserCapabilities class and contains the most of the original information about the current browser except for the one:

public class MobileBrowserCapabilities : HttpBrowserCapabilities
{
    public override bool IsMobileDevice
    {
        get
        {
            return false;
        }
    }
 
    public MobileBrowserCapabilities() { }
 
    public MobileBrowserCapabilities(HttpBrowserCapabilities browserCaps) : this()
    {
        if (browserCaps.Adapters != null && browserCaps.Adapters.Count > 0)
        {
            foreach (object key in browserCaps.Adapters.Keys)
            {
                Adapters[key] = browserCaps.Adapters[key];
            }
        }
 
        Capabilities = browserCaps.Capabilities;
        HtmlTextWriter = browserCaps.HtmlTextWriter;
    }
}

Now, this will also work for all the sites and site collections for that particular web application. So, if you want the default mobile redirection feature for any of your site and site collections, you won’t be able to do that as it works at web application level. To read about more about this particular technique, follow This Article mentioned by Waldek Mastykarz.

Third Method:

If you want to disable the mobile redirection for your site at the web application level (For all sites and site collections of that Web App), there is another way by which you can permanently disable the Mobile Redirection which is to change the property of IsMobileDevice  inside Compat.browser inside the following directory:

C:\inetpub\wwwroot\wss\VirtualDirectories\[ Port No of Web App]\App_Browsers

Find the following line under every browser settings category:

<capability name="isMobileDevice"                    value="true" />

Change the value to value=”false” for every section of browser types.

This is work right away without resetting the IIS.

Fourth method:

If you want to disable the hidden feature for MobilityRedirect, you can do so by running the following command in PowerShell:

stsadmn -o deactivatefeature -name MobilityRedirect -url http://[your site url]

This method will probably not work for the Internet Facing sites as it will still redirect the users to the All Site Content Page.

Now, there is another way for redirection recommended by MSDN which is only possible for the homepage of your site. But for that to happen, one needs to type in upto default.aspx or append /m/ after the URL. The article can be found here ( I have already tested this method and it works).

So far, these are the techniques I have discovered Mobile Redirection for a SharePoint site. I would appreciate if someone has a different or better solution. Till then, we have to be satisfied with these inconvenient methods provided by Microsoft. Good Luck!

Few things to remember While Developing Sandboxed solutions

SharePoint 2010 provides a great new feature called “Sandboxed Solutions” which separates the User Code Host Process from IIS Worker Process and let the Site Collection Administrator deploy the solution easily from Solution Gallery without direct intervention or concern of Farm Administrator and thus it does not affect the application pool while maintaining stability across the Farm. But, Sandboxed Solution has got some limitations that we should remember:

1. You cannot develop any Visual Web Part, Application pages etc. as Sandboxed Solutions which requires direct access to 14 hive.

2. You cannot use any code which requires elevated privileges. For example: SPSecurity object is not supported in Sandboxed Solution. Often we are used to use SPSecurity.RunWithElevatedPriveleges which runs the segment of code as a system account which is not supported at all in Sandboxed Solution. Although, it will not throw any validation error while building the solution from Visual Studio 2010, it will eventually throw an error saying “Failied to load assembly” while running the feature which tries to execute SPSecurity object after deployment.

3. You cannot access another site collection as Sandboxed Solution restricts you only to Current Site Collection.

4. Normal Debugging method from Visual Studio 2010 will not work for Sandboxed Solution if you have already deployed it. You can debug it in normal way by pressing “F5” before deploying though. In order to debug an already Deployed Sandboxed Solution, you need to attach to Sandboxed Worker Process “SPUCWorkerProcess.exe” manually because it does not run under the IIS Worker Process used by SharePoint also known as w3wp.exe.

Another way of sending Data from External List to Data View Web Part of Form page of a List

Previously, I told about how you can send Filtered data from an external list to a New/Edit form page’s Data View Web Part by connecting the web parts. Today, I will show another easy way to populate Data within a Data View Web Part sent from a External List.

This time around, I have used Inline Coding inside the Form Page (You have to go in Advanced mode to write inline code and modify the web.config for allowing).

Open up your form page and find the following:

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">

I have to use my code inside this tag. So, first of all create some Text Fields which will grab the values of the Data sent from the External list.

<asp:TextBox ID="txtID" runat="server" Text="" Visible="false"/>
<asp:TextBox ID="txtFullName" runat="server" Text="" Visible="false"/>
<asp:TextBox ID="txtEmail" runat="server" Text="" Visible="false"/>
<asp:TextBox ID="txtPhone" runat="server" Text="" Visible="false"/>

Note that, I set the visibility to false as obviously you don’t want to show these on your Forms

For writing C# code inside the form, we have to add a tag like the following:

<script runat="server" type="text/c#">

Now, I need to write the logic inside the Page_Load event as that is when my External List will interact with this form and populate the fields.

protected void Page_Load(object sender, EventArgs e)
        {
         
       //Get the current site
         SPWeb currentWeb = SPContext.Current.Web;
       //Get the current user
         SPUser currentUser = currentWeb.CurrentUser;
        //Get the current user's login name
              string username1 = currentWeb.CurrentUser.LoginName.ToString();
              
      //As current username's format is ABC\username, i need to use substring to get "username"
             string username2 = username1.Remove(0,4); 

      //This username exist in the external List as Upper Case
             string currentUsername=username2.ToUpper(); 
         
      //Elevate the permission of current user to use System Account
         SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                
                    using (SPSite site = new SPSite(SPContext.Current.Web.Url))
                    {
                        using (SPWeb web = site.OpenWeb())
                        {
                        
                        //Get the List of this current form page, in my case it is "Request For Change"
                         SPList list1 = web.Lists["Request For Change"];  
                            
                            bool isMember=false;
                          //Checking the current user whether he belongs to a particular group
                            isMember=SPContext.Current.Web.IsCurrentUserMemberOfGroup(web.Groups["Request For Change Members"].ID);
                            bool isModerator=false;
                            isModerator=SPContext.Current.Web.IsCurrentUserMemberOfGroup(web.Groups["Request For Change Moderators"].ID);
                            bool isAdmin=false;
                            isAdmin=SPContext.Current.Web.IsCurrentUserMemberOfGroup(web.Groups["Request For Change Admin"].ID);

                       //If the user does not belong to any group, redirect him to the main home page of the site
                            if(isMember==false && isModerator==false && isAdmin==false)
                            {
                                Context.Response.Redirect(SPContext.Current.Web.Url.ToString());
                            }

                  //Get the External List, in my case the name is "eBusinessInfo"      
              SPList list=web.Lists["eBusinessInfo"];
                 //Using CAML
               SPQuery theQuery=new SPQuery();

                            theQuery.ViewFields="<FieldRef Name='ID' /><FieldRef Name='FULL_NAME' /><FieldRef Name='EMAIL' /><FieldRef Name='WORK_PHONE' /><FieldRef Name='USER_NAME' />" ;
                            theQuery.Query = @"<Where>
                            <Eq>
                                <FieldRef Name='USER_NAME' />
                                <Value Type='Text'>"+ currentUsername +@"</Value>
                            </Eq>
                            </Where>";
                            //get the Result of CAML query
                            SPListItemCollection foundItem = list.GetItems(theQuery);
                           //Store the ID, Full Name, Email and Phone inside the TextBox that we created before
                            txtID.Text= foundItem[0]["ID"].ToString();
                            txtFullName.Text = foundItem[0]["FULL_NAME"].ToString();
                            string strEmail= foundItem[0]["EMAIL"].ToString();
                            if(foundItem[0]["WORK_PHONE"]==null)
                            txtPhone.Text= "";
                            else
                            txtPhone.Text=foundItem[0]["WORK_PHONE"].ToString();
                            //string strPhone=foundItem[0]["WORK_PHONE"].ToString();
                            if(strEmail != null)
                            
                            txtEmail.Text = foundItem[0]["EMAIL"].ToString();
                            else
                            txtEmail.Text= "N/A";
                            
                        
                            }
                        }
                    
                    });

        }

</script>

Upto now, I have retrieved the value from the external list using a CAML query and stored the value inside the TextBox Fields. Now, In order to use these values inside my form, I have to find the following web part zone:

<WebPartPages:WebPartZone runat=“server” FrameType=“None” ID=“Main” Title=“loc:Main”><ZoneTemplate>

 
add the parameters inside this web part zone:
<ParameterBinding Name="paramID" Location="Control(txtID, Text)" DefaultValue=""/> 
<ParameterBinding Name="paramFullName" Location="Control(txtFullName, Text)" DefaultValue=""/>
<ParameterBinding Name="paramEmail" Location="Control(txtEmail, Text)" DefaultValue=""/>
<ParameterBinding Name="paramPhone" Location="Control(txtPhone, Text)" DefaultValue=""/>

 

Then, add the parameters inside the XSLT right after this web part zone.

<xsl:param name="paramID" />
<xsl:param name="paramFullName" />
<xsl:param name="paramEmail" />
<xsl:param name="paramPhone" />

Now, Instead of using SharePoint:FormField for the desired fields, change those like the following:

<asp:TextBox runat="server" id="ff1{$Pos}" ReadOnly="True" Text="{$paramID}" __designer:bind="{ddwrt:DataBind('i',concat('ff1',$Pos),'Text','TextChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@ID')}"/>

While changing this, do not forget to change “Value” and “ValueChanged” to “Text” and “TextChanged” respectively inside the Tag. That’s it. Now, save the form and see the result!

Hide form fields using JQuery and SharePoint Web Services

There are many occasions when we need to hide certain form fields based on some permission / group settings. Well, there is an easy way of achieving this using a JQuery plugin called “SPServices” which is free to use.

Using this plugin, you can call any in built SharePoint web service, get the value retrieved by the web services and use it in your form easily.

In this example, I have three types of users for a specific list which are “Users”, “Moderators” & “Admin”. I would like to check the current user which group he belongs and based on that I am going to hide/show some fields.

First of all, Download the SPServices plugin from here and then open your form in Advanced Mode using SharePoint Designer 2010 and look for the following line:

<asp:Content ContentPlaceHolderId="PlaceHolderBodyAreaClass" runat="server">

Add the script for SPServices:

<script type="text/javascript" src="http://(YOUR SITE URL)/(SAVED FOLDER FOR SPSERVICES)/jquery.SPServices-0.6.2.min.js"></script>

and add the following code under that

<script type="text/javascript">
    $(document).ready( function ()
    {
                    var isUserOnly = false; //User group member only
            var isModerator= false;
            var isAdmin=false;
                    var MODERATORGROUP = "Moderators"; // SPGroup named “Moderators”
                    var ADMINGROUP = "Admin";  // SPGroup named “Admin”

                    $().SPServices
                    ({
                      operation: "GetGroupCollectionFromUser", //Calling GetGroupCollectionFromUser.asmx web service
                      userLoginName: $().SPServices.SPGetCurrentUser(), //Calling Current User
                      async: false,
                      completefunc: function(xData, Status)
                      {
                                        $(xData.responseXML).find("Groups").each(function() //Retrieving the values from Group method of Web Service
                                        {
                                             $(this).find("Group").each(function()  //Parsing through all the groups the current user belongs to
                                             {
                        //Checking whether Current User belongs to Admin Group

                                                  if(($(this).attr("Name").toLowerCase() === ADMINGROUP.toLowerCase()))
                                                    isAdmin = true;
                        //Checking whether Current User belongs to Moderators Group

                          else if(($(this).attr("Name").toLowerCase() === MODERATORGROUP.toLowerCase()))
                            isModerator=true; 

                        //If Current User does not belong to Moderators or Admin, then definitely a normal user

                          else
                            isUserOnly=true;
                                             });
                                        });
                         }
                   }); // Closing SPServices Method

                   if(isUserOnly)
                   {

                        //Hide fields as this user is Not a member of Modeartora or Admin Group

               jP.Form.readForm("http://teamradiant/sites/rfc/").Comments.hide();
               jP.Form.readForm("http://teamradiant/sites/rfc/").Approval.hide();
                    }
           if(isModerator)
            {
            //Hide Approval field if the current user belongs to Moderators Group

                jP.Form.readForm("http://teamradiant/sites/rfc/").Approval.hide();

            //Show Comments field if the current user belongs to Mdoerators Group

                jP.Form.readForm("http://teamradiant/sites/rfc/").Comments.hide();
            }
            if(isAdmin)
            {

            //Show both fields if the current user belongs to Admin group

                 jP.Form.readForm("http://teamradiant/sites/rfc/").Comments.show();
                 jP.Form.readForm("http://teamradiant/sites/rfc/").Approval.show();
            }

   });
</script>
After that save the Form, and check that it works!

How to create Dynamic Column in a List Event Receiver & set the Column Name with a Dynamic Value and set Value of that Column for Current List item?

Few days ago, I was working on a List where the necessity of creating dynamic column raised. Also, the need to initiate the column name inside an event receiver and setting the value of the newly column for the current item were needed. So, I created a custom (demo) List which is named as “DemoProjects”.

I created the following columns:

Title, Description, Comments, RadComments (for checking the event)

I needed the column to be created for an existing List item. So, I created a new item like the following keeping “Comments” and “RadComments” blank.

image

image

Now, in order to create the event receiver I created a new project of “Event Receiver” type in Visual Studio 2010.

I set the “ItemUpdated” method to True as I need to create the column while updating the list item.

image

After that, Visual Studio automatically creates the method name and parameters for ItemUpdated method

 public override void ItemUpdated(SPItemEventProperties properties)
 {
    base.ItemUpdated(properties);
 }

Now, this base.ItemUpdated runs will run for each custom list. In order to attain my objective, I have modified this method to the following: ( I have tried to clarify all the steps by commenting each line of code )

  1. public override void ItemUpdated(SPItemEventProperties properties)
  2.        {
  3.            if (properties.ListTitle == “DemoProjects”)
  4.            {
  5.                string colComments = “”; // Column Display name
  6.                string colCommentsStr = “”;  //  Internal name of the Column name
  7.                string curUser = “”;  // Current user name
  8.                using (SPSite site1 = new SPSite(properties.SiteId)) //Accessing the current site with current user permission
  9.                {
  10.                    using (SPWeb web1 = site1.OpenWeb(properties.RelativeWebUrl))
  11.                    {
  12.                        curUser = web1.CurrentUser.Name.ToString();  // Getting the current user display name
  13.                    }
  14.                }
  15.                SPSecurity.RunWithElevatedPrivileges(delegate()  // To Elevate the permisson of the current user which runs as a system acc to create the list column
  16.                {
  17.                    using (SPSite site = new SPSite(properties.SiteId))
  18.                    {
  19.                        using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
  20.                        {
  21.                            EventFiringEnabled = false;   // Disabling the events firing to prevent unnecessary actions
  22.                            web.AllowUnsafeUpdates = true;  // To execute actions which are not permitted by default
  23.                            SPList radList = web.Lists[properties.ListId];  // Getting the current List
  24.                            SPListItem project = web.Lists[properties.ListId].GetItemById(properties.ListItem.ID); // Getting the Current List Item
  25.                            colComments = “Comments: “ + curUser; // Setting the column name with current user name
  26.                            if (!radList.Fields.ContainsField(colComments))
  27.                            {
  28.                                radList.Fields.Add(colComments, SPFieldType.Note, false); // Adding new column in current List
  29.                                radList.Update(); // Updating the current List after adding new column
  30.                                Guid guidViewID = radList.Views[“All Items”].ID; // Getting the Guid for “All items” view of current list
  31.                                SPView vw = radList.GetView(guidViewID);
  32.                                vw.ViewFields.Add(colComments);  // Adding the new column in All items view
  33.                                vw.Update();  // Updating the All items view
  34.                                project.Update(); // Updating the current List item
  35.                                base.ItemUpdated(properties);  // Attaching current event to current list item
  36.                                radList.Update();  // Updating the current list
  37.                            }
  38.                            colCommentsStr = radList.Fields[colComments].InternalName.ToString(); // Getting the internal name of newly created column
  39.                        }
  40.                    }
  41.                });
  42.                using (SPSite site2 = new SPSite(properties.SiteId))  // Again accessing the site with current user permission
  43.                {
  44.                    using (SPWeb web2 = site2.OpenWeb(properties.RelativeWebUrl))
  45.                    {
  46.                        SPListItem project1 = web2.Lists[properties.ListId].GetItemById(properties.ListItem.ID); // Getting current List item
  47.                        project1[colCommentsStr] = project1[“Comments”].ToString(); // Setting current item’s new column’s value to Comments column
  48.                        project1[“RadComments”] = “Done!”; // Settting RadComments column’s value to “Done”
  49.                        project1.Update();   // Updating the current list item
  50.                        web2.AllowUnsafeUpdates = false; // After completing the action setting unsafe updates to false
  51.                    }
  52.                }
  53.            }
  54.            EventFiringEnabled = true;  // Enabling event firing for user interaction
  55.            base.ItemUpdated(properties); // Attaching current event to current list item
  56.        }

Now, after deploying this Event Receiver, I edited the previously created new item.

image

Notice that, I kept “RadComment” blank and there are currently no other columns in this list except these four. So, what happened when I clicked “Save”.

See the following window:

image

So, my dynamic column got created now (Comments: Current User name) and the value is also set by the event receiver which made it possible to know who commented the item. Now, if another user edits this item, another “Comment: Username” will be automatically be created and thus I can track all the comments made by the item editors and find out what he commented for that item. That’s pretty cool, isn’t it? Enjoy!!