SharePoint
provides a rich and complex object model for working with SharePoint data.
Although it is challenging to master the details of the SharePoint object
model, an even greater challenge that many developers face is taking their
knowledge of the object model and using it to craft a solution that delivers on
a set of requirements. By its nature, SharePoint solutions are often made up of
loosely coupled components that combine to deliver a full set of functionality,
and it is sometime difficult to figure out how to translate knowledge of the
object model into the set of loosely coupled components in SharePoint. Ask
three SharePoint developers how to solve a particular, nontrivial problem, and
you will likely receive three unique solutions that might all be equally valid.
This chapter attempts
to first cover, in broad strokes, the various customization mechanisms that
SharePoint exposes to developers and then provides a number of sample problems
and describes how they can be solved the “SharePoint Way.” The chapter provides
some basic code snippets illustrating how you can implement various features,
but because this chapter is more about how components can be plugged together
and combined into solutions, the coverage is not exhaustive.
Customizing SharePoint
SharePoint is a
powerful platform that offers many points of extension and customization. You
can’t cover everything that SharePoint encompasses within a single chapter, but
this chapter highlights some of the most common bits of functionality that you
are likely to need to implement in your role as a SharePoint developer.
UI Components
Master Pages and Themes
Master Pages and Themes
Master Pages and
Themes are the primary mechanism in SharePoint to modify the look and feel of
SharePoint. Master Pages can best be thought of as controlling the “edges” of a
SharePoint page. Everything from the breadcrumb and up and from the quick
launch and left is determined by the Out of the Box (OOTB), v4.master Master
Page. This could be extended to control items to the right and below the primary
content area of SharePoint pages as well. One common change typically
implemented by deploying a custom Master Page is the addition of a standard
footer to the bottom of pages in SharePoint.
Themes determine
the color palette utilized by SharePoint. You can build themes using the Theme
Designer available from Site Settings or also by using PowerPoint 2010 themes.
In addition to developing a custom theme, you likely also need a custom .CSS
file to provide the granular control most branding efforts require.
Custom
Web Parts
Web parts are the
primary UI building block within SharePoint. They are modular elements that you
can place on almost any page within SharePoint. SharePoint ships with a large
number of web parts, but it will often be necessary to write your own.
Within SharePoint 2010, there are two types of web parts: visual web parts and traditional web parts. Back in the days of SharePoint 2007, there was only a single type of web part, the traditional web part. Unlike most other visual controls within Visual Studio, web parts did not have any sort of WYSIWYG designer, and developers had to build the UI via code. SharePoint 2010 introduces the ability to build visual web parts that enable the developer to use a WYSIWYG designer to build the UI.
Following is code for a traditional, “Hello World!” web part:
Within SharePoint 2010, there are two types of web parts: visual web parts and traditional web parts. Back in the days of SharePoint 2007, there was only a single type of web part, the traditional web part. Unlike most other visual controls within Visual Studio, web parts did not have any sort of WYSIWYG designer, and developers had to build the UI via code. SharePoint 2010 introduces the ability to build visual web parts that enable the developer to use a WYSIWYG designer to build the UI.
Following is code for a traditional, “Hello World!” web part:
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace Wrox.ObjectModel.TraditionalWebPart
{
[ToolboxItemAttribute(false)]
public class TraditionalWebPart : WebPart
Custom
Web Parts
Web parts are the
primary UI building block within SharePoint. They are modular elements that you
can place on almost any page within SharePoint. SharePoint ships with a large
number of web parts, but it will often be necessary to write your own. Within
SharePoint 2010, there are two types of web parts: visual web parts and
traditional web parts. Back in the days of SharePoint 2007, there was only a
single type of web part, the traditional web part. Unlike most other visual
controls within Visual Studio, web parts did not have any sort of WYSIWYG
designer, and developers had to build the UI via code. SharePoint 2010 introduces the ability to build visual web parts that enable the developer to use a WYSIWYG designer to build the UI.
designer, and developers had to build the UI via code. SharePoint 2010 introduces the ability to build visual web parts that enable the developer to use a WYSIWYG designer to build the UI.
Following is code
for a traditional, “Hello World!” web part:
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace Wrox.ObjectModel.TraditionalWebPart
{
[ToolboxItemAttribute(false)]
public class TraditionalWebPart : WebPart
Customizing SharePoint ❘ 5
{
protected override void CreateChildControls()
{
Label label = new Label();
label.Text = “Hello World!”;
Controls.Add(label);
}
}
}
As stated
earlier, the “visual” aspect of the new Visual Web Part simply refers to the
design time experience. Rather than having to programmatically create the
controls and adding them to the web part as in the preceding example, a Visual
Web Part enables you to drag and drop controls onto the design surface. Figure
1-1 shows what a visual web part looks like in Visual Studio.
.
Figure 1-1: A Visual Web Part design canvas
When placed on a SharePoint page,
the two web parts appear to be nearly identical, as shown in Figure 1-2.
.
Figure 1-2: Web parts
You probably
wonder why traditional web parts continue to exist within SharePoint 2010
because Visual Web Parts provide a better design experience. The answer is
twofold. The first is to maintain backward compatibility and to allow solutions
that can be deployed to both 2007 and 2010 environments. The second is that
because of the way Visual Web Parts deploy, they are not supported within
Sandboxed Solutions. There are some alternative implementations of Visual Web
Parts, such as those found within Microsoft’s Visual Studio 2010 SharePoint
Power Tools, which do work for sandboxed solutions.
Web parts are
primarily applicable when you need to implement functionality that requires a
UI, is meant to be end-user configurable, and is meant for the site owners or
designers to place on pages of their choosing. This may not always be the case
however.
Custom
Application Pages
The other primary
UI building block within SharePoint is the custom application page. These are
standard .ASPX pages that are deployed beneath the _layouts directory within
your SharePoint environment.
Unlike web parts,
custom application pages are standalone pages of self-contained functionality.
Users cannot edit
the contents of the page or add custom web parts. If you don’t want the user to
determine the placement of the control, or you need to link to the page from
another solution element (for example, a custom action, web part, and so on),
application pages are generally the right solution to the problem.
A common use for
custom application pages is for settings pages, which is probably the scenario
in which you see them most used within the OOTB SharePoint screens. For example,
go to the site settings section of any site in SharePoint, and every one of the
settings links take you to an application page.
Creating a custom
application page is as simple as selecting Add ➪ New Item within your
project, and selecting the Application Page type. This creates an .ASPX page
beneath the Layouts directory of your project, as shown in Figure 1-3.
.
Figure 1-3: Application page in Visual Studio
As you can see in
the screenshot, application pages are just like any traditional .ASPX page
written in .NET with a few references added to SharePoint assemblies. Just like
the other pages within SharePoint, application pages make use of a Master Page,
and content placeholder regions are exposed for you to add content to. Figure
1-4 shows a sample application page.
Application pages
are deployed to the Layouts directory and, as such, are not deployed as part of
a feature. This means that application pages are accessible from within any
site within your farm just by appending \_layouts\<path to page>
and SharePoint does not secure them assuming the user has access to the site,
so you need to check user access within your own code.
.
Figure 1-4: Application page in SharePoint
Custom
Lists
SharePoint Lists
provide a flexible and extensible way for users to store data within SharePoint
and is frequently be used by developers as a location to store information as
well. In many instances, these types of “configuration” lists are hidden from
the user, and all interactions with the list data occurs through UI elements
such as web parts and application pages, but there are cases, particularly when
the user must maintain lists of data that you want to expose the list directly
to the user.
In those cases,
it may be sufficient to simply link them directly to the list and use the
default list UI to allow the users to manage the list items. In more
complicated scenarios (consider an example in which you have complex
multicolumn validation requirements or in which you have multilevel dropdowns)
the OOTB UI provided by lists is insufficient.
Custom
List Forms
List forms refer
to the add, edit, and display forms accessible from SharePoint when a user adds
a new list item or clicks on an existing list item and chooses to view or edit
it. Typical scenarios in which you might need to customize these would be if
you need to add complex validation logic that spans across multiple columns in
the list or in which selections made to one column affect those available in
another. However, list forms are only one way in which you can enter list data
within a list. Users interacting with a list through the datasheet view, through
the office information panel, or through Access cannot enjoy the same
experience, so you need to account for these usage scenarios.
Customizing
SharePoint 9
“Custom
Field Types, Content Types and List Definitions,” covers lists and list forms
in great detail and illustrates how to create custom list forms, so they will
not be covered further here, but 2010 provides a number of mechanisms for
customizing these forms: You can use SharePoint Designer, InfoPath, and Visual
Studio to create more robust list forms.
Custom Field Types
Field types are
the basic building blocks of lists and content types. Whenever you add a new
column to a SharePoint list, you select from a variety of predefined column
types such as Currency, Number, and Choice. By selecting these types, you can
decide what types of data can be entered and how that data displays and can be
manipulated. Figure 1-5 shows a standard column creation dialog.
.
Figure 1-5: Creating a column in SharePoint
Creating custom
field types adds to the list of standard types of new columns and enables you
to dictate how information displays and interacts within the standard
SharePoint list UI. They are most appropriate when you need to implement rules
about the types of data fields can contain or the formatting of the display of
your data (masking a Social Security number, for example).
Delegate
Controls
SharePoint
supports delegate controls, which might best be described as functionality placeholders.
Developers can register their own implementation of the functionality that
overrides the OOTB implementation. You can embed many delegate controls within
the default Master Page and include things such as the search box.
As example of how to embed a delegate control in a Master Page is shown in the following code snippet:
As example of how to embed a delegate control in a Master Page is shown in the following code snippet:
<SharePoint:DelegateControl ControlId=”SmallSearchInputBox”
AllowMultipleControls=”false”/>
This snippet
basically tells SharePoint that the developer wants to embed a control called
SmallSearchInputBox into this location, but the developer requires no knowledge
of the implementation of the control.
Delegate controls
can also have a default implementation specified directly within the body as
well.
Following is an
example in the TopNavigationDataSource delegate control from the v4.master:
<SharePoint:DelegateControl runat=”server”
ControlId=”TopNavigationDataSource” Id=”topNavigationDelegate”>
<Template_Controls>
<asp:SiteMapDataSource
ShowStartingNode=”False”
SiteMapProvider=”SPNavigationProvider”
id=”topSiteMap”
runat=”server”
StartingNodeUrl=”sid:1002”/>
</Template_Controls>
</SharePoint:DelegateControl>
You can override
this default implementation of a delegate control by deploying a feature that
contains a control definition that uses the same ID specified in the ControlId
attribute of the delegate control. The elements file would look something like
this:
<?xml version=”1.0” encoding=”utf-8” ?>
<Elements xmlns=”http://schemas.microsoft.com/sharepoint/“>
<Control
Id=”SmallSearchInputBox”
Sequence=”25”
ControlClass=”WroxSearchBox”
ControlAssembly=”Wrox.ObjectModel”/>
</Elements>
The preceding
definition basically tells SharePoint that this control (WroxSearchBox) should
be used anywhere the SmallSearchInputBox delegate control is requested. The
“AllowMultipleControls” attribute of the DelegateControl determines what
happens if multiple implementations of the control exist. If it is set to true,
all controls will be included in the order of their sequence number (lowest to
highest). If it is false, only the control with the lowest sequence number is
used.
Delegate controls
are most useful when you need to replace a bit of OOTB functionality that
exists within multiple locations in SharePoint such as the search box,
navigation providers, and so on. Unfortunately, not every control in the OOTB
Master Pages uses delegate controls, so their use is limited to specific
controls.
Following is a
list of the delegate controls that exist within the v4.master Master Page:
·
AdditionalPageHead
·
GlobalNavigation
·
GlobalSiteLink0
·
GlobalSiteLink2
·
GlobalSiteLink3
·
PublishingConsole
·
SmallSearchInputBox
·
TopNavigationDataSource
·
QuickLaunchDataSource
·
TreeViewAndDataSource
Some of these
delegate controls, such as the TopNavigationDataSource control, are embedded
within content placeholders of the Master Page. This means that page layouts
can replace the entire contents of this section and remove the delegate control
definition. This is an issue covered in more depth when discussing implementing
a global navigation solution in Chapter 11, “Building a Custom Global
Navigation Solution.”
Nonvisual
Components
Visual components
are only half of the story with most SharePoint customizations. Equally
necessary is the ability to implement nonvisual functionality, such as
executing code at periodic intervals (a timer job) or executing code after an
item is added to a list (event handlers or workflow).
Event Handlers
You can attach
event handlers in SharePoint to lists and document libraries and can trigger
them before adding, updating, or deleting items or after you add, update, or
delete items. The event handlers that occur before the event end in “ing” (for
example, ItemAdding), whereas the event handlers that occur after an event
occurred end in “ed” (for example, ItemAdded).
Within the “ing”
events, the developer can stop the action from occurring. So for example, if
you need to implement a rule that enforces that items can be added to a list,
but can never be removed, you could do that with an ItemDeleting event handler
that cancels the action.
Workflow
SharePoint 2007
supported a single type of workflow called a list workflow. These were
workflows associated with a list that you could manually trigger when you
created an item and when you updated an item. Unlike event handlers, you can
trigger list workflows only after an action occurs and only on item addition or
update. You cannot trigger event handlers on a deletion.
Because there is
considerable functionality overlap, determining when to write a list workflow
versus writing an event handler is confusing. Workflows provide some advantages
during the creation process because codeless workflows can be authored via
SharePoint Designer, and workflow actions exist so that developers don’t need
to write custom code. But assuming you need to write code with either
implementation, event handlers are best used for short-running, atomic
processes that do not require user involvement and are tied to the specific
list in question. Event handlers are also the only option if you want to
intercept an action before it happens. If the requirement being met involves
long-running processes that involve waiting for multiple inputs from users or
which are triggered from multiple locations, a workflow is more likely to be
the correct solution.
SharePoint 2010
also introduces a new type of workflow called a site workflow. Site workflows
are tied to sites rather than individual lists and can be only manually
triggered (or via code). Site workflows are primarily useful when the workflow
in question is not tied to actions around a particular item in a list. Imagine
a scenario in which you need to find a single item in a list based on some
characteristic and then email the creator of that item. In that case, because
you want the workflow to find the item, a site workflow is appropriate. If
instead you want to email the creator of an item every time it updates, a list
workflow is appropriate.
Timer
Jobs
Timer jobs are
SharePoint’s mechanism to enable you to run code on a scheduled basis. You can
schedule timer jobs to run anywhere from every minute to every month.
Timer jobs are the preferred mechanism any time you must run code on a scheduled basis or where operations are particularly long running. An example of a sample scenario in which a timer job might be appropriate might be if you have a requirement in which every month you want to scan all MySites sites and find any files older than 90 days to confirm if you can delete them.
Feature Receivers
Timer jobs are the preferred mechanism any time you must run code on a scheduled basis or where operations are particularly long running. An example of a sample scenario in which a timer job might be appropriate might be if you have a requirement in which every month you want to scan all MySites sites and find any files older than 90 days to confirm if you can delete them.
Feature Receivers
Feature receivers
are just an alternative form of an event receiver tied to SharePoint Features
rather than SharePoint Lists. Feature receivers enable the developer to run
arbitrary code whenever a feature is installed or removed from a farm/web
application/site collection/web, whenever a feature is activated or
deactivated, or when a feature is upgraded.
One common use for
feature receivers is to register timer jobs deployed by a feature. Following is
an example of what code to add for a timer job whenever the feature activates:
public override void
FeatureActivated(
SPFeatureReceiverProperties properties)
{
SPSite site = (SPSite)properties.Feature.Parent;
CustomTimerJob smbJob = new CustomTimerJob(“My Job Name”, site);
SPFeatureReceiverProperties properties)
{
SPSite site = (SPSite)properties.Feature.Parent;
CustomTimerJob smbJob = new CustomTimerJob(“My Job Name”, site);
SPMinuteSchedule schedule =
new SPMinuteSchedule();
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval = 2;
smbJob.Schedule = schedule;
smbJob.Update();
}
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval = 2;
smbJob.Schedule = schedule;
smbJob.Update();
}
Another sample use
would be if a feature depends on certain data, such as a SharePoint subsite
existing, prior to it properly working. Your feature receiver could create the
required subsite as part of the activation process.
External Access
Up to this point,
this chapter has focused only on solutions that exist within SharePoint and run
within the farm, but it is also common to need to access SharePoint content and
interact with SharePoint from outside of the farm. One example of such a need
would be an application that enables you to scan documents from your desktop
and store them directly within SharePoint. SharePoint provides two mechanisms
to support external access. The first, which existed in SharePoint 2007 and
continues to exist in 2010, are web services. The second is Client Object
Model, which is new to SharePoint 2010, which you can access from .NET,
Silverlight, or JavaScript. The main advantage to using the Client Object Model
over the web services is that rather than having to learn a completely new way
to access SharePoint content, developers can reuse much of their knowledge of
the server-side object model. There definitely are some differences, but in
general, the Client Object Model provides a much more familiar framework.
SharePoint Web Services
SharePoint
provides a rich set of web services (both .ASMX and WCF/RESTful) to enable
external applications to interact with SharePoint. Although not everything is
exposed via web services, a great deal of functionality is. For those writing
code in .NET, Silverlight, or Javascript, the Client Object Model introduced in
2010 will likely be the preferred mechanism for interacting with SharePoint,
but for developers writing in other languages, web services continue to be the
primary mechanism used to interact with SharePoint.
The list of web
services SharePoint provides follows:
·
Admin
·
Alerts
·
Authentication e
·
BDC Admin
·
Cell Storage
·
Copy
·
Diagnostics
·
Document
Workspace
·
Forms
·
Imaging
·
Lists
·
Meetings
·
People
·
Permissions
·
Shared Access
·
Distribution List
·
Site Data
·
Sites
·
Search
·
User/Group
·
Versions
·
Views
·
Web Part Pages
·
Webs
·
Organization Profile Service
·
Published Links
·
Social Data
·
User Profile
Covering each one
of these services is outside of the scope of this chapter, but you can benefit
from learning the capabilities of these web services. As mentioned, Microsoft
recommends using the managed Client Object Model whenever possible instead of
the web services. Chapter 4, “Leveraging the SharePoint Lists Web Service,”
focuses on just one of these web services, the List Web Service.
Client Object Model
The Client Object
Model is a set of APIs that enable you to design custom applications that
access SharePoint content. It includes libraries for client applications based
on the .NET Framework. This new API is targeted for building things such as
console, Windows Forms, and WPF applications. The Client Object Model also
includes a library for Silverlight and JavaScript client applications. The
Silverlight library is composed of a subset of the object model. These
interfaces include the Managed .NET Client Object model, Silverlight Client
Object, ECMAScript (JavaScript, Jscript) Client Object Model, and LINQ to
SharePoint.
The .NET Client
Object Model for SharePoint 2010 is one of the newest APIs for working with
SharePoint content. The Client Object Model API enables you to build custom
applications in any of the Managed .NET languages using an object-oriented
approach. This new API is the ideal method to access and manipulate SharePoint
2010 content from a client application. The new APIs were designed to have
counterparts to many of the types of objects that you have been using from the
Microsoft .SharePoint server API. However, some objects from the server API
provided with limited functionality in the Client Object Model APIs.
Using the new
object model is fairly straightforward. You begin by creating a client context
object like you would if you were using the server API. From there, you can
load, create, and manipulate the core components of SharePoint: sites, webs,
lists, and libraries. You can, of course, access the children of these objects.
Depending on the client that you develop, you can use methods to synchronously
or asynchronously perform operations against these objects, which enables you
more control of your application’s user experience. For Silverlight and
JavaScript client, you can only asynchronously perform actions, whereas .NET
clients enable only synchronous operations. As previously mentioned, Client
Object Model APIs exist for .NET, Silverlight, and ECMAScript. When you use the
API for any of these languages, the syntax looks similar, but there may be some
minor differences depending on the language. In the following sections you learn
how to use the Client Object Model to load sites and to manipulate lists and
libraries.
Working
with the ClientContext
The most basic
building block of content access from standalone applications to SharePoint is
the ClientContext class. This class is similar to the SPContext server class.
The ClientContext class is responsible for making connections to sites,
executing queries, fetching lists, and performing all other actions in
SharePoint 2010.
To create a client context for a SharePoint 2010 site located on the local server, use the follow code:
To create a client context for a SharePoint 2010 site located on the local server, use the follow code:
ClientContext clientContext = new ClientContext(“http://localhost/”)
This code assumes
that your user logged in with an account that has SharePoint permissions. This
new client context allows you to access SharePoint 2010 objects. This newly
created client context will use the credentials of the user running the
application. In some scenarios, this can be a problem.
The user account
that you need to use for SharePoint could be different from the account you use
to log into the computer. In this case you can specify the credentials that
your application needs to use.
To replace the default network credentials with custom ones you use code like the following:
To replace the default network credentials with custom ones you use code like the following:
NetworkCredential credential = new NetworkCredential(“Administrator”, “Password”,
“TestDomain”);
clientContext.Credentials = credential;
This creates a new
network credential and sets the credentials for the client context to them. If
you use forms-based authentication, you also need to change the
AuthenticationMode property of the ClientContext object to
ClientAuthenticationMode.FormsAuthentication to make this work.
Your next step
toward accessing content with the Client Object Model is to load a web or
multiple webs into the context you have created. The Client Object Model does
not load any content until it is explicitly requested. To request that the
client context load a SharePoint 2010 client object, you must write lines of
code that specify what objects to load. To add objects to load into the
context, you first access the property from the client context that you want
loaded. Next, you call the Load method on the client context with the property
as a parameter. Finally, you must call the ExecuteQuery method on the client
context to send the request to SharePoint.
The Client OM
enables you to load multiple objects by calling Load multiple times. When this
is done, all requests are batched and performed using specialized WCF services.
This need to explicitly load objects or queries into the context serves a vital
purpose. The Load method consolidates multiple requests together to reduce
network traffic and improve performance.
Working with Sites and Webs
Now that you have
a client context, look at the things you can do with it. For starters, you can
load the root web and its child webs. To do this, use the following code:
Web rootWeb = clientContext.Web;
clientContext.Load(rootWeb);
clientContext.Load(rootWeb.Webs);
clientContext.ExecuteQuery();
This loads the
entire web and its properties except for the EffectiveBasePermissions,
HasUniqueRoleAssignments, and RoleAssignments properties. If you need to work
with any of these properties, you must explicitly request them. If you are
concerned with reducing unnecessary data transfer between your client
application and the server, you should request only the specific properties
that your application will use. For example, if you know that you want to use
only the title of a Web site, you would use the following code to load only the
title property:
Web oWeb = clientContext.Web;
clientContext.Load(oWeb, web=>web.Title);
clientContext.ExecuteQuery();
Using the Client
OM, you can also change many properties of an existing web. For instance, you
can update a web and change the title and description. To do so, access the web
you want to change, modify the properties, and update the web. Because this is
the Client OM, you must always call ExecuteQuery to send the request to the
server. Code to do this would look like the following:
Web oWeb = clientContext.Web;
oWeb.Title = “Updated Web Title”;
oWeb.Description = “This is a sample of updating a web”;
oWeb.Update();
You can also
create Web site objects with the Client OM. To do this use the
WebCreationInformation class. You need to set the various properties on the
WebCreationInformation object and add it to the web’s collection. The web’s
collection you add the new creation object to becomes the parent web for your
new one. To add a new blog site use the following code:
Web oWeb = clientContext.Web;
WebCreationInformation webBlogCreate = new WebCreationInformation();
webBlogCreate.Description = “This is a new Blog Site”;
webBlogCreate.Language = 1033; //English Language code is 1033
webBlogCreate.Title = “New Blog Site”;
webBlogCreate.Url = “newblogsite”;
webBlogCreate.UseSamePermissionsAsParentSite = true;
webBlogCreate.WebTemplate =
“BLOG#0”;
Web oNewWeb = oWeb.Webs.Add(webBlogCreate);
clientContext.ExecuteQuery();
Web oNewWeb = oWeb.Webs.Add(webBlogCreate);
clientContext.ExecuteQuery();
In the previous
example, the value for the Language property is 1033. This is the Microsoft
locale code for the English – United
States locale. If you create a site for
another locale, you need to replace 1033 with the locale code for your locale.
A value for the URL of the new site is set. You do not need to put in a full
URL here; this needs to be only the final part of the URL. Finally, set the
WebTemplate property. In this case you use the string BLOG#0, but you can use
any template in your SharePoint 2010 environment. Because this property is just
a string, you can even use a custom web template. Now that you have some
understanding of the basic building blocks of the Client Object Model, look at
using them to access lists.
Working with Lists and Libraries
Lists and document
libraries are a key component of the content stored in SharePoint. Lists and
libraries are quite similar in design, but they certainly hold different types
of information. Lists generally hold data that you can represent in a
spreadsheet or tabular manner, whereas libraries hold documents and metadata
about those documents. In the following sections you learn how to work with
list and library data. You also learn how to create new lists and libraries.
Working with Lists
Working with Lists
If you have worked
with SharePoint before, lists are probably not new to you. They are one of the
main components for storing data in SharePoint. This section discusses how to
create, update, and delete lists and list items.
Managing Lists
Before starting to
work with list data, you need to create a custom list to work with. This is
certainly not required because most SharePoint 2010 site templates come with
predefined lists. It is, however, a common development task to create and
modify lists. To create a list, use the client context you created in the last
section. You also use a new class called List CreationInformation. To create a
list, you create a ListCreationInformation object and set the Title and
TemplateType properties. The Title property is just the string title that you
want to use for the name of the list.
The TemplateType property is an integer representing the template to use for your new list instance. You can retrieve template types from the ListTemplateType enumeration and cast to an integer, or you can enter them directly as an integer. The following code creates a list using the announcements template and titled Custom Announcements List:
The TemplateType property is an integer representing the template to use for your new list instance. You can retrieve template types from the ListTemplateType enumeration and cast to an integer, or you can enter them directly as an integer. The following code creates a list using the announcements template and titled Custom Announcements List:
ListCreationInformation listCreationInfo = new ListCreationInformation();
listCreationInfo.Title = “Custom Announcements”;
listCreationInfo.TemplateType = (int)ListTemplateType.Announcements;
List oList = oWebsite.Lists.Add(listCreationInfo);
clientContext.ExecuteQuery();
You can also update properties of the list with the Client
OM API. To do this you need to load the list using the client context. You can
do this with a LINQ to objects query or CAML query, or by calling the
GetByTitle method on the clientContext.Webs.Lists object and passing the title
of the list. The GetByTitle approach has the benefit of loading only the list
that you modify instead of loading all the lists in your query. After you load
the list, you can set the property that you want to update, and call the Update
method on the list object. Of course, you also must call ExecuteQuery on the
client context to send the update to SharePoint, as follows:
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
oList.Description = “This is the new Custom Announcements List”;
oList.Update();
clientContext.ExecuteQuery();
Another common
list management task is adding a field to an existing list. To perform this
task, you need to get a reference to the list object that you want to add the
field to. You can add a field with any of the types available from the user
SharePoint 2010 interface. You can then use the AddField or AddFieldAsXml
method to add a field. If you need to add attributes to your new field, you can
also do that. The following code creates a new number field and sets the
minimum and maximum values for the field.
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
Field oField = oList.Fields.AddFieldAsXml(
“<Field DisplayName=’Percent Complete’ Type=’Number’/>”,
true, AddFieldOptions.DefaultValue);
FieldNumber fieldNum = clientContext.CastTo<FieldNumber>(oField);
fieldNum.MaximumValue = 100;
fieldNum.MinimumValue = 0;
fieldNum.Update();
clientContext.ExecuteQuery();
The final list
management task discussed is deleting a list. Deleting a list with the Client
OM is simple.
As with adding a
field or modifying a list, you start by getting a reference to the list you
want to delete.
Then you call the
DeleteObject method on that object. Finally, you call ExecuteQuery on the
clientcontext to perform the request.
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
oList.DeleteObject();
clientContext.ExecuteQuery();
Now that you have
learned how to create, modify, and delete SharePoint lists, you learn how to
work with list data. As you will see in the next section, this is similar to
the creating of lists from this section.
Adding
List Items
Creating list
items with the Client OM is straightforward. To create a list item and add it
to the list, you use the ListItemCreationInformation class. After you
instantiate a ListItemCreationInformation object, you need to add it to a list
object using the AddItem method on the list. Of course, that means that you
need to get or load the list that you want to modify. With the list item
creation information added to the list, you can set the various field values
for the list item and call the Update method on the list item. Finally, call
the ExecuteQuery method on the client context to send your changes to the
SharePoint server. The code to perform follows:
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
ListItemCreationInformation itemCreateInfo = new ListItemCreationInformation();
ListItem oListItem = oList.AddItem(itemCreateInfo);
oListItem[“Title”] = “New List Item”;
oListItem[“Body”] = “This is my new List Item”;
oListItem.Update();
clientContext.ExecuteQuery();
Updating List Items
Updating list
items is easy with the Client OM. All you need to do is get the list item you
want to update, set the fields to update, and call the Update method on the
list item. The list properties are accessed and modified with the indexer for
the list item in the same way as if you were modifying a dictionary object. The
following code gets the third list item in the Custom Announcements list and
updates the Title property:
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
ListItem listItem = oList.Items.GetById(3);
listItem[“Title”] = “My Updated Title.”;
listItem.Update();
clientContext.ExecuteQuery();
You can also
update more than one list item at a time with only a single call to execute the
query. To do this, you need to get the list items that you want to update. The
easiest way to get multiple items is to use the GetItems method. This method
takes a CamlQuery as a parameter. To illustrate this, you can load all the
items that have a completed percentage of 50 and update them to 100 like this:
CamlQuery query = new CamlQuery();
query.ViewXml = “<View><Query><Where><Eq>”+
“<FieldRef Name=’Percent Complete’/><Value Type=’Number’>” +
“50</Value></Eq></Where></Query>” +
“<RowLimit>2</RowLimit></View>”;
ListItemCollection collListItem = list.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
foreach (SPCL.ListItem item in collListItem)
{
item[“Percent Complete”] = 100;
item.Update();
}
context.ExecuteQuery();
Deleting
List Items
Deleting a list
item is similar to deleting a list. All you need to do is get the list item you
want to delete and call the DeleteObject method on the list item. The following
code gets the third list item in the Custom Announcements list and deletes it:
List oList = clientContext.Web.Lists.GetByTitle(“Custom Announcements”);
ListItem listItem = oList.Items.GetById(3);
listitem.DeleteObject()
clientContext.ExecuteQuery();
You can also
delete more than one list item at a time with only a single call to execute the
query. To do this, you need to get the list items that you want to delete. As
you saw in the previous section, the easiest way to get multiple items is to
use the GetItems method. The following code fetches all list items that have
Percent Complete of 100 and deletes them:
CamlQuery query = new CamlQuery();
query.ViewXml = “<View><Query><Where><Eq>”+
“<FieldRef Name=’Percent Complete’/><Value Type=’Number’>” +
“100</Value></Eq></Where></Query>” +
“<RowLimit>2</RowLimit></View>”;
ListItemCollection collListItem = list.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
foreach (SPCL.ListItem item in collListItem)
{
item.DeleteObject();
}
context.ExecuteQuery();
Querying Lists
Now that you know
how to access lists, consider how to access subsets of data. There are two ways
to query lists with the Client OM. You can write a LINQ to objects query, or
you can create a CAML query. Because you can create queries for the Client OM
in two ways, you probably wonder how to decide when to use one or the other. As
a general rule LINQ style queries are much easier to create. If you have used
LINQ to SQL, you are already familiar with the basics. So then why use a CAML
query? CAML style queries are not as easy to create as LINQ to objects queries.
CAML queries are, however, much faster with the Client OM than LINQ queries
are. This performance improvement is because LINQ queries are performed on a
frontend web server, whereas CAML queries are performed directly against the
database. In addition, you cannot query some objects with LINQ.
For instance, you
cannot query list items with LINQ directly. In this case you must perform a
CAML query.
The LINQ queries
are not using the new LINQ to SharePoint provider. LINQ to SharePoint is available
only when you program against the server object model, which is discussed later
in this chapter.
Querying
with LINQ
If you are
familiar with LINQ syntax, you probably know that LINQ can be expressed as
method syntax or query syntax. For those of you new to LINQ, a brief
explanation of LINQ syntax is given. Query syntax, also called queryable load,
is the style of LINQ that looks similar to SQL expressions. It contains
familiar SQL keywords such as from, in, where, and select, but the syntax does slightly
differ. Still, those familiar with writing SQL queries can quickly pick up the
new syntax. If you want to write a LINQ expression in query syntax to load the
list in the root web with the name Announcements, it would look like this:
var query = from list
in clientContext.Web.Lists
where list.Title == “Announcements”
select list;
var result = clientContext.LoadQuery(query);
clientContext.ExecuteQuery();
Method syntax, however, looks different. A LINQ expression written in method syntax, also called
in-place load, looks like multiple method calls. The previous query in method syntax would look
like this:
clientContext.Load(clientContext.Web,
website => website.Lists.Include(
list => list.Title).Where(
list => list.Title == “Announcements”));
clientContext.ExecuteQuery();
You have seen the
two styles of LINQ that you can use. Why is this important? The style of LINQ
that you use determines how the Client OM loads your objects. If you use the
query style syntax, the results of your query are stored in an object instead
of in the client context. This is called a queryable load. The method syntax
query causes the Client OM to perform an in-place load, meaning that the
objects load in the client context. In-place loads keep data in the client
context through subsequent loads. In a queryable load, you are responsible for
keeping the results in the application because they are not stored in the
client context. The benefit of the queryable load is that you can perform
multiple queries that each return different results and keep the data separate.
If you perform multiple queries using the in-place loads and then you looped
through the results in the client context, you would loop over records that do
not match your latest query.
Querying with CAML
Querying items
with LINQ is nice and easy as you have seen. Unfortunately, you cannot query
all the objects in the Client OM using LINQ. For example, you cannot query list
items with LINQ. If you want to query only list items with LINQ to objects,
your only option is to pass an empty query to the GetItems method and then work
with the result. This is not considered to be a good practice with the Client
OM. You should not create queries to return too many records. Microsoft
recommends against returning 2,000 or more records. This causes a large amount
of traffic to cross the wire, and realistically the user experience of a grid
with so many records would not be good. If you need to work with so many
records, your user interface is probably going to show them in some kind of
paged view.
Querying with CAML
queries gives you the ability to execute queries and return the results in
pages.
To use this
built-in paging, use a class called ListItemCollectionPosition. Initially, you
set it to null so that the CAML query starts at the first item. You create a
new CamlQuery object and set the ViewXml property. Now here is the first part
of paged results with CAML. You set the RowLimit in the CamlQuery object to the
number of items you want returned at once. To set up the client request, you
create a new ListItemCollection and set it equal to list.GetItems(camlQuery).
In this example, list is the List Client OM object that you query and camlQuery
is the CamlQuery Client OM object representing your query.
ListItemCollectionPosition itmPosition = null;
CamlQuery query = new CamlQuery();
Query.ListItemCollectionPosition = itmPosition;
while(true)
{
query.ViewXml = “<View><ViewFields>” +
“<FieldRef Name=’Title’/>”+
“<RowLimit>10</RowLimit></View>”;
ListItemCollection collListItem = list.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
itmPosition = collListItem.ListItemCollectionPosition;
foreach(ListItem item in collListItem)
Console.WriteLine(“Title: {0}”,item[“Title”]);
if(itmPosition == null
break;
}
Working with Libraries
If you are
somewhat new to SharePoint, you might not be aware of the relationship between
lists and document libraries. For those new to SharePoint, a document library
is a special type of list that, in addition to containing metadata, contains
actual files and folders. Because of this relationship accessing lists and
accessing libraries is similar.
Managing Libraries
Managing Libraries
Because libraries
are specialized lists, all the list management operations previously described
apply to libraries. Some additional management tasks are specific to libraries.
For instance, libraries can be versioned; individual documents must be checked
in and out. Documents are published and unpublished while this operation
doesn’t make sense of list items. In this section, you learn how to add and
upload documents, work with file versions, and work with publishing.
Adding Documents
Adding Documents
Adding items to
document libraries is a little more complicated than adding list items to
lists.
One major item is
getting the actual file bytes into the document library. There are two methods
to do this, and each have their own issues. In the first method you read the
bytes into a FileCreationInformation object and add that object into a document
library. This returns a SharePoint Client OM File object. With that file object
you call the Load method on the ClientContext that causes the file to upload.
After that you call the ExecuteQuery method on the ClientContext. Finally, you
can get the ListItem that the file is associated with by accessing the ListItemAllFields
method on the SharePoint File object that you just loaded. At this point, you
have a list item, and you can modify the metadata properties and update the
list item.
The code for this
would look like:
ClientContext context = new ClientContext(“http://localhost/”);
Web web = ctx.Web;
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = System.IO.File.ReadAllBytes(@”C:\TestFile.doc”);
newFile.Url = “/” + fileName;
List docs = web.Lists.GetByTitle(“Shared Documents”);
File uploadFile = docs.RootFolder.Files.Add(newFile);
context.Load(uploadFile);
context.ExecuteQuery();
SPClient.ListItem item = uploadFile.ListItemAllFields;
//Set the metadata
string docTitle = string.Empty;
item[“Title”] = docTitle;
item.Update();
context.ExecuteQuery();
This approach is
relatively straightforward and makes it simple to set the document metadata. It
is also good for creating folders in libraries. This approach does, however,
come with a major issue.
Using the
FileCreationInformation approach works only for files that are not too large.
You get server errors when the file byte size is larger than the size that your
site is configured to use. Although there are ways to change this setting,
there is actually a better way to add and upload documents.
The second method
is to utilize the WebDAV feature of SharePoint by calling the File
SaveBinaryDirect method on the Client OM File class. This method takes a client
context, server relative file path, stream object, and boolean flag indicating
if the method should replace an existing file.
context.Load(list.RootFolder,item
=> item.ServerRelativeUrl);
context.ExecuteQuery();
string path = list.RootFolder.ServerRelativeUrl + “/“;
using (FileStream fs = new FileStream(txtFilename.Text, FileMode.Open))
{
SPCL.File.SaveBinaryDirect(context, path + filename, fs, true);
}
string file = path + filename.Replace(“\\“,”“);
SPCL.CamlQuery query = new SPCL.CamlQuery();
context.ExecuteQuery();
string path = list.RootFolder.ServerRelativeUrl + “/“;
using (FileStream fs = new FileStream(txtFilename.Text, FileMode.Open))
{
SPCL.File.SaveBinaryDirect(context, path + filename, fs, true);
}
string file = path + filename.Replace(“\\“,”“);
SPCL.CamlQuery query = new SPCL.CamlQuery();
query.ViewXml =
“<View><Query><Where><Eq><FieldRef
Name=’FileRef’/>” +
“<Value Type=’Text’>” + file +
“</Value></Eq></Where></Query>” +
“<RowLimit>2</RowLimit></View>”;
SPCL.ListItemCollection collListItem = list.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
if (collListItem.Count == 1)
{
collListItem[0][“Title”] = txtTitle.Text;
collListItem[0].Update();
context.ExecuteQuery();
}
“<Value Type=’Text’>” + file +
“</Value></Eq></Where></Query>” +
“<RowLimit>2</RowLimit></View>”;
SPCL.ListItemCollection collListItem = list.GetItems(query);
context.Load(collListItem);
context.ExecuteQuery();
if (collListItem.Count == 1)
{
collListItem[0][“Title”] = txtTitle.Text;
collListItem[0].Update();
context.ExecuteQuery();
}
This chapter just
touched the surface of the things that you can do with the Client Object Model
but has hopefully enabled you to understand the power exposed by it.
No comments:
Post a Comment