Using ListData service in SharePoint 2010

For the past few weeks, I have been using ListData.svc, SharePoint Client object model and Jquery to create custom web parts which display some information from a list. First things first, ListData service reside in http:///_vti_bin/ListData.svc. So as an example, Let’s say that I have an employee list with First Name and Last Name columns. In order for me to get the list of employees from Employee list, the url would be http://samplesite/_vti_bin/ListData.svc/Employee.


var listUrl = 'http://samplesite/_vti_bin/ListData.svc/Employee';
$.getJSON(listUrl, function(data) {
   for (var i=0; i<data.d.results.length; i++)
   {
       var employee = data.d.results[i];
       alert(employee.FirstName);
   }
});

We can also filter the results that we would like to retrieve from the list by using the $filter query string or sort the results by using the $orderby. The example below filters the list by using the list item Id. For more information on what query strings that can be used, please have a look at this page in MSDN.

var listUrl = 'http://samplesite/_vti_bin/ListData.svc/Employee?$filter=Id eq 15';
$.getJSON(listUrl, function(data) {
   for (var i=0; i<data.d.results.length; i++)
   {       
   }
});

We can also use ListData service to create a new list item or update an existing item. Credits go to Anatoly Mironov for explaining on how to perform save and update operations. Following our example above, let’s say that we want to create an entry for a new employee

var newEmp = {};
newEmp.FirstName = "Joe";
newEmp.LastName = "Blogg";

var body = Sys.Serialization.JavaScriptSerializer.serialize(newEmp);

$.ajax({
            type: "POST",
            contentType: "application/json; charset=utf-8",
            processData: false,
            url: listUrl,
            data: body,
            dataType: "json",
            success: function () { alert('succeeded');},
            error: function (xhr, status, error) { alert(xhr.responseText); }
        });
    }

Assuming that the create operation above succeeded and the new employee’s id is 20. We can update the employee list item as such

var emp = {};
emp.FirstName = "Joe";
emp.LastName = "B";

var body = Sys.Serialization.JavaScriptSerializer.serialize(emp);
$.ajax({
            type: "POST",
            contentType: "application/json; charset=utf-8",
            processData: false,
            headers: {
                "If-Match": "*",
                "X-HTTP-Method": "MERGE"
            },
            url: listUrl + "(20)",
            data: body,
            dataType: "json",
            success: function () { alert('succeeded');},
            error: function (xhr, status, error) { alert(xhr.responseText); }
        });

Notice the url and the headers for the AJAX call. The url that I specified points to the record that I just created and then I specified ‘MERGE’ to indicate that this is an update operation.

Advertisements

Retrieving SP user info using Client Object Model

Recently I built a visual web part to show current user’s information (Picture, Name, etc). Instead of using SharePoint server object model, this time I choose to use client OM. First I need to get the id of the logged-in user, which can be retrieved from _spPageContextInfo.userId (Ted Pattison has posted an excellent article explaining this variable). Next step is to get the client context.

var context = new SP.ClientContext.get_current();
    var web = context.get_web();

    var userInfoList = web.get_siteUserInfoList();

    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ID\' />' +
                                '<Value Type=\'Number\'>' + userId + '</Value></Eq>' +
                                '</Where></Query><RowLimit>1</RowLimit></View>');

    this.userListItem = userInfoList.getItems(camlQuery);

    context.load(this.userListItem);

    context.executeQueryAsync(
            Function.createDelegate(this, onSucceeded),
            Function.createDelegate(this, onFailed));

The onSucceeded function is called when the query runs successfully, otherwise the onFailed function will be executed.


function onSuceeded(sender, eventArgs)
{
        var item = this.userListItem.itemAt(0);
        var name = item.get_item('Name');
        var userName = "Name";

        if (name) {
            userName = name;
        }

        alert(userName);
}

One last thing to remember is to run the code after SP.js is loaded

SP.SOD.executeOrDelayUntilScriptLoaded(LoadUserInfo, 'SP.js');

Programmatically Connect SharePoint Web Parts

In this post I am adding two web parts into a page programmatically and then connect them by using SPLimitedWebPartManager.SPConnectWebParts. Lately, I have been using Performance point web part and QueryStringFilter web part so I am going to use them for this example.

First step is to create an instance of the two web parts :


//Create an instance of the PerformancePoint web part 
ReportViewWebPart wp = new ReportViewWebPart();
wp.LocationUrl = "/TestBI/Lists/PerformancePoint Content/200_.000";

//Create an instance of the query string filter web part
QueryStringFilterWebPart filterwebpart = new QueryStringFilterWebPart();
filterwebpart.ZoneID = "Zone 1";
filterwebpart.DefaultValue = "Perth";
filterwebpart.QueryStringParameterName = "City";
filterwebpart.ID = "g_" + Guid.NewGuid();
filterwebpart.FilterName = "MyFilter";
filterwebpart.Title = "Hello";

Once we have created the web parts, we can add those web parts into a page by using SPLimitedWebPartManager


using (SPLimitedWebPartManager wpm = web.GetLimitedWebPartManager("/TestBI/Dashboards/MyTest/TestPage.aspx",System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared))                                                                     
            {
                wpm.AddWebPart(filterwebpart, "Zone 1", 0);
                wpm.AddWebPart(wp, "Zone 1", 1);                

                var cc = wpm.GetConsumerConnectionPoints(wp);
                var pp = wpm.GetProviderConnectionPoints(filterwebpart);

                var transformer = = new TransformableFilterValuesToParametersTransformer();

            filter.ConsumerFieldNames = new string[] {"SqlReportViewUniqueParameterIdSI1"};
            filter.ProviderFieldNames = new string[] { "MyFilter" };

ConsumerConnectionPoint consumerConnection = null;

                foreach (ConsumerConnectionPoint cpoint in cc)
                {
                    if (cpoint.InterfaceType == typeof(IWebPartParameters))
                        consumerConnection = cpoint;
                }

                ProviderConnectionPoint providerConnection = null;

                foreach (ProviderConnectionPoint ppoint in pp)
                {
                    if (ppoint.InterfaceType == typeof(ITransformableFilterValues))
                        providerConnection = ppoint;

                }

                var conn = wpm.SPConnectWebParts(filterwebpart, providerConnection , wp, consumerConnection, transformer);

                wpm.SPWebPartConnections.Add(conn);
            }

The QueryStringFilterWebPart resides in Microsoft.Office.Server.FilterControls.dll which resides in GAC.

As for the ReportViewWebPart, it resides in Microsoft.PerformancePoint.Scorecards.WebControls.dll which can found in GAC.

SharePoint: Custom provider web part

Got a requirement to read a querystring and pass it to some of the web parts on a page. I thought that this would be easy since I can use the OOTB query string url filter web part to read and then pass the value after setting up the connection.

However, apparently some of the consumer web parts need the value to be in different format. My first thought was to append another querystring with the same value (just different format) but that would not be very nice (especially when the url length is restricted). This is where ITransformableFilterValues comes in handy. The interface is used for web part to web part connections.

Below are an example on how this interface can be implemented.


public class CustomQueryStringFilterWebPart : System.Web.UI.WebControls.WebParts.WebPart, ITransformableFilterValues
    {        
        string _queryStringKey = "category";
		string _filterValue = "";
		
        [WebPartStorage(Storage.Shared)]
        [WebBrowsable(true)]
        [WebDisplayName("Query String Key")]
        [Personalizable(PersonalizationScope.Shared)]
        [Category("Settings")]
        public string QueryStringKey
        {
            get { return _queryStringKey; }
            set { _queryStringKey = value; }
        }      

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            _filterValue = HttpContext.Current.Request.QueryString[_queryStringKey];
        }

        public bool AllowAllValue
        {
            get { return true; }
        }

        public bool AllowEmptyValue
        {
            get { return false; }
        }

        public bool AllowMultipleValues
        {
            get { return false; }
        }

        public string ParameterName
        {
            get { return "Query String Filter"; }
        }

        public System.Collections.ObjectModel.ReadOnlyCollection<string> ParameterValues
        {
            get
            {
                string value = string.Format("[{0}]", _filterValue.ToUpper());
                string[] values = new string[] { value };
                return new ReadOnlyCollection<string>(values);
            }
        }

        [ConnectionProvider("Query String Filter", "ITransformableFilterValues", AllowsMultipleConnections = true)]
        public ITransformableFilterValues SetConnectionInterface()
        {
            return this;
        }        
    }

The code above is a custom web part that implements ITransformableFilterValues. It gets the querystring value and convert it to uppercase.

Add the above web part to a page and to pass the transformed value, connections to the consumer web parts need to be created. Once the connection is setup, everything should just work 🙂

Building custom OData service on SharePoint 2010

Recently I had a task to build a custom OData service for SharePoint, so here are the steps:

First, need to make sure that ADO.Net data service framework is installed on SharePoint server:
ADO.Net for Windows 2008
Or
ADO.Net for Windows 2008 R2

After that, add the following references on the project:

  • System.ServiceModel
  • System.ServiceModel.Web
  • System.ServiceModel
  • System.Runtime.Serialization
  • System.Data.Services
  • System.Data.Services.Client
  • Microsoft.SharePoint.Client.ServerRuntime

The next step is to create a SharePoint Mapped Folder and then select ISAPI. This is the folder where we are going to create our .svc file. Open the svc file and make sure it uses MultipleBaseAddressDataServiceHostFactory.

<%@ ServiceHost Language="C#" Debug="true" Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressDataServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
    Service="TestODataService.EmployeeService,$SharePoint.Project.AssemblyFullName$" %>

In my project, I have created the following classes

[Serializable]
    [DataContractAttribute(IsReference = true)]
    [DataServiceKey("EmployeeId")]
    public class Employee
    {
        [DataMemberAttribute]
        public int EmployeeId { get; set; }

        [DataMemberAttribute]
        public string FirstName { get; set; }

        [DataMemberAttribute]
        public string LastName { get; set; }
    }
public class DataContext
    {
        public IQueryable<Employee> Employees
        {
            get
            {
                List<Employee> employeeList = EmployeeStore.GetEmployees();

                return employeeList.AsQueryable();
            }
        }

        
    }

In this case the DataContext class will be used as the data source type.

The last one is the EmployeeService itself, which inherits from DataService class

[BasicHttpBindingServiceMetadataExchangeEndpoint]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class EmployeeService : DataService<DataContext>
    {
        [WebGet]
        public IQueryable<Employee> GetEmployeeByName(string name)
        {
            var searchResult = EmployeeStore.GetEmployees()
                                        .Where(e => e.FirstName.Contains(name) || e.LastName.Contains(name));

            return searchResult.AsQueryable();
        }

        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }

Please note that I have set access to all entities and service operations to AllRead since I only need to read the data.

To consume the above service, we can add a service reference in our test project and use the url to the service, e.g.: http://localhost/_vti_bin/EmployeeService.svc

To get access to the entities, we can use the data context as below:

var ctx =
              new ServiceReference1.DataContext(new Uri("http://localhost/_vti_bin/employeeService.svc"));
            ctx.Credentials = System.Net.CredentialCache.DefaultCredentials;

            ctx.Employees.ToList().ForEach(e => Console.WriteLine(e.FirstName + " " + e.LastName));

To use the service operations:

var ctx =
              new ServiceReference1.DataContext(new Uri("http://localhost/_vti_bin/employeeService.svc"));
            ctx.Credentials = System.Net.CredentialCache.DefaultCredentials;
var query = ctx.CreateQuery<Employee>("GetEmployeeByName").AddQueryOption("name", "'Blogg'");
            var result = query.Execute();

            result.ToList().ForEach(e => Console.WriteLine(e.FirstName + " " + e.LastName));

Create a new Wiki Page

The code below adds a new wiki page and then inserts a web part into the new page’s rich text editor

SPList pagesList = web.Lists["Pages"];

SPFolder rootFolder = pagesList.RootFolder;

SPFileCollection fileCollection = rootFolder.Files;

string pageUrl = rootFolder.ServerRelativeUrl + "/MyNewPage.aspx";

//Create the page
SPFile newFile = fileCollection.Add(pageUrl, SPTemplateFileType.WikiPage);

//Checkout and add custom web part to the newly created page
newFile.CheckOut();

//Use a new Guid as the web part id
Guid storageKey = Guid.NewGuid();

string wpId = String.Format("g_{0}", storageKey.ToString().Replace('-', '_'));

//Get web part manager
using (SPLimitedWebPartManager manager = newFile.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared))

{

//Initialize the web part and insert it into 'wpz' web part zone.
//Web part zone is hidden in Rich text editor and its name is fixed ('wpz')

MyWebPart webPart = new MyWebPart();

webPart.ID = wpId;

webPart.Title = "My New Page";

manager.AddWebPart(webPart, "wpz", 0);

string content = String.Format(CultureInfo.InvariantCulture
, "<div class=\"ms-rtestate-read ms-rte-wpbox\" contentEditable=\"false\"><div class=\"ms-rtestate-read {0}\" id=\"div_{0}\"></div><div style='display:none' id=\"vid_{0}\"></div></div>"

, new object[] { storageKey.ToString("D") });

SPListItem item = newFile.Item;

item["WikiField"] = content;

item.Update();

}
newFile.CheckIn(string.Empty);

Hope that helps 🙂

Super user for SharePoint Web Application

I found the following warning message in EventViewer :

“Object Cache: The super user account utilized by the cache is not configured. This can increase the number of cache misses, which causes the page requests to consume unneccesary system resources.”

Resolved it by creating 2 user policies for the web application, 1 for super user and 1 for super reader. These users should not be farm administrators nor Application pool accounts.

Steps:
1. Go to Central Administration
2. Click the Application Management
3. Click the Manage web applications
4. Click the Web application and select User Policy from the Policy ribbon bar
5. Click Add Users
6. Select All Zones and click the Next button
7. Type domain\spSuperUser and give the user Full Control permission
8. Click Finish
9. Repeat Step 4 -8 for domain\spSuperReader

After adding the user policies, run the following PowerShell script:

$webApp = Get-SPWebApplication "http://url/&quot;
$webApp.Properties["portalsuperuseraccount"] = "domain\spsuperuser"
$webApp.Properties["portalsuperreaderaccount"] = "domain\spsuperreader"
$webApp.Update()

Please note if you use Claim based authentication the username format will be different.

Hope that helps