Chapter 9. SharePoint lists

One of the greatest strengths of Microsoft SharePoint is that it enables users to create lists and to customize them for specific business scenarios. Users can create a new custom list and add whatever columns are required for their current business needs. All the details of how SharePoint tracks the schema definition for columns in a list and how it stores list items in the content database are handled behind the scenes. This approach allows business users to rapidly create light business applications with no involvement from the IT department.

Although lists make it easy to create simple no-code solutions, they are also the foundation of many SharePoint solutions and apps. Developers often rely on lists as the primary data source for solutions and apps because they can be easily accessed through the application programming interface (API) and also provide a ready user interface that supports full create, read, update, delete (CRUD) operations. All this makes the SharePoint list a critical piece of infrastructure and one that developers should know well.

Creating lists

When a user creates a new list, SharePoint allows the user to select a list template from a collection available in the current site. Table 9-1 shows a listing of commonly used list templates supplied by SharePoint Foundation. Each list has both a type and an identifier, which are used to identify classes of lists in solutions and apps. This table is just a small sampling of the available lists. In SharePoint Server Enterprise, for example, there are more than 50 different list types available.

Table 9-1. Common list templates and IDs

Template name

Template type

Template type ID

Custom List

GenericList

100

Document Library

DocumentLibrary

101

Survey

Survey

102

Links List

Links

103

Announcements List

Announcements

104

Contacts List

Contacts

105

Calendar

Events

106

Tasks List

Tasks

107

Discussion List

DiscussionBoard

108

Picture Library

PictureLibrary

109

Form Library

XMLForm

115

Wiki Page Library

WebPageLibrary

119

As you can imagine, there are many ad hoc scenarios in which creating a list does not require the assistance of a developer. Users who are skilled in SharePoint site customization are more than capable of creating lists and configuring them to achieve a desired goal. At the same time, however, there are definitely scenarios in which it makes sense to automate the creation of lists by using a SharePoint solution. This is especially true when the process for creating a specific set of lists must be repeatable across different sites or different farms.

New lists can be created in solutions or apps using a feature that contains a ListInstance element. The ListInstance element sets the attributes TemplateType and FeatureId to identify the list template and the list feature, respectively. The list template type is an identifying number such as one of those listed in Table 9-1. The feature ID is obtained from the Feature.xml file of the feature that defined the list template schema. For example, the following ListInstance element will create a new Contacts list:

<ListInstance
  TemplateType="105"
  FeatureId="00bfea71-7e6d-4186-9ba8-c047ac750105"
  Title="Customers"
  Description="Wingtip customers list "
  Url="Lists/Customers"
  OnQuickLaunch="TRUE" >
</ListInstance>

Chapter 3, introduced the fundamentals of server-side solution development along with the Visual Studio 2012 list designer, which is used to create new lists in solutions and apps. The list designer is valuable because it saves developers from having to figure out the required template types and feature IDs for lists. Additionally, the list designer results in a purely declarative solution, which is preferred because it will work in all types of solutions and apps.

Although creating lists declaratively is useful, you also have the option of creating lists programmatically. You can use either the server-side object model (SSOM), the client-side object model (CSOM), or the Representational State Transfer (REST) interface to create lists. Example 9-1 shows a sample of each approach.

Example 9-1. Creating lists programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Web;
string ListTitle = "Contacts";
string ListDescription = "Wingtip customers list";
Guid ListId = site.Lists.Add(ListTitle, ListDescription, SPListTemplateType.Contacts);
SPList list = site.Lists[ListId];
list.OnQuickLaunch = true;
list.Update();

//Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var createInfo = new SP.ListCreationInformation();
createInfo.set_title("Contacts");
createInfo.set_templateType(SP.ListTemplateType.contacts);
var newList = ctx.get_web().get_lists().add(createInfo);
ctx.load(newList);
ctx.executeQueryAsync(success, failure);

//REST interface
$.ajax({
    url: _spPageContextInfo.webServerRelativeUrl +
        "/_api/web/lists",
    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify(
        { '__metadata': { 'type': 'SP.List' },
          'Title': 'Contacts',
          'BaseTemplate': 105
        }),
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val()
    }
});

Looking at the declarative and code-based options for creating lists can be a bit overwhelming. With four different approaches, developers could easily become confused as to which is the best approach. The declarative approach is simple because it uses tools integrated into Visual Studio 2012. However, the declarative approach does have a noteworthy disadvantage: There is no graceful way to handle conflicts. For example, suppose that a user attempts to activate a feature with the ListInstance element in a site that already contains a list with the same title. The feature activates successfully, but the feature’s attempt to create the list silently fails due to the conflict, leaving the functionality of the feature in an unpredictable state. For apps, this is generally not a problem because each app instance can create its own set of lists.

Developers clearly have more control when creating a list programmatically. Developers can, for example, query the Lists collection of the current site to see if there is an existing list with the same title before attempting to create a new list. If there is a list with a conflicting title, you can delete that list or change its title to ensure that your code can create the new list with the desired title successfully.

Another advantage of using code to create lists instead of using the declarative ListInstance element is that you have more control over configuring list properties. Example 9-2 demonstrates how to configure a list to appear on the Quick Launch bar, support attachments, and allow versioning.

Example 9-2. Configuring lists programmatically
//Server-Side Object model
Guid ListId = site.Lists.Add(ListTitle, ListDescription, SPListTemplateType.Contacts);
list = site.Lists[ListId];
list.OnQuickLaunch = true;
list.EnableAttachments = false;
list.EnableVersioning = true;
list.Update();

//Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("Contacts")
ctx.load(list);
var.list.set_onQuickLaunch(quickLaunch);
list.set_enableAttachments(attachments);
list.set_enableFolderCreation(folders);
list.set_enableVersioning(versions);
ctx.executeQueryAsync(success, failure)

//REST Interface
$.ajax({
    url: _spPageContextInfo.webServerRelativeUrl +
        "/_api/web/lists/getByTitle('" + title + "')",
    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify(
        { '__metadata': { 'type': 'SP.List' },
          'OnQuickLaunch': quickLaunch,
          'EnableAttachments': attachments,
          'EnableVersioning': versions
        }),
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val(),
        "IF-MATCH": "*",
        "X-Http-Method": "PATCH"
    }
});

Working with fields and field types

Each SharePoint list contains a collection of fields that define what users see as columns. A field can be created inside the context of a single list. These are the types of fields that we will discuss first. However, a field can also be defined within the context of a site, which makes it possible to reuse it across multiple lists. These types of fields are known as site columns, and they will be introduced in the Understanding site columns section later in the chapter.

Every field is defined in terms of an underlying field type. Table 9-2 shows a list of field types that SharePoint Foundation displays to users when they are adding a new field to a list by using the Create Column page. Note that in addition to the field types included with SharePoint Foundation, there are extra field types installed by SharePoint Server 2013. Additionally, it’s possible to develop custom field types in a SharePoint solution to extend the set of field types available for use within a farm. Custom field types are covered in Chapter 10.

Table 9-2. SharePoint Foundation field types

Field type

Display name

Text

Single Line Of Text

Note

Multiple Lines Of Text

Choice

Choice

Integer

Integer

Number

Number

Decimal

Decimal

Currency

Currency

DateTime

Date And Time

Lookup

Lookup

Boolean

Yes/No

User

Person Or Group

URL

Hyperlink Or Picture

Calculated

Calculated

At a lower level, SharePoint classifies lists by using base types. Standard lists have a base type of 0, whereas document libraries have a base type of 1. There also are less frequently used base types for discussion forums (3), vote or survey lists (4), and issue lists (5). The base type defines a common set of fields, and all list types configured to use that base type automatically inherit those fields.

For example, all five base types define a field named ID. This field enables SharePoint to track each item in a list behind the scenes with a unique integer identifier. All five base types also define fields named Created, Modified, Author, and Editor. These fields allow SharePoint to track when and by whom each item was created and last modified.

Performing basic field operations

Every list contains special fields named Title, LinkTitle, and LinkTitleNoMenu. The Title field contains text that represents the list item. The LinkTitle field and the LinkTitleNoMenu field are computed fields based on the value in the Title field and cannot be edited. The LinkTitle field is used to render the Edit Control Block (ECB) menu shown in Figure 9-1.

This field is special for lists because it contains the value that is rendered inside the Edit Control Block (ECB) menu shown in Figure 9-1. Though the Title field contains the value rendered inside the ECB menu, it is not actually the field that SharePoint uses to create the ECB menu within a view. Instead, SharePoint uses a related field named LinkTitle, which reads the value of the Title field and uses it to render the ECB menu. The LinkTitleNoMenu field can be used to display the value of the Title field in a hyperlink that can be used to view an item.

The Edit Control Block menu
Figure 9-1. The Edit Control Block menu

Each field in a list has an internal name as well as a display name. After a field has been created, its internal name can never be modified. However, the display name can be modified. For example, the out-of-the-box Contacts list modifies the display name of the Title field to “Last Name”. As a result, the Last Name column provides the ECB menu in a Contacts list.

The display name of the Title field can be modified directly in the browser or through code. As an example, imagine that you need a list to track product categories, so you create a new list by using the custom list template. The SPList class provides a Fields collection, from which you can retrieve the Title field. After you have obtained an SPField reference to the Title field, you can modify its display name by using the Title property of the SPField class and then save your changes by calling the Update method. Example 9-3 shows how to accomplish this modification by using different approaches.

Example 9-3. Modifying a field programmatically
//Server-Side Object Model
SPList list = SPContext.Current.Web.Lists["ProductCategories"];
SPField fldTitle = list.Fields.GetFieldByInternalName("Title");
fldTitle.Title = "Category";
fldTitle.Update();

//Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var field = ctx.get_web().get_lists().getByTitle("ProductCategories")
           .get_fields().getByInternalNameOrTitle("Title");
ctx.load(field);
field.set_title("Category");
field.update();
ctx.executeQueryAsync(success, failure);

//REST Interface
$.ajax(
  {
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/lists/getByTitle('ProductCategories')" +
         "/fields/getByInternalNameOrTitle('Title')",

    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify(
      { '__metadata': { 'type': 'SP.Field' },
        'Title': "Category"
      }),
    headers: {
      "accept": "application/json;odata=verbose",
      "X-RequestDigest": $("#__REQUESTDIGEST").val(),
      "IF-MATCH": "*",
      "X-Http-Method": "PATCH"
    }
  });

Now that we have covered the basics of fields and field types, let’s write the code required to add a few custom fields to a new list. The Fields collection supports an Add method that can easily be used to add new fields. Additionally, the AddFieldAsXml method allows you to structure an XML chunk to define the new field. These different approaches are shown in Example 9-4 using the various APIs.

Example 9-4. Adding fields to a list programmatically
//Server-Side Object Model
list.Fields.Add("ProductDescription", SPFieldType.Text, false);

//JavaScript Client Object Model
var xmlDef = "<Field DisplayName='ProductDescription' Type='Text'/>";
var ctx = new SP.ClientContext.get_current();
var field = ctx.get_web().get_lists().getByTitle("Products")
            .get_fields().addFieldAsXml(xmlDef, false,
             SP.AddFieldOptions.addToNoContentType);
field.update();
ctx.load(field);
ctx.executeQueryAsync(success, failure);

//REST Interface
$.ajax(
    {
        url: _spPageContextInfo.webServerRelativeUrl +
            "/_api/web/lists/getByTitle('Products')/fields",
        type: "POST",
        contentType: "application/json;odata=verbose",
        data: JSON.stringify(
            { '__metadata': { 'type': 'SP.Field' },
              'Title': 'ProductDescription',
              'FieldTypeKind': SP.FieldType.text
            }),
        headers: {
            "accept": "application/json;odata=verbose",
            "X-RequestDigest": $("#__REQUESTDIGEST").val()
        }
    }
);

Note that the previous code avoids using spaces when creating the field names. If you create a field with a space in its name, SharePoint creates the internal name by replacing each space with _x0020_. This means that calling the Add method and passing a name of “List Price”, for example, would create a field with an internal name of List_x0020_Price. Most developers prefer to create fields by using a name without spaces.

The properties of the field, such as DefaultValue, represent common properties that are shared across all field types. However, specific field types have associated classes that inherit from SPField. Examples of these field type classes include SPFieldBoolean, SPFieldChoice, SPFieldCurrency, SPFieldDateTime, SPFieldDecimal, SPFieldNumber, SPFieldText, SPFieldUrl, and SPFieldUser. Although the REST API lets you use an endpoint against the underlying type, when you are using the server-side or client-side object model you must convert a field to one of these specific field types. The following code shows an example:

//Server-Side Object Model
SPList list = SPContext.Current.Web.Lists["Products"];
SPFieldCurrency fld =
  (SPFieldCurrency)list.Fields.GetFieldByInternalName("ListPrice");
fld.MinimumValue = 0;
fld.MaximumValue = 10000;
fld.Update();

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var field = ctx.get_web().get_lists().getByTitle("Products")
            .get_fields().getByInternalNameOrTitle("ListPrice");
var fieldAsNumber = ctx.castTo(field, SP.FieldNumber);
fieldAsNumber.set_minimumValue(0);
fieldAsNumber.set_maximumValue(10000);
fieldAsNumber.update();
ctx.load(field);
ctx.executeQueryAsync(success, failure);

Working with lookups and relationships

SharePoint Foundation supports lookup fields, which make it possible for users to update a field value by using a drop-down menu populated by the field values in another list. For example, imagine that you want to add a field to a list of products that lets the user pick a product category. You can accomplish this by adding a new lookup field to the products list. Example 9-5 shows how to do this by using both the server-side and client-side object models.

Example 9-5. Establishing list relationships programmatically
//Server-Side Object Model
string LookupFieldDisplayName = "Category";
SPList LookupList = site.Lists["Product Categories"];
Guid LookupListID = LookupList.ID;
list.Fields.AddLookup(LookupFieldDisplayName, LookupListID, true);
SPFieldLookup fldLookup = (SPFieldLookup)list.Fields["Category"];
fldLookup.Indexed = true;
fldLookup.RelationshipDeleteBehavior = SPRelationshipDeleteBehavior.Restrict;
fldLookup.Update();

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var xmlDef = "<Field DisplayName='Category' Type='Lookup'/>";
var field = ctx.get_web().get_lists().getByTitle("Products").get_fields()
           .addFieldAsXml( xmlDef, false, SP.AddFieldOptions.addToNoContentType);
var lookupField = ctx.castTo(field, SP.FieldLookup);
lookupField.set_lookupList({GUID});
lookupField.set_lookupField("Title");
lookupField.set_relationshipDeleteBehavior(SP.RelationshipDeleteBehavior.restrict);
lookupField.update();
ctx.executeQueryAsync(success, failure)

When a list is used as a lookup, it can have a relationship that allows you to prevent users from deleting any item in the lookup list that is currently being used by any item in the master list. This is accomplished by setting the RelationshipDeleteBehavior property to restricted delete. Under this setting, attempting to delete an item from the lookup list that is in use will then result in an error. In some scenarios, however, it makes more sense to create a relationship with cascading delete behavior. When a user deletes an item in the lookup list under this setting, all the list items that have been assigned that lookup will be deleted as well.

Though there is definite value in the support for defining list relationships, it is important that you don’t overestimate what it really does. It cannot be used to create the same level of referential integrity that can be achieved between two tables in a Microsoft SQL Server database. That means that it does not really prevent the possibility of orphaned items, such as when you have products that are not assigned to an existing product category. The value of creating the relationship with delete behavior simply relieves you of additional development work, such as writing an event handler to achieve something such as cascade delete behavior.

Understanding site columns

As you have seen, SharePoint supports the creation of fields within the scope of a list. SharePoint also supports the creation of site columns, which are fields created within the scope of a site. The advantage of a site column is that it represents a field that can be reused across multiple lists.

Every site within a site collection contains its own site columns gallery. When you add a site column to a site columns gallery, that site column is available for use within the current site, as well as in all the child sites in the site hierarchy below it. When you add a site column to the site column gallery of a top-level site in a site collection, it is available for use throughout the entire site collection. For this reason, site columns are generally added to the site columns gallery of top-level sites instead of child sites.

SharePoint automatically adds a standard set of site columns to the site columns gallery of every top-level site by using a hidden feature. Table 9-3 shows a small sampling of this standard set of site columns that are always available for use in every SharePoint site.

Table 9-3. Sampling of site columns

Internal name

Display name

Field type

ID

ID

Counter

Title

Title

Text

LinkTitle

Title

Computed

LinkTitleNoMenu

Title

Computed

Author

Created By

User

Created

Created

DateTime

Editor

Modified By

User

Modified

Modified

DateTime

FirstName

First Name

Text

HomePhone

Home Phone

Text

CellPhone

Mobile Number

Text

WorkPhone

Business Phone

Text

EMail

E-Mail

Text

HomeAddressStreet

Home Address Street

Text

HomeAddressCity

Home Address City

Text

HomeAddressStateOrProvince

Home Address State Or Province

Text

HomeAddressPostalCode

Home Address Postal Code

Text

WorkAddress

Address

Note

WorkCity

City

Text

WorkFax

Fax Number

Text

WorkState

State/Province

Text

WorkZip

ZIP/Postal Code

Text

StartDate

Start Date

DateTime

Birthday

Birthday

DateTime

SpouseName

Spouse

Text

As you can see, the standard set of site columns includes the common fields that the SharePoint Foundation base types add to every list, such as ID, Title, Author, Created, Editor, and Modified. It also includes site columns that are used by standard list types such as Announcements, Contacts, Calendar, and Tasks.

SharePoint Foundation adds more than 400 site columns into the site column gallery of every top-level site. However, many of these site columns are hidden, including quite a few that are included only for backward compatibility with earlier versions. Site administrators can view the site columns that are not hidden by using a standard application page named mngfield.aspx, which is accessible via the Site columns link in the Galleries section of the Site Settings page of a top-level site.

When you need to add a new field to a list, you should determine whether there is already an existing site column that meets your needs. In general, it is preferable to reuse an existing site column instead of creating a new field inside the scope of a list. This is especially true in scenarios where multiple lists require a field with common properties. By updating a site column, you can automatically push your changes to any list within the current site collection that contains a field based on that site column. The use of site columns also makes writing queries easier because you can standardize field names across multiple lists.

The SPWeb class provides a Fields collection that makes it possible to enumerate through the site columns in the site column gallery for the current site. It is important to understand that the site columns available for use in a site include the site columns in the local site column gallery as well as the site columns in the galleries of all parent sites. The SPWeb class provides a second collection property named AvailableFields, which allows you to enumerate through all site columns that can be used in the current site. Example 9-6 shows how to retrieve a site column from the AvailableFields collection.

Example 9-6. Retrieving columns programmatically
//Server-Side Object Model
SPField fld = SPContext.Current.Web.AvailableFields.GetFieldByInternalName("Title");

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var column = ctx.get_web().get_availableFields().getByInternalNameOrTitle("Title");
ctx.load(column);
ctx.executeQueryAsync(success, failure)

//REST Interface
$.ajax(
{
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/availableFields/getByInternalNameOrTitle('" + internalFieldName + "')",
    type: "GET",
    headers: { "accept": "application/json;odata=verbose", }
});

When should you access site columns through the Fields collection rather than the AvailableFields collection? The first observation is that accessing the Title field through the Fields collection works only when your code is executed in the context of a top-level site. This code will fail when executed within the scope of a child site. Therefore, accessing site columns through the AvailableFields collection can offer more flexibility. However, you should note that you cannot modify a site column that has been accessed through the AvailableFields collection. If you plan to modify a site column, you must access it by using the Fields collection in the context of the site where it exists.

In addition to using the standard site columns provided by SharePoint Foundation, you can also create your own manually by using the browser or with code by using the server-side object model. You will find that writing the code for creating a new site column is just like writing the code for creating a new field in a list. The main difference is that you call the Add method on the Fields collection of a site instead of the Fields collection of a list. Example 9-7 demonstrates how to create a new site column in the current site. You could also choose to add the site column to the root site of the current site collection so that it would be available to all sites. This can even be done by using client-side code from an app, as long as the app has the Manage permission for the host site collection.

Example 9-7. Creating a site column programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Web;
site.Fields.Add("EmployeeNumber", SPFieldType.Text, true);
SPField fld = site.Fields.GetFieldByInternalName("EmployeeNumber");
fld.Title = "Employee Number";
fld.Group = "Wingtip Toys";
fld.Update();

//JavaScript Client Object Model
var xmlDef = "<Field DisplayName='EmployeeNumber' Type='Text'/>";
var ctx = new SP.ClientContext.get_current();
var field = ctx.get_web().get_fields().addFieldAsXml(
            xmlDef, false, SP.AddFieldOptions.addToNoContentType);
ctx.load(field);
field.set_group("Wingtip Toys");
field.updateAndPushChanges(false);
ctx.executeQueryAsync(success, failure)

//REST Interface
$.ajax(
{
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/fields",
    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify(
    { '__metadata': { 'type': 'SP.Field' },
      'Title': 'EmployeeNumber',
      'FieldTypeKind': 2,
      'Group': 'Wingtip Toys'
    }),
    headers: {
      "accept": "application/json;odata=verbose",
      "X-RequestDigest": $("#__REQUESTDIGEST").val()
    }
});

A significant benefit to using site columns is that they can be used to update multiple lists at once. Imagine a scenario where you have created 10 different lists within a site collection that contain fields based on the site choice column named EmployeeStatus. What would you need to do if you wanted to add a new choice value to the EmployeeStatus site column and make it available for use in any of those lists? The answer is that you can simply add the new choice value to the site column and then call the Update method with a value of true to push your changes to all the lists within the current site collection that contain fields based on the site column. Example 9-8 shows the approach.

Example 9-8. Adding choices programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Site.RootWeb;
SPFieldChoice fld =
        (SPFieldChoice)site.Fields.GetFieldByInternalName("EmployeeStatus");
fld.Choices.Add("Contractor");
fld.Update(true);

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var column = ctx.get_web().get_fields().getByInternalNameOrTitle("EmployeeStatus");
ctx.load(column);
var choiceColumn = ctx.castTo(column, SP.FieldChoice);
ctx.load(choiceColumn);
choiceColumn.set_choices("Contractor");
choiceColumn.updateAndPushChanges(true);
ctx.executeQueryAsync(success, failure)

Working with content types

SharePoint 2013 supports a flexible and powerful mechanism for designing lists known as a content type. A content type is an entity that uses site columns to define a schema of fields for an item in a list or a document in a document library. It’s important to understand that content types, like site columns, are defined independently outside the scope of any list or document library. A content type defines a field collection that is reusable across multiple lists or multiple document libraries. Furthermore, content types can be updated to make sweeping changes to many lists at once, such as ascenario in which you need to add a new field to accommodate changing business requirements.

Every site within a site collection contains a content types gallery. The content types gallery for a site can be viewed and administered through an application page named mngctype.aspx, which is accessible through the Site Content Types link in the Galleries section of the Site Settings page.

Content type visibility behaves just as it does for site columns. When you add a content type to the content types gallery of a specific site, that content type is available for use within the current site, as well as in all the child sites in the site hierarchy below it. When you add a content type to the content types gallery of a top-level site, it is available for use throughout the entire site collection.

SharePoint Foundation automatically adds a standard set of content types to the content types gallery of every top-level site. Table 9-4 shows a partial listing of the standard content types that SharePoint Foundation makes available within every site. This table lists each content type with its ID and name along with the name of its parent content type.

Table 9-4. A partial listing of the standard SharePoint Foundation content types

ID

Name

Parent

0x01

Item

System

0x0101

Document

Item

0x0102

Event

Item

0x0104

Announcement

Item

0x0105

Link

Item

0x0106

Contact

Item

0x0108

Task

Item

0x0120

Folder

Item

0x010101

Form

Document

0x010102

Picture

Document

0x010105

Master Page

Document

0x010108

Wiki Page

Document

0x010109

Basic Page

Document

0x012002

Discussion

Folder

0x012004

Summary Task

Folder

Content types are defined based upon the principles of inheritance. Every content type that you can create or use inherits from another content type. SharePoint Foundation provides a special hidden content type named System, which exists at the very top of the inheritance hierarchy above Item. However, as a developer, you will never deal directly with the System content type or program against it. Therefore, the Item content type can be considered the parent of all other content types in SharePoint.

Each content type has a string-based ID that begins with the ID of its parent content type. The Item content type has an ID based on the hexadecimal number 0x01. Because every content type inherits from Item, all content types have an ID that begins with 0x01. For example, the Document content type, which inherits from Item, has an ID of 0x0101. Content types that inherit from Document have IDs that begin with 0x0101, such as Form, which has an ID of 0x010101, and Picture, which has an ID of 0x010102.

If you create a new content type through the browser or code, SharePoint will create a new content type ID for you. SharePoint creates a content type ID by parsing together the base content type ID, followed by 00 and a new GUID. For example, if you create a new content type that inherits from Document, SharePoint will create a content type ID that looks something like the following:

0x010100F51AEB6BBC8EA2469E1617071D9FF658

The inheritance-based architecture of content types yields quite a bit of power. For example, consider what happens if you add a new site column to the Document content type in the content types gallery of a top-level site. You would effectively be adding the site column to all the content types that inherit from Document, which provides a simple and effective way to add a new field to every document library in a site collection.

Content types go beyond defining a set of fields. A content type can also define behaviors with event handlers and workflow associations. For example, consider what would happen if you register an event handler on the Document content type in the content types gallery of a top-level site. You would effectively be registering the event handler on every document in the site collection, including those documents based on derived content types such as Form, Picture, Master Page, Wiki Page, and Basic Page.

Programming with content types

The SPWeb class provides a ContentTypes collection that makes it possible to enumerate through the content types in the content types gallery of the current site. Following the same pattern as for site columns, you can also use a second collection named AvailableContentTypes, which allows you to enumerate the aggregation of content types in the content types gallery of the current site and all parent sites. Example 9-9 shows how to retrieve a content type when you know its name or ID.

Example 9-9. Retrieving a content type programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Web;
SPContentType ctype = site.ContentTypes["WingtipToysProduct"];

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var ctype = ctx.get_web().get_contentTypes().getById("0x010100F51AEB6BBC8EA2469E1617071D9
FF658");
ctx.load(ctype);
ctx.executeQueryAsync(success, failure);

//REST Interface
$.ajax(
{
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/contentTypes/getById('0x010100F51AEB6BBC8EA2469E1617071D9FF658')",
    type: "GET",
    headers: {
        "accept": "application/json;odata=verbose",
    }
});

When would you want to access a content type through the ContentTypes collection rather than the AvailableContentTypes collection? The first observation is that the line of code that accesses the content type through the ContentTypes collection will fail if it is ever executed within the context of a child site. Therefore, accessing content types with the AvailableContentTypes collection is more flexible if you need to write code that might execute in the context of a child site. This behavior is similar to the behavior of site columns.

However, another important point is that a content type that has been retrieved through the AvailableContentTypes collection is read-only and cannot be modified. Therefore, you must retrieve a content type by using the ContentTypes collection inside the context of the site where it exists if you need to modify it. Remember that all the standard SharePoint Foundation content types are added to the content types gallery of each top-level site.

Each content type is made up of a set of site columns. Although each content type defines a collection of site columns, it doesn’t just track a collection of site columns by using SPField objects as you might expect. Instead, it tracks each site column by using an SPFieldLink object. You can think of an SPFieldLink object as a layer on top of an SPField object that the content type can use to customize default property values of the underlying site column.

Consider a scenario in which you might want to add the standard site column named Company to an existing content type. This is accomplished by adding a new field to the collection of field links. Example 9-10 adds the site column to an existing content type, and then pushes the change to every list and document library that uses it.

Example 9-10. Adding site columns to a content type programmatically
//Server-Side Object Model
SPContentType ctype = site.ContentTypes["WingtipToysProduct"];
SPField companyField = site.Fields.GetFieldByInternalName("Company");
ctype.FieldLinks.Add(new SPFieldLink(companyField));
ctype.Update(true, false);

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var field = ctx.get_site().get_rootWeb().get_fields()
           .getByInternalNameOrTitle("Company");
var ctype = ctx.get_site().get_rootWeb().get_contentTypes()
           .getById("0x010100F51AEB6BBC8EA2469E1617071D9FF658");
ctx.load(ctype);
ctx.load(field);
var createInfo = new SP.FieldLinkCreationInformation();
createInfo.set_field(field);
var fieldLink = ctype.get_fieldLinks().add(createInfo);
ctype.update (true);
ctx.load(fieldLink);
ctx.executeQueryAsync(success, failure);

Note that the call to Update in the server-side code passes a second parameter with a value of false. This second parameter requires some explanation. There are some content types in the standard set of content types added by SharePoint Foundation that are defined as read-only content types. If you do not pass a value of false for the second parameter, the call to Update fails when it attempts to modify one of these read-only content types. When you pass a value of false, the call to Update succeeds because it ignores any failures due to the inability to update read-only content types.

Creating custom content types

You will never create a content type from scratch. Instead, you always select an existing content type to serve as the base content type for the new content types you are creating. For example, you can create the most straightforward content type by inheriting from the content type named Item. This automatically provides your new content type with the standard fields and behavior that are common across all content types. Alternatively, you can elect to inherit from another content type that inherits from the Item content type, such as Announcement, Contact, Task, or Document. Beyond creating a new content type manually by using the browser, you can also do it declaratively with Collaborative Application Markup Language (CAML), or programmatically.

Adding a new content type declaratively is straightforward when you are using Visual Studio 2012. Simply select the option to add a new item to your app or solution and select Content Type. A wizard then walks you through selecting from the available content types from which to inherit. You can then easily add existing site columns to the content type. If you need a custom site column, it can also be added as a new item. The end result is an Elements.xml file containing the declarative definition of the new content type, as shown in the following code:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Document (0x0101) -->
  <ContentType ID="0x010100FD82D89E067A4B0C8D0E7474FA662E70"
               Name="FinancialDocument"
               Group="Financial"
               Description="Financial Content Type"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{a93bbb3e-2a86-4763-bfb5-9c86849d1630}"
                DisplayName="Amount"
                Required="FALSE"
                Name="Amount" />
    </FieldRefs>
  </ContentType>
</Elements>

To create a new content type with server-side code, you must call the SPContentType class constructor, passing three parameters. The first parameter is the SPContentType object associated with the parent content type you want to inherit from. The second parameter is the target content types collection, which should typically be the ContentTypes collection of a top-level site. The third parameter is the string name of the content type to be created. After you have created an SPContentType object and initialized its properties, such as Description and Group, you must call the Add method on the ContentTypes property of the target site to actually save your work in the content database. The following code shows how this works:

SPWeb site = SPContext.Current.Site.RootWeb;
string ctypeName = "FinancialDocument";
SPContentType ctypeParent = site.ContentTypes["Document"];
SPContentType ctype = new SPContentType(ctypeParent, site.ContentTypes, ctypeName);
ctype.Description = "A new content type";
ctype.Group = "Financial Content Types";
site.ContentTypes.Add(ctype);

When creating a new content type by using CSOM, the process is similar to the server-side example. When creating a new content type by using REST, the process uses a POST method but requires the same basic information. Example 9-11 shows each approach.

Example 9-11. Creating content types programmatically
//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var parent = ctx.get_web().get_contentTypes().getById("0x0101");
ctx.load(parent);
var createInfo = new SP.ContentTypeCreationInformation();
createInfo.set_name("FinancialDocument");
createInfo.set_description("A new content type");
createInfo.set_parentContentType(parent);
var newCtype = ctx.get_site().get_rootWeb().get_contentTypes().add(createInfo);
ctx.load(newCtype);
ctx.executeQueryAsync(success, failure)

//REST Interface
$.ajax({
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/site/rootWeb/contentTypes",
    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify({
        '__metadata': { 'type': 'SP.ContentType' },
        'Name': contentTypeName,
        'Description': description,
        'Id': {
            '__metadata': { 'type': 'SP.ContentTypeId' },
            'StringValue': createCTypeId(parentId)
               }
    }),
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val()
        }
});

Working with document libraries

A document library is really just a specialized type of list. The main difference between a document library and other types of lists is that a document library is designed to store documents instead of merely list items. Every item in a document library is based on a file stored inside the content database. You can add extra fields to a document library just as you can to a standard list. This is a common practice because the fields in a document library allow you to track document metadata that is stored outside the document file.

Document libraries have an underlying base type of 1, which defines several document-related fields. For example, SharePoint tracks the name of each file in a document library by using a field named FileLeafRef, which has a display name of Name. There is a second field named FileRef, with a display name of URL Path, which contains the file name combined with its site-relative path. There is another field named FileDirRef, with a display name of Path, which contains the site-relative path without the file name.

The ECB menu works differently in document libraries than in standard lists. More specifically, SharePoint populates the ECB menu for a document by using the file name instead of the Title field. SharePoint uses a related field named LinkFilename, which reads the file name from the FileLeafRef field and uses it to render the ECB menu. There is a third related field named LinkFilenameNoMenu, which can be used to display the file name in a hyperlink that can be used to navigate to the View Properties page associated with a document.

You can program against a document library by using the SPList class just as you would with any other type of list. The server-side object model also provides the SPDocumentLibrary class, which inherits from SPList. SPDocumentLibrary extends SPList with additional functionality that is specific to document libraries. After you obtain an SPList reference to a document library from the Lists collection of a site, you can convert the SPList reference to an SPDocumentLibrary reference to access the extra methods and properties that are only available with document libraries, as shown in the following code:

SPDocumentLibrary DocLib = (SPDocumentLibrary)site.Lists["Product Proposals"];

Creating a document library

You can create a document library declaratively by creating a ListInstance element with a TemplateType value of 101 and the GUID that identifies the DocumentLibrary feature provided by SharePoint. The following code shows a sample in CAML:

<ListInstance
  TemplateType="101"
  FeatureId="00bfea71-e717-4e80-aa17-d0c71b360101"
  Url="ProductProposals"
  Title="Product Proposals"
  Description=""
  OnQuickLaunch="TRUE" />

If you prefer to create document libraries by using code, you can do so just as you do when creating a standard list. The only difference is that you pass a list template parameter with an SPListTemplateType enumeration value of DocumentLibrary. Refer to the code in the section “Creating lists” earlier in this chapter for details.

Adding a custom document template

One unique characteristic of document libraries is that they provide support for document templates. For example, you can create a document template by using Microsoft Word that allows users to create documents that already contain the company letterhead; or you can create a document template using Microsoft Excel that allows users to create documents for expense reports that have a structure predefined by the accounting department. A user can then create a new document from a document template by using the New Document menu on the Documents tab of a document library.

A document library is initially configured with a generic document template. However, you can update it to utilize a custom document template. One approach is to upload the custom document template file by using a Module element. SharePoint contains a special folder named Forms inside the root folder of each document library. The Forms folder is the proper location in which to upload the document template for a document library. The following code shows the declarative CAML necessary to define a document template within a Module element:

<Module Name="ProductProposalTemplates" Url="ProductProposals" List="101" >
  <File Path="ProductProposalTemplatesProposal.dotx"
        Url="Forms/Proposal.dotx"
        Type="GhostableInLibrary" />
</Module>

The Url attribute of the Module element contains the site-relative URL of the document library, which in this case is ProductProposals. This is required so that the files inside this Module element are provisioned relative to the root of the document library instead of the root of the site. You should also notice that the Module element includes a List attribute with a value of 101, which is required to indicate that the target location exists inside the scope of a document library. The Url attribute of the File element contains a path to provision the document template file in the Forms folder. The Type attribute is configured with a value of GhostableInLibrary, which tells SharePoint to provision the template within a document library.

As an alternative to using a declarative Module, you can upload the document template programmatically. In the server-side object model, the SPFolder object provides a Files collection property with an Add method, which makes it possible to upload a copy of the document template file. When you call the Add method on a Files collection object to create a new file such as a document template, you can pass the contents of the file by using either a byte array or an object based on the Stream class, as shown in the following code:

SPDocumentLibrary DocLib = (SPDocumentLibrary)site.Lists[DocLibID];
SPFolder formsFolder = DocLib.RootFolder.SubFolders["Forms"];
formsFolder.Files.Add("Specification.dotx", {Stream Object});
DocLib.DocumentTemplateUrl = @"ProductSpecifications/Forms/Specification.dotx";
DocLib.Update();

When using the client-side object model or REST interface through JavaScript, you must create a mechanism for selecting and reading the document template into a variable. This is accomplished by using an input element of type file and the FileReader object. The following code snippet shows some HTML and JavaScript for selecting files:

<input id="inputFile" type="file" />
<input id="uploadButton" type="button" value="Upload" class="app-button"/>

$("#uploadButton").click(function () {
    var buffer;
    var error;
    var file = document.getElementById("inputFile").files[0];
    var filename = document.getElementById("inputFile").value;

    var reader = new FileReader();
    reader.onload = function (e) {
        buffer = e.target.result;
    };
    reader.onerror = function (e) {
        error = e.target.error;
    };
    reader.readAsArrayBuffer(file);
});

The input element of type file creates a control that allows users to browse for and select files. The FileReader object can read the selected files into a buffer by referencing the files collection of the control. The FileReader reads the selected file asynchronously and fires the onload event when finished. The FileReader should use the readAsArrayBuffer method, which returns an array that is compatible with both the client-side object model and REST. At this point, the buffer containing the file can be retrieved from the result property. The contents of the buffer can be used with the client-side object model to upload the file into the Forms folder, as shown in the following code:

var ctx = new SP.ClientContext.get_current();
var createInfo = new SP.FileCreationInformation();
createInfo.set_content(buffer);
createInfo.set_overwrite(true);
createInfo.set_url("template.dotx");
var files =
ctx.get_web().getFolderByServerRelativeUrl("ProductSpecifications/Forms").get_files();
ctx.load(files);
files.add(createInfo);
ctx.executeQueryAsync(success, failure);

Note that the JavaScript client object model can only upload files up to 1.5 megabytes (MB) in size. If you need to upload larger files, then you must use the REST interface. The REST interface supports uploading documents as large as 2 gigabytes (GB). The following code shows the endpoint to use for uploading with the REST interface:

$.ajax({
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/GetFolderByServerRelativeUrl(' ProductSpecifications/Forms ')/Files" +
         "/Add(url='template.dotx', overwrite=true)",
    type: "POST",
    data: buffer,
    processData: false,
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val(),
        "content-length": content.length
    }
});

After the document template is uploaded, you must write code to configure the DocumentTemplateUrl property of the document library. This code ensures that the template is used for the creation of new documents. The following code shows how to do this with server-side and client-side code:

//Server-Side Object Model
SPWeb site = SPContext.Current.Web;
SPDocumentLibrary libProposals;
libProposals = (SPDocumentLibrary)site.Lists["Product Proposals"];
string templateUrl = @"ProductProposals/Forms/Proposal.dotx";
libProposals.DocumentTemplateUrl = templateUrl;
libProposals.Update();

//JavaScript Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var library = ctx.get_web().get_lists().getByTitle('ProductSpecifications')
ctx.load(library);
library.set_documentTemplateUrl('ProductSpecifications/Forms/Proposal.dotx'),
library.update();
ctx.executeQueryAsync(success, failure);

Creating document-based content types

You can create custom content types for tracking documents. This design approach provides the same type of advantages as creating content types for standard lists. For example, you can create a content type that defines a custom set of site columns for tracking document metadata and then reuse that content type across many document libraries. If you design document libraries by using custom content types, you also have the ability to add new columns to a content type and push the changes to many document libraries at once. Custom content types for document libraries generally inherit from the Document content type. One unique aspect of content types that inherit from the Document content type is that they add support for document templates.

After you have created a new content type, your next step is to upload a copy of the document template file. Uploading a file for the content type document template is done in the same way as for a document library. However, the tricky part is knowing where to upload this file. SharePoint creates a dedicated folder for each site content type in the virtual file system of the hosting site within a special folder named _cts. When you create a new content type, SharePoint automatically creates a new folder for it at _cts/[Content Type Name]. This is the location where you should upload the document template.

After you have uploaded a copy of the document template file, you can then configure the content type to use it by modifying its DocumentTemplate property. In order to properly set the document template, you must know the content type ID and the name of the file to use. Example 9-12 shows how this is accomplished for a content type named “Invoice”.

Example 9-12. Setting a document template URL programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Site.RootWeb;
SPContentType ctype = site.ContentTypes["Invoice"];
ctype.DocumentTemplate("Invoice.docx");

//JavaScript Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var ctype = ctx.get_web().get_contentTypes().getById("0x0101adbc123")
ctx.load(ctype);
ctype.set_documentTemplate("Invoice.docx");
ctype.update();
ctx.executeQueryAsync(success, failure);

//REST Interface
$.ajax({
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/contentTypes/getById('0x0101adbc123')",
    type: "POST",
    contentType: "application/json;odata=verbose",
    data: JSON.stringify({
        '__metadata': { 'type': 'SP.ContentType' },
        'DocumentTemplate': 'Invoice.docx'
     }),
     headers: {
     "accept": "application/json;odata=verbose",
     "X-RequestDigest": $("#__REQUESTDIGEST").val(),
     "IF-MATCH": "*",
     "X-Http-Method": "PATCH"
     }
});

After the content types are created and the templates defined, the content types can be used in a document library. To add new content types to a document library, the ContentTypesEnabled property must be set to true. After that, new content types can be added to the collection as shown in Example 9-13.

Example 9-13. Adding content types to a library programmatically
//Server-Side Object Model
SPWeb site = SPContext.Current.Site.RootWeb;
SPDocumentLibrary DocLib = (SPDocumentLibrary)site.Lists["FinancialDocuments"];
DocLib.ContentTypes.Add(site.AvailableContentTypes["Invoice"]);

//JavaScript Client-Side Object Model
var ctx = new SP.ClientContext.get_current();
var library = ctx.get_web().get_lists().getByTitle("FinancialDocuments");
ctx.load(library);
var ctype = ctx.get_web().get_contentTypes().getById('0x0101adbc123'),
ctx.load(ctype);
library.get_contentTypes().addExistingContentType(ctype);
ctx.executeQueryAsync(success, failure);

//REST Interface
$.ajax(
{
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/lists/getByTitle('FinancialDocuments')" +
         "/contentTypes/addAvailableContentType('0x0101adbc123')",
    type: "POST",
    contentType: "application/json;odata=verbose",
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val(),
        }
});

There is a dual aspect to programming documents in a document library. Though you can program against a document library by using an SPList object, you can also program against a document by using an SPListItem object. However, each document is also represented in the server-side object with an SPFile object. That means you can program against a document in a document library as either an SPListItem or SPFile object. You can obtain the file associated with an item through the File property by using either server-side or client-side code.

The SPListItem object can be used to read or update fields just as you would read or update fields for an item in a standard list. The SPFile object, on the other hand, can be used to control other aspects of the document, such as versioning, check-in, and checkout, as well as reading from and writing to the document’s content.

When the server-side object model is used, the contents of the document can be accessed through the SPFile.OpenBinaryStream method. When the managed client object model is used, the contents can be accessed through the File.OpenBinaryDirect and File.SaveBinaryDirect methods. When the REST API is used, the contents are accessed by referencing the file followed by the $value method, as shown here:

http://site/_api/web/GetFileByServerRelativeUrl('filepath')/$value

Working with folders

Many document libraries contain folders in addition to documents. Folders, like files, are stored as items within a document library and show up as SPListItem objects in the Items collection. This can be confusing if you are expecting a classic treeview structure within the library. Instead of navigating a tree, you can inspect a SPListItem property named FileSystemObjectType before attempting to process an item as an SPFile object, as shown in Example 9-14.

Example 9-14. Identifying folder items
//Server-Side Object Model
SPWeb site = SPContext.Current.Site.RootWeb;
SPDocumentLibrary DocLib = (SPDocumentLibrary)site.Lists["FinancialDocuments"];
foreach (SPListItem item in docLib.Items) {
  if (item.FileSystemObjectType == SPFileSystemObjectType.File) {
    // process item as document
    SPFile file = item.File;
  }
}

//JavaScript Client Object Model
var enumerator = listItems.getEnumerator(); //Returned from async call
while (enumerator.moveNext()) {
    var listItem = enumerator.get_current();
    if (listItem.get_fileSystemObjectType() === SP.FileSystemObjectType.file) {
        //process item as document
    }
}

//REST interface
var results = data.d.results; //Returned from async call
for (var i = 0; i < results.length; i++) {
    if (results[i].FileSystemObjectType === SP.FileSystemObjectType.file) {
        //process item as document
    }
}

One last point to keep in mind is that discovering documents by enumerating through the Items collection of a document library finds all documents without regard to whether they exist in the root folder or in folders nested below the root folder. If you would rather enumerate through only the documents in the root folder of a document library, you can use a different approach by using the SPFolder and SPFile classes. Example 9-15 shows how to access only documents located in the root folder of the library.

Example 9-15. Accessing the root folder programmatically
//Server-Side Object model
SPWeb site = SPContext.Current.Site.RootWeb;
SPDocumentLibrary DocLib = (SPDocumentLibrary)site.Lists["FinancialDocuments"];
foreach (SPFile file in docLib.RootFolder.Files) {
  // program against file using SPFile class
}

//JavaScript Client Object Model
var ctx = new SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle("FinancialDocuments");
ctx.load(list);
var files = list.get_rootFolder().get_Files();
ctx.load(files);
ctx.executeQueryAsync(success, failure);

//REST interface
$.ajax(
{
    url: _spPageContextInfo.webServerRelativeUrl +
         "/_api/web/lists/getByTitle ('FinancialDocuments')/rootFolder/files",
    type: "GET",
    headers: {
        "accept": "application/json;odata=verbose",
             }
});

Creating and registering event handlers

SharePoint supports event notification on host objects such as sites, lists, content types, and apps. This support is valuable to developers because it makes it possible to write event handlers, which are methods that are executed automatically in response to actions such as creating a new list, updating an item in a list, and deleting a document.

Events can be separated into two main categories: before events and after events. Before events fire before the corresponding event action occurs and before SharePoint Foundation has written any data to the content database. A key point is that a before event is fired early enough that it supports the cancellation of the event action that triggers it. Therefore, before events are often used to perform custom validations.

After events fire after the event action has completed and after SharePoint has written to the content database to commit the event action. After events do not support cancelling the event action. Instead, after events are used to execute code in response to an event action. A common example is sending out email notifications to let all the members of a site know when a new document has been uploaded.

SharePoint uses a special naming convention for events. Before events are based on methods whose names end with ing. For example, before events have names such as WebAdding, WebDeleting, ItemAdding, and ItemUpdating. The methods for after events have names that end with ed, such as WebProvisioned, WebDeleted, ItemAdded, and ItemUpdated.

Each event handler is executed under a specific synchronization mode. The two supported synchronization modes are synchronous and asynchronous. Before events are always executed under a synchronization mode of synchronous. A key point is that synchronous event handlers have a blocking nature because they run on the same thread that is processing the event action.

By default, SharePoint executes after events under a synchronization mode of asynchronous. The main difference is that asynchronous event handlers execute on a separate thread so they do not block the response that is sent back to the user. Imagine a scenario where a user uploads a new document and an after event responds by sending out a series of email notifications to more than 100 users. The asynchronous nature of an after event doesn’t require the user who has uploaded the document to wait while the code in the event handler is sending out email messages. The response page is returned to the user while the after event continues to execute.

Although SharePoint Foundation executes after events asynchronously by default, you have the option of configuring an after event to run as a synchronous event. Configuring an after event to run synchronously can be a useful technique in a scenario where code executed by the after event makes an update to an item that must be seen immediately.

Understanding event receiver classes

Event handling with the server-side object model in SharePoint solutions is based on event receiver classes. You create a new event receiver class by inheriting from one of the following event receiver base classes that are defined inside the Microsoft.SharePoint assembly:

  • SPItemEventReceiver

  • SPListEventReceiver

  • SPEmailEventReceiver

  • SPWebEventReceiver

  • SPWorkflowEventReceiver

The SPItemEventReceiver class provides event handling support for when users add, modify, or delete items in a list or documents in a document library. The SPListEventReceiver class provides event handling support for when users create and delete lists, as well as when users modify a list’s fields collection. The SPEmailEventReceiver class provides event handling support for when users send email messages to an email-enabled list.

The SPWebEventReceiver class provides event handling support for when users create new child sites within a site collection. The SPWebEventReceiver class also provides event handling support for when users move or delete sites, including both child sites and top-level sites. The SPWorkflowEventReceiver class provides event handling support for when users start a workflow instance as well as event handling support to signal when a workflow instance has completed or has been postponed.

After you have created a class that inherits from one of the event receiver base classes, you implement the event receiver class by overriding methods that represent event handlers. As an example, Example 9-16 creates an event handler for an announcements list. The event handler adds a notice and tracking number to each announcement when it is added to the list. Furthermore, the handler prevents the deletion of any items from the list.

Example 9-16. Server-side event handler
public class ItemReceiver : SPItemEventReceiver
{
   public override void ItemAdding(SPItemEventProperties properties)
   {
       properties.AfterProperties["Body"] += "
 ** For internal use only ** 
";
   }

   public override void ItemAdded(SPItemEventProperties properties)
   {
       properties.ListItem["Body"] +=
         "
 Tracking ID: " +  Guid.NewGuid().ToString() + " 
";
       properties.ListItem.Update();
   }


   public override void ItemDeleting(SPItemEventProperties properties)
   {
       properties.Cancel = true;
       properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
       properties.ErrorMessage = "Items cannot be deleted from this list.";
   }
}

Understanding remote event receivers

Event handling in apps is based upon a completely different architecture known as remote event receivers. Remote event receivers are similar in concept to the standard event handlers except that the receiver is a remote service endpoint instead of an assembly. Remote event receivers support events at the web, app, list, and list-item level, which can be both synchronous and asynchronous.

Remote event receivers can be added to an app through either the Add New Item dialog box or the Properties dialog box. If the remote event receiver is to handle anything other than app life cycle events, it should be added to the app by using the Add New Item dialog box. If the remote event receiver is to handle one of the app life cycle events, it is added by setting one of the event properties for the app, as shown in Figure 9-2 and detailed in Chapter 4.

Setting an app life cycle event
Figure 9-2. Setting an app life cycle event

If the remote event receiver is added through the Add New Item dialog box, you will be further prompted to select the event scope and event types to handle. After the scope and type are defined, Visual Studio will automatically add a new web project to your app to handle the events. This web project is automatically set as the associated remote web for the app so that it will start during the debugging process.

Remote event receivers implement the IRemoteEventService interface. This interface consists of two methods: ProcessEvent and ProcessOneWayEvent. You use the ProcessEvent method to handle synchronous events and the ProcessOneWayEvent method to handle asynchronous events. The new web project comes with template code that implements the IRemoteEventService interface and uses the TokenHelper class to retrieve a CSOM ClientContext for calling back into SharePoint. Example 9-17 implements a remote event handler for an announcements list similar to what is shown in Example 9-16.

Example 9-17. Remote event receiver
public class AnnouncementsReceiver : IRemoteEventService
{
    public SPRemoteEventResult ProcessEvent(RemoteEventProperties properties)
    {
        SPRemoteEventResult result = new SPRemoteEventResult();
        switch (properties.EventType)
        {
            case SPRemoteEventType.ItemAdding:
                result.ChangedItemProperties.Add("Body", "
 ** For internal use only **

");
                break;
            case SPRemoteEventType.ItemDeleting:
                result.ErrorMessage = "Items cannot be deleted from this list";
                result.Status = SPRemoteEventServiceStatus.CancelWithError;
                break;
        }
        return result;
    }
    public void ProcessOneWayEvent(RemoteEventProperties properties)
    {
        HttpRequestMessageProperty requestproperty =
        (HttpRequestMessageProperty)OperationContext.Current.
        IncomingMessageProperties[HttpRequestMessageProperty.Name];
        string contexttokenstring = requestproperty.Headers["x-sp-accesstoken"];
        if (contexttokenstring != null)
        {
            SharePointContextToken contexttoken =
            TokenHelper.ReadAndValidateContextToken(
            contexttokenstring, requestproperty.Headers[HttpRequestHeader.Host]);
            Uri sharepointurl = new Uri(properties.ItemEventProperties.WebUrl);
            string accesstoken = TokenHelper.GetAccessToken(
            contexttoken, sharepointurl.Authority).AccessToken;
            using (ClientContext clientcontext =
            TokenHelper.GetClientContextWithAccessToken(
            sharepointurl.ToString(), accesstoken))
            {
                if (properties.EventType == SPRemoteEventType.ItemAdded)
                {
                    List list =
                    clientcontext.Web.Lists.GetByTitle(
                    properties.ItemEventProperties.ListTitle);
                    clientcontext.Load(list);
                    ListItem item =
                    list.GetItemById(properties.ItemEventProperties.ListItemId);
                    clientcontext.Load(item);
                    clientcontext.ExecuteQuery();
                    item["Body"] += "
 Tracking ID: " +  Guid.NewGuid().ToString() + " 
";
                    item.Update();
                    clientcontext.ExecuteQuery();
                }
            }
        }
    }
}

Registering event handlers

After you create an event receiver, you must bind one or more of its event handler methods to a host object by using event registration. The types of objects that support event registration include site collections, sites, lists, content types, and documents. Note that only certain types of event handlers are supported by each type of host object. For example, you can register a ListAdded event handler with a site collection or a site, but that event type is not supported by host objects such as lists, content types, or documents. Likewise, you can register an ItemUpdating event handler with a list, content type, or document, but that event type is not supported by site collections or sites.

The simplest and most common technique for registering an event handler is to use a declarative Receivers element in a CAML file. Example 9-18 shows the CAML for registering the event receiver in Example 9-16.

Example 9-18. Registering an event receiver
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="104" Scope="Web">
      <Receiver>
        <Name>ItemReceiverItemAdding</Name>
        <Type>ItemAdding</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>AnnouncementHandler.ItemReceiver.ItemReceiver</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>ItemReceiverItemDeleting</Name>
        <Type>ItemDeleting</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>AnnouncementHandler.ItemReceiver.ItemReceiver</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>ItemReceiverItemAdded</Name>
        <Type>ItemAdded</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>AnnouncementHandler.ItemReceiver.ItemReceiver</Class>
        <SequenceNumber>10000</SequenceNumber>
        <Synchronization>Synchronous</Synchronization>
      </Receiver>
  </Receivers>
</Elements>

The registration file for a remote event receiver is nearly identical to the one used for a server-side event receiver. The only difference is that the file adds a Url element that refers to the endpoint of the remote event receiver. This is the endpoint that is invoked when the event occurs. Example 9-19 shows the registration CAML for the remote event receiver described in Example 9-17.

Example 9-19. Registering a remote event receiver
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="10000">
      <Receiver>
        <Name>AnnouncementsReceiverItemAdding</Name>
        <Type>ItemAdding</Type>
        <SequenceNumber>10000</SequenceNumber>
        <Url>http://webs.wingtiptoys.com/AnnouncementsReceiver.svc</Url>
      </Receiver>
      <Receiver>
        <Name>AnnouncementsReceiverItemDeleting</Name>
        <Type>ItemDeleting</Type>
        <SequenceNumber>10000</SequenceNumber>
        <Url>http://webs.wingtiptoys.com/AnnouncementsReceiver.svc</Url>
      </Receiver>
      <Receiver>
        <Name>AnnouncementsReceiverItemAdded</Name>
        <Type>ItemAdded</Type>
        <SequenceNumber>10000</SequenceNumber>
        <Url>http://webs.wingtiptoys.com/AnnouncementsReceiver.svc</Url>
      </Receiver>
  </Receivers>
</Elements>

Although registering event handlers declaratively by using a Receivers element works in the majority of scenarios, there are a few cases for which it doesn’t suffice. For example, you cannot register an event handler on a host object such as a content type or an individual document by using a declarative Receivers element. These types of event registration must be accomplished by using code.

Host object types that support events such as SPContentType expose a collection property named EventReceivers. The EventReceivers collection property exposes an Add method that has five different overloaded implementations. The most straightforward implementation of the Add method accepts three parameters: the event type, the four-part assembly name, and the namespace-qualified name of the event receiver class. Example 9-20 shows an example of implementing the FeatureActivated method for a site collection–scoped feature that registers an event handler on the Item content type by using an event receiver class named ItemContentTypeEvents.

Example 9-20. Registering an event receiver by using a feature receiver
public override void FeatureActivated(SPFeatureReceiverProperties properties) {
  SPSite siteCollection = (SPSite)properties.Feature.Parent;
  SPWeb site = siteCollection.RootWeb;

  // retrieve content type
  SPContentType ctypeItem = site.ContentTypes["Item"];

  // register event handler for content type
  string ReceiverAssemblyName = this.GetType().Assembly.FullName;
  string ReceiverClassName = typeof(ItemContentTypeEvents).FullName;
  ctypeItem.EventReceivers.Add(SPEventReceiverType.ItemDeleting,
                            ReceiverAssemblyName,
                            ReceiverClassName);

  // push updates to all lists and document libraries
  ctypeItem.Update(true, false);
}

Though calling the Add method with these three parameters provides the easiest approach for registering an event handler, it provides the least flexibility. For example, you cannot assign registration properties for receiver data or synchronization. To obtain more control, you can register the event handler by calling another implementation of the Add method, one that takes no parameters and returns an SPEventReceiverDefinition object, as shown in the following code:

SPContentType ctypeItem = site.ContentTypes["Item"];
string ReceiverAssemblyName = this.GetType().Assembly.FullName;
string ReceiverClassName = typeof(ItemContentTypeEvents).FullName;

// register event handler by creating SPEventReceiverDefinition object
SPEventReceiverDefinition def = ctypeItem.EventReceivers.Add();
def.Type = SPEventReceiverType.ItemDeleting;
def.Assembly = ReceiverAssemblyName;
def.Class = ReceiverClassName;
def.SequenceNumber = 100;
def.Data = "MyData";
def.Update();

// push updates to all lists and document libraries
ctypeItem.Update(true, false);

Remote event receivers support the same type of server-side registration by using a different overload of the Add method. In this case, an additional parameter is passed that specifies the Url of the remote event receiver endpoint. The general form of the registration is shown in the following code:

string serviceUrl= "http://webs.wingtiptoys.com/AnnouncementsReceiver.svc";
string siteUrl= "http://intranet.wingtiptoys.com";
using (SPSite site = new SPSite(siteUrl))
{
   using (SPWeb web = site.RootWeb)
   {
      SPList list = web.Lists["Announcements"];
      list.EventReceivers.Add(SPEventReceiverType.ItemAdded, serviceUrl);
   }
}

Programming before events

When you write the event handler implementation for a before event, you have the ability to cancel the user action that triggered the event. In server-side event handlers, you can accomplish this by assigning a value of true to the Cancel property of the event handler parameter named properties, as shown in Example 9-16. In remote event receivers, you can assign the value of CancelWithError to the SPRemoteEventResult object, as shown in Example 9-17. When canceling the event action, you can also use the ErrorMessage property to assign a custom error message that will be displayed to the user.

When you cancel a before event, SharePoint responds by short-circuiting the user’s request and canceling the event action. The key point here is that before events provide you with a layer of defense against unwanted modifications. Instead of deleting the list as the user requested, SharePoint displays the custom error message in an message box to the user.

In some scenarios, you might decide that the standard error dialog box displayed by SharePoint upon the cancellation of an event action is not sufficient. If you want to create a user experience that is more customized, you can modify the Status and RedirectUrl properties to redirect the user to a custom error page. These properties are available in both server-side and remote event receivers. The following code shows a sample for each:

//Server-Side Event Receiver
public override void ListDeleting(SPListEventProperties properties) {
  if (!properties.Web.UserIsSiteAdmin) {
    properties.Cancel = true;
    properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
    properties.RedirectUrl = properties.Web.Site.Url + "/error.aspx";
  }
}

//Remote Event Receiver
public SPRemoteEventResult ProcessEvent(RemoteEventProperties properties)
{
    SPRemoteEventResult result = new SPRemoteEventResult();
    switch (properties.EventType)
    {
        case SPRemoteEventType.ItemDeleting:
            result.Status = SPRemoteEventServiceStatus.CancelWithError;
            result.RedirectUrl = "http://webs.wingtiptoys.com/error.aspx";
            break;
    }
    return result;
}

Programming after events

After events cannot be used to cancel an event action. Instead, after events provide the opportunity to execute code in response to an event action, such as a user successfully creating a new list. When programming after events, you must reach back into SharePoint and explicitly retrieve the item to work with. In Example 9-16, for example, the server-side object model is used to retrieve the recently added announcement.

In remote event handlers, you must take the same basic approach. However, calling back into SharePoint requires passing an OAuth token from the event handler, as shown in Example 9-17. This process is the same for remote event receivers as it is for any SharePoint app containing a remote web. Chapter 6, covers the OAuth security infrastructure and the use of the TokenHelper class in detail, so it will not be repeated here.

When programming after events, you must be aware of several situations that can result in unexpected behavior. The first situation concerns the manner in which items are updated. The second situation involves the accidental creation of cascading events. The third situation is the need to call after events synchronously.

When updating list items in an after event, you should use the UpdateOverwriteVersion method instead of the Update method. You should avoiding calling Update in an after event on a list where versioning is enabled because it will generate two versions each time a user adds or updates an item. The UpdateOverwriteVersion method is provided for this exact scenario because it updates the most current version instead of generating a new version.

Modifying an item in an after event can trigger another cascading event. Consider the scenario in which an after event updates an item, which triggers the after event, which updates the item that triggers the after event, and so on. If you are not careful, you can create a recursive loop that will run until an error occurs. Here is an example of a flawed implementation of ItemUpdated that will experience this problem:

public override void ItemUpdated(SPItemEventProperties properties) {
  properties.ListItem["Title"] = properties.ListItem["Title"].ToString();
  properties.ListItem.UpdateOverwriteVersion();
}

Whenever you are required to implement an after event in which you update the item that triggered the event, you must disable event firing by modifying the EventFiringEnabled property of the event receiver class:

public override void ItemUpdated(SPItemEventProperties properties) {
  this.EventFiringEnabled = false;
  properties.ListItem["Title"] = properties.ListItem["Title"].ToString();
  properties.ListItem.UpdateOverwriteVersion();
  this.EventFiringEnabled = true;
}

If you do not disable event firing in the ItemAdded event handler, it is not as critical because it will not cause a recursive loop. However, it is still recommended because you then avoid triggering an Update event and executing ItemUpdated unnecessarily when a user adds a new item:

public override void ItemAdded(SPItemEventProperties properties) {
  this.EventFiringEnabled = false;
  properties.ListItem["Title"] = properties.ListItem["Title"].ToString();
  properties.ListItem.UpdateOverwriteVersion();
  this.EventFiringEnabled = true;
}

The final situation of concern centers on the fact that SharePoint Foundation executes the event handlers for after events such as ItemAdded and ItemUpdated asynchronously by default. The problem with after events that execute asynchronously revolves around the user seeing inconsistent field values. When a user updates a field value in the browser and saves the changes with a postback, any updates made by an event handler running asynchronously are not guaranteed to be reflected in the page that is sent back to the user. When you configure the event handler for an after event to run synchronously, you guarantee that the event handler’s updates are reflected in the page returned to the user. To configure the event handler for an after event to run synchronously, you can add a Synchronization element with an inner value of Synchronous into the Receiver element, which is shown in Example 9-18.

Querying lists with CAML

Using Collaborative Application Markup Language (CAML) to query lists is one of the oldest data-access techniques in SharePoint development and remains a core developer skill. Chapter 5, presented many examples using CAML with the client-side object model, so that information will not be repeated here. Instead, this section will focus on a more detailed examination of CAML using the server-side object model.

Understanding CAML fundamentals

Querying a list for specific items that meet a certain criteria can done by using the Microsoft.SharePoint.SPQuery object. The SPQuery object exposes a Query property that accepts a CAML fragment, which defines the query to perform. A ViewFields property defines the fields to return. The following code shows a simple query run against a list:

SPQuery query = new SPQuery();
query.Viewfields = @"<FieldRef Name='Title'/><FieldRef Name='Expires'/>";
query.Query =
@"<Where>
  <Lt>
    <FieldRef Name='Expires'/>
    <Value Type='DateTime'><Today/></Value>
  </Lt>
</Where>";
SPList list = SPContext.Current.Web.Lists.TryGetList("Announcements");
SPListItemCollections items = list.GetItems(query);

The ViewFields property accepts a CAML fragment containing a series of FieldRef elements. Each FieldRef element has a Name attribute that specifies the name of the list field to return from the query. Note that the Name attribute must contain the name of the field as it is defined in the schema.xml file for the list definition and not simply the display name of the field.

In order to create a query, you must properly construct a CAML fragment defining the items to return from the list. At the highest level, the CAML fragment may contain Where, OrderBy, and GroupBy elements. Inside of each of these elements, you can use additional CAML elements to specify conditions. Table 9-5 contains a complete list of CAML elements that can be used to create a query, and Example 9-21 shows the basic form of the CAML query.

Example 9-21. CAML query form
<Where>
  <Lt>,<Gt>,<Eq>,<Leq>,<Geq>,<Neq>,<BeginsWith>,<Contains>,<IsNotNull>,<IsNull>
    <FieldRef/>
    <Value>[Test Value], Today</Value>
  </Lt>,</Gt>,</Eq>,</Leq>,</Geq>,</Neq>,</BeginsWith>,</Contains>,</IsNotNull>,</IsNull>
  <And>,<Or>
  <Lt>,<Gt>,<Eq>,<Leq>,<Geq>,<Neq>,<BeginsWith>,<Contains>,<IsNotNull>,<IsNull>
    <FieldRef/>
    <Value>[Test Value], Today</Value>
  </Lt>,</Gt>,</Eq>,</Leq>,</Geq>,</Neq>,</BeginsWith>,</Contains>,</IsNotNull>,</IsNull>
  </And>,</Or>
</Where>
<OrderBy>
  <FieldRef/>
</OrderBy>
<GroupBy>
  <FieldRef/>
<GroupBy>
Table 9-5. CAML elements for querying

Element

Description

And

Groups multiple conditions

BeginsWith

Searches for a string at the beginning of the text field

Contains

Searches for a string within the text field

Eq

Equal to

FieldRef

A reference to a field (useful for GroupBy elements)

Geq

Greater than or equal to

GroupBy

Groups results by these fields

Gt

Greater than

IsNotNull

Is not null (not empty)

IsNull

Is null (empty)

Join

Used to query across two lists that are joined through a Lookup field

Leq

Less than or equal to

Lt

Less than

Neq

Not equal to

Now

The current date and time

Or

Boolean OR operator

OrderBy

Orders the results of the query

Today

Today’s date

TodayIso

Today’s date in ISO format

Where

Used to specify the Where clause of the query

Querying joined lists

In addition to querying single lists, the SPQuery object can be used to query across two lists that are joined by a Lookup field and surfacing projected fields. As an example, consider two lists named Instructors and Modules. The Instructors list is a simple list that contains contact information for classroom instructors. The Modules list is a custom list that contains information about training modules that will be taught in the classroom. The Modules list is joined to the Instructors list via a lookup field that shows the FullName of the instructor. Additionally, the instructor’s Email Address is available as a projected field. In this way, an instructor can be assigned a Module to teach. Using a SPQuery object and CAML, you can create a query that returns fields from both of these lists as shown in Example 9-22.

Example 9-22. Querying joined lists
SPWeb site = SPContext.Current.Web;
SPList listInstructors = site.Lists["Instructors"];
SPList listModules = site.Lists["Modules"];

SPQuery query = new SPQuery();
query.Query = "<Where><Eq><FieldRef Name="Audience"/>" +
              "<Value Type="Text">Developer</Value></Eq></Where>";
query.Joins = "<Join Type="Inner" ListAlias="classInstructors">" +
              "<Eq><FieldRef Name="Instructor" RefType="Id" />" +
              "<FieldRef List="classInstructors" Name="Id" /></Eq></Join>";
query.ProjectedFields =
"<Field Name='Email' Type='Lookup' List='classInstructors' ShowField='Email'/>";
query.ViewFields = "<FieldRef Name="Title" /><FieldRef Name="Instructor" />" +
                   "<FieldRef Name="Email" />";

SPListItemCollection items = listModules.GetItems(query);

In Example 9-22, the Where clause is created to return training modules that are intended for a developer audience; this is similar to the simple example shown earlier. The Join property is new and defines the join between the two lists through the lookup field. Remember that the query is being run on the Modules list, so it must be joined to the Instructors list. The ListAlias attribute defines an alias for the Instructors list that can be used in the join clause. The first FieldRef element refers to the name of the lookup field in the Modules list and will always have a RefType equal to Id. The second FieldRef in the join clause uses the alias name for the Instructors list and will always have a Name equal to Id. The ProjectedFields property also uses the alias name for the Instructors list and refers to additional fields in the instructors list that should be returned with the query.

Querying multiple lists

Although the SPQuery object is good for querying a single list or joined lists, if you want to query multiple lists within a site collection simultaneously, then you can make use of the Microsoft.SharePoint.SPSiteDataQuery object. Like the SPQuery object, the SPSiteDataQuery object has Query and ViewFields properties. In addition to these fields, the SPSiteDataQuery object also has Lists and Webs properties. The Lists property is used to specify the lists within the site collection that should be included in the query. The Webs property is used to determine the scope of the query. Example 9-23 shows a query that returns events from all calendars in the current site collection where the end date is later than today.

Example 9-23. Querying multiple lists
//Creates the query
SPSiteDataQuery query = new SPSiteDataQuery();

//Builds the query
query.Query = "<Where><Gt><FieldRef Name='EndDate'/>" +
              "<Value Type='DateTime'><Today OffsetDays="-1"/></Value></Gt></Where>";

//Sets the list types to search
query.Lists = "<Lists ServerTemplate='106' />";

//Sets the Fields to include in results
query.ViewFields = "<FieldRef Name='fAllDayEvent' />" +
                   "<FieldRef Name='Title' />" +
                   "<FieldRef Name='Location' />" +
                   "<FieldRef Name='EventDate' />" +
                   "<FieldRef Name='EndDate' />";

//Sets the scope of the query
query.Webs = @"<Webs Scope='SiteCollection' />";

//Execute the query
DataTable table = SPContext.Current.Site.RootWeb.GetSiteData(query);

The Lists property in Example 9-23 is a CAML fragment that can take several forms to specify the lists to include in the query. Setting the property to <Lists ServerTemplate=[value]/> limits the query to lists of a certain server template. For example, type 106 is a calendar. Table 9-6 shows all of the possible values for the ServerTemplate attribute. Setting the property to <Lists BaseType=[value]/> limits the query to lists of a certain BaseType. Table 9-7 lists the possible vales for the BaseType attribute. Setting the property to <Lists Hidden=’true’/> includes hidden lists in the query. Setting the property to <Lists MaxListLimit=[value]/> limits the query to considering no more than the specified number of lists.

The Webs property is a CAML fragment that must either be <Webs Scope=’SiteCollection’/> or <Webs Scope=’Recursive’/>. SiteCollection includes all lists in the site collection, whereas Recursive includes only those lists in the current site or subsites beneath the current site.

Table 9-6. Server templates

Server template

ID

Description

GenericList

100

Custom list

DocumentLibrary

101

Document library

Survey

102

Survey

Links

103

Links List

Announcements

104

Announcements List

Contacts

105

Contacts List

Events

106

Calendar

Tasks

107

Tasks List

DiscussionBoard

108

Discussion Lists

PictureLibrary

109

Picture library

DataSources

110

Data sources library

WebTemplateCatalog

111

Site template gallery

UserInformation

112

User list

WebPartCatalog

113

Web Part gallery

ListTemplateCatalog

114

List template gallery

XMLForm

115

InfoPath form library

MasterPageCatalog

116

Master Page gallery

WebPageLibrary

119

Wiki Page Library

DataConnectionLibrary

130

Data connection library

WorkflowHistory

140

Workflow History list

GanttTasks

150

Project Tasks list

Meetings

200

Meetings

Agenda

201

Meeting agenda

MeetingUser

202

Meeting attendees

Decision

204

Meeting decisions

MeetingObjective

207

Meeting objectives

Posts

301

Blog posts

Comments

302

Blog comments

Categories

303

Blog categories

IssueTracking

1100

Issue tracking list

AdminTasks

1200

Central Administration tasks

Table 9-7. BaseType values

Value

Description

0

Generic list

1

Document library

3

Discussion forum

4

Vote or Survey

5

Issues list

Throttling queries

SharePoint 2013 provides special support for large lists. In particular, SharePoint allows administrators to throttle the number of items returned in a list view in order to prevent performance degradation caused by returning an excessive number of items. Though these throttle settings apply to views created by end users, they also apply to queries executed in custom code.

When you are executing queries, the number of results returned will be determined by the throttle settings for the specified list and the rights of the current user. Throttle settings are set for the web application in Central Administration. Rights for the current user that affect throttling include administration rights on the web front-end server, administration rights in the web application, and auditor rights in the web application.

In the context of list throttling, users who have server administration rights on the web front end where the query is run are known as server administrators. Users granted Full Read (auditors) or Full Control (administrators) through the web application policy in Central Administration are considered super users. Everyone else is simply termed a normal user.

The List View Threshold is set at the web application level and specifies the maximum number of items that can be involved in a database operation at a single time. The default value for this setting is 5,000, which means that results returned from a SPQuery or SPSiteDataQuery object will be generally limited to 5,000 items for both super users and normal users. Additionally, the List View Lookup Threshold specifies the maximum number of lookup, person/group, or workflow status fields that can be involved in the query. This value defaults to 6. Server administrators are normally not affected by the List View Threshold or List View Lookup Threshold settings.

Both the SPQuery and SPSiteDataQuery objects have a QueryThrottleMode property that can be set to one of the values in the Microsoft.SharePoint.SPQueryThrottleOption enumeration. The possible values for the property are Default, Override, and Strict. Setting the QueryThrottleMode property to Default causes query throttling to be implemented for both super users and normal users based on the List View Threshold and List View Lookup Threshold settings. Server administrators are not affected.

Setting the QueryThrottleMode property to Override allows super users to return items up to the limit specified in the List View Threshold for the Auditors and Administrators setting as long as the Object Model Override setting is set to “Yes” for the current web application. Normal users are still limited to returning the number of items specified in the List View Threshold and List View Lookup Threshold settings. Server administrators remain unaffected.

Setting the QueryThrottleMode property to Strict causes the limits specified by the List View Threshold and List View Lookup Threshold settings to apply to all users. In this case, it makes no difference what rights you have in the web application or server. Example 9-24 shows the RenderContents method from a Web Part with configurable throttling returning query results from a list and demonstrating the concepts discussed.

Example 9-24. Throttling query results
protected override void RenderContents(HtmlTextWriter writer)
{
    SPWeb site = SPContext.Current.Web;
    SPList list = site.Lists[listName];
    SPUser user = SPContext.Current.Web.CurrentUser;
    SPQuery query = new SPQuery();

    //Throttle settings
    if (overrideThrottling)
        query.QueryThrottleMode = SPQueryThrottleOption.Override;
    else
        query.QueryThrottleMode = SPQueryThrottleOption.Strict;

    //Execute query
    query.Query = "</OrderBy>";
    query.ViewFields = "<FieldRef Name="Title" />";
    SPListItemCollection items = list.GetItems(query);

    //Show user role
    if(user.IsSiteAdmin || user.IsSiteAuditor)
        writer.Write("<p>You are a 'Super User'</p>");
    else
        writer.Write("<p>You are a regular user</p>");

    //Is throttling enabled?
    if(list.EnableThrottling)
        writer.Write("<p>Throttling is enabled</p>");
    else
        writer.Write("<p>Throttling is not enabled</p>");

    //Show count of items returned
    writer.Write("<p>" + items.Count + " items returned.</p>");

}

Regardless of the value set for the QueryThrottleMode property, no results will be throttled if the query is run within the time specified in the Daily Time Window for Large Queries. During this time period, all queries are allowed to run to completion. Additionally, the EnableThrottling property of the list can be set to False to remove the list from any and all throttling restrictions. The EnableThrottling property can only be set by someone with Farm Administrator rights using a Windows PowerShell script similar to the following:

$site = Get-SPWeb -Identity "http://wingtip.com/products"
$list = $site.Lists["Toys"]
$list.EnableThrottling = $false

Working with LINQ to SharePoint

Though CAML queries have been the workhorse of list querying for some time, CAML does present some challenges. First of all, CAML is not object oriented. As the samples have shown, CAML queries are written as text, so they are vulnerable to simple typographical error. Second, CAML is difficult to construct correctly because the rules are not always clear. Typically, a reference and a lot of troubleshooting is required to get the query right.

In response to these challenges, Microsoft introduced Language Integrated Query (LINQ) in SharePoint 2010. The LINQ to SharePoint provider is part of the Microsoft.SharePoint.Linq namespace and is used as an additional layer on top of CAML. LINQ queries created with the LINQ to SharePoint provider are translated into CAML queries for execution. Though LINQ to SharePoint is not a complete replacement for CAML, it does provide CRUD operations for lists in an object-oriented library.

Because of its full support for CRUD operations and the inherent advantages of LINQ development over CAML, you will generally use LINQ as your primary interface for working with lists through the server-side object model. You will fall back to CAML when using the client-side object model, overriding throttles, or aggregating multiple lists with the SPSiteDataQuery object.

Generating entities with SPMetal

SharePoint list data is maintained in the content database. This means that the structure of the list and item data is based on relational tables. As a SharePoint developer, however, you do not need to understand the structure of these tables because the object model abstracts the structure into SPList and SPListItem objects. When you write a LINQ to SharePoint query, you should expect the same experience as when using the object model. List and item data should be abstracted so that you do not have to understand the content database schema.

LINQ to SharePoint provides an object layer abstraction on top of the content database through the use of entity classes. Entity classes are lightweight, object-relational interfaces to the list and item data in the content database. Additionally, entity classes are used to track changes and provide optimistic concurrency during updates.

Entity classes are created by using a command-line utility called SPMetal. SPMetal is located in theSharePoint system directory at C:Program FilesCommon FilesMicrosoft Sharedweb server extensions15in. As a best practice, you should update the PATH variable in your environment to include the path the SPMetal. This way, you can simply run the utility immediately after opening a command window.

Generating entity classes with SPMetal can be very simple. At a minimum, you must specify the site for which you want to generate entities and the name of the code file to create. After the code file is created, you can immediately add it to a project in Visual Studio and start writing LINQ queries. The following code shows an example that will generate entity classes for all of the lists and content types in a site:

SPMetal /web:http://wingtiptoys.com /code:Entities.cs

Though generating entity classes can be quite easy, you will likely want more control over which entities are created and how they are structured. SPMetal provides a number of additional arguments that you can use to alter code generation. Table 9-8 lists all of the possible arguments for SPMetal and describes them.

Table 9-8. SPMetal Arguments

Argument

Description

/code:<filename>

Specifies the name of the generated file.

/language:<language>

Specifies the language for the generated code. Can be either csharp or vb.

/namespace:<namespace>

Specifies the namespace for the generated code.

/parameters:<file>

Specifies an XML file with detailed code-generation parameters.

/password:<password>

Specifies credentials to use for data access during the code-generation process.

/serialization:<type>

Specifies the serialization type. Can be either none or unidirectional.

/user:<username>

Specifies credentials to use for data access during the code-generation process.

/useremoteapi

Specifies that the generation of entity classes is to be done for a remote SharePoint site such as SharePoint Online.

/web:<url>

The URL of the SharePoint site for which entities will be generated.

If you examine the code file generated by SPMetal, you will see that there are two kinds of classes created. First, a single class is created that inherits from Microsoft.SharePoint.Linq.DataContext. The DataContext class provides a connection to lists and change tracking for operations. You can think of the DataContext class as serving a purpose similar to the SqlConnection class in data access code. Second, multiple classes are generated that represent the various content types used by the lists in the site. Using the DataContext class together with the entity classes allows you to write LINQ queries. Example 9-25 shows a simple LINQ query written to return all training modules contained in the list named Modules by using a DataContext class named Entities.

Example 9-25. A simple LINQ to SharePoint query
using (Entities dc = new Entities(SPContext.Current.Web.Url))
{
    var q = from m in dc.Modules
            orderby m.Title
            select m;

    foreach (var module in q)
    {
        moduleList.Items.Add(module.Title);
    }
}

Understanding the DataContext class

Before performing any LINQ operations, you must connect to a site by using the DataContext object. The DataContext accepts a URL in the constructor so that you can specify the site where it should connect, which is useful as you move your code from development to production. Of course, the site you specify must actually have the lists and content types for which entities have been generated. Otherwise, your operations will fail. The DataContext class also implements IDisposable so that it can be coded with a using block.

The DataContext class provides a GetList<T>() method that provides access to each list for which an entity has been generated. You can use this method in LINQ query syntax to easily specify the list against which the query should be run. Along with the method, the DataContext also has a property of EntityList<T> for each list.

The Log property can be used for viewing the underlying CAML created from the LINQ query. Not only is this useful for monitoring and debugging, but it can be used to help create CAML queries for other purposes. The Log property accepts a System.IO.TextWriter object so you can easily write the log to a file or display it in a Web Part.

The DataContext will track changes made to the entity objects so that they can be written back to the content database. The ObjectTrackingEnabled property determines whether the DataContext will track changes. The property defaults to True, but setting it to False will improve performance for read-only operations. If the DataContext is tracking changes, then the content database can be updated by calling the SubmitChanges() method, as discussed in the section “Adding, deleting, and updating with LINQ to SharePoint” later in this chapter.

Using parameters.xml to control code generation

The arguments accepted by SPMetal provide a fair amount of control over the entity-generation process, but in practice you will likely want even more control. The highest level of control over entity generation is given by passing a parameters.xml file to SPMetal with detailed information about the entities to generate.

The parameters.xml file contains elements that give SPMetal specific details about code generation. In particular, it specifies what lists, content types, and fields should be generated in code. The parameters.xml file is passed to SPMetal through the /parameters argument. The following code shows a sample parameters.xml file:

<?xml version="1.0" encoding="utf-8"?>
<Web Class="Entities" AccessModifier="Public"
  xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal" >
  <List Name="Instructors" Member="Instructors">
    <ContentType Name="Contact" Class="Instructor">
      <Column Name="FullName" Member="FullName"/>
      <ExcludeOtherColumns/>
    </ContentType>
  </List>
  <List Name="Modules" Member="Modules" />
  <ExcludeOtherLists/>
</Web>

The Web element is the root of the schema. The Class attribute specifies the name of the DataContext class to generate, and the AccessModifier attribute specifies the access level to the class. The List element is a child of the Web element and specifies the name of a list for which entities should be generated. The Member attribute specifies the name of the property in the DataContext that will represent this list. The ContentType element is a child of the List element and specifies a content type for which an entity should be generated. The Column element is a child of the ContentType element and specifies a column that should be included in the generated entity. The ExcludeOtherColumns, ExcludeOtherContentTypes, and ExcludeOtherLists elements are used to stop looking for items to include in entity generation. In this way, you can specify the exact set of lists, content types, and columns to include in the generated entities. This is very useful for excluding list, content types, and columns that are present in the development environment, but will not be present in the production environment. Table 9-9 shows the complete schema for the parameters.xml file.

Table 9-9. Parameters.xml schema

Element

Child Elements

Attribute

Description

Web

List

ExcudeList

ExcludeOtherLists

IncludeHiddenLists

ContentType

ExcludeContentType

ExcludeOtherContentTypes

IncludeHiddenContentTypes

Class (optional)

Name of DataContext class.

AccessModifier (optional)

Specifies accessibility of DataContext and entity classes. May be Internal or Public.

List

ContentType

ExcludeContentType

Name

Name of the list in SharePoint.

Member (optional)

Name of the DataContext property representing the list.

Type (optional)

Type of the DataContext property representing the list.

ContentType

Column

ExcludeColumn

ExcludeOtherColumns

IncludeHiddenColumns

Name

Name of the content type.

Class (optional)

Name of the generated class.

AccessModifier (optional)

Accessibility of the generated class.

Column

 

Name

Name of the column.

Member (optional)

Name of the generated property for the column.

Type (optional)

Type of the generated property for the column.

ExcludeColumn

 

Name

Name of the column to exclude from entity generation.

ExcludeOtherColumns

  

Excludes all columns not explicitly included.

IncludeHiddenColumns

  

Includes hidden columns in entity generation.

ExcludeList

 

Name

Name of the list to exclude from entity generation.

ExcludeOtherLists

  

Excludes all lists not explicitly included.

IncludeHiddenLists

  

Includes hidden lists in entity generation.

ExcludeContentType

 

Name

Name of content type to exclude from entity generation.

ExcludeOtherContentTypes

  

Excludes all content types not explicitly included.

IncludeHiddenContentTypes

  

Includes hidden content types in entity generation.

Querying with LINQ to SharePoint

After you have entities generated, then you can begin to write LINQ to SharePoint queries. Writing LINQ to SharePoint queries is very similar to writing LINQ queries for other data sources. You formulate a query by using query syntax, receive the results into an anonymous type, and then use the IEnumerable interface to iterate over the results.

LINQ to SharePoint also supports querying across lists that are joined by a lookup field. LINQ to SharePoint makes the syntax much simpler than with CAML. The following code shows the equivalent LINQ query for the CAML shown in Example 9-22. Note how the join is simply done by using the dot operator to move easily from the Modules list to the joined Instructors list:

var q = from m in dc.Modules
        orderby m.ModuleID
        select new { m.Title, Presenter = m.Instructor.FullName, Email = m.Instructor.Email};

Not only does the code join two lists together, but you can see that it is also using a projection. The new keyword is creating a new set of anonymous objects whose field names have been set to Title, Presenter, and Email.

LINQ to SharePoint also allows you to perform query composition. Query composition is the ability to run a LINQ query on the results of a LINQ query. For example, the following code shows how to run a new query specifically looking for a training module named Visual Studio 2012:

var q1 = from m1 in dc.Modules
        orderby m1.ModuleID
        select new { m1.Title, Presenter = m1.Instructor.FullName, Email = m1.Instructor.Email};
var q2 = from m2 in q1
        where m2.Title.Equals("Visual Studio 2012")
        select m2;

Finally, LINQ to SharePoint supports a number of extension methods that you can use for aggregation, grouping, and returning specific entities. The methods are often used on the results of the query. The following code, for example, shows how to return the total number of training modules in the query results. The most commonly used extension methods are listed in Table 9-10:

var t = (from m in dc.Modules
         select m).Count();
Table 9-10. Commonly used extension methods

Name

Description

Any()

Returns true if there are any items in the query results.

Average()

Returns the aggregated average value.

Count()

Returns the count of items in the query result.

First()

Returns the first item in the results. Useful if you are expecting a single result.

FirstOrDefault()

Returns the first item in the results. If there is no first item, returns the default for the object type.

Max(), Min()

Return the item with the maximum or minimum value.

Skip()

Skips a certain number of items in the results. Useful when used with Take() for paging.

Sum()

Returns the aggregated sum.

Take()

Allows you to return only a specified number of results. Useful when used with Skip() for paging.

ToList()

Returns the query results into a generic List<T>.

Adding, deleting, and updating with LINQ to SharePoint

Along with querying, you can also add, delete, and update lists with LINQ to SharePoint. Adding and deleting items are accomplished by using methods associated with the EntityList<T> property of the DataContext. The InsertOnSubmit() method adds a single new item to a list. The InsertAllOnSubmit() method adds a collection of new items to a list. The DeleteOnSubmit() method deletes a single item from a list, and the DeleteAllOnSubmit() method deletes a collection of items from a list. The RecycleOnSubmit() method puts a single item into the recycle bin, and the RecycleAllOnSubmit() method puts a collection of items in the recycle bin. The following code shows an example of adding a new item to the Modules list:

using (Entities dc = new Entities(SPContext.Current.Web.Url))
{
    ModulesItem mi = new ModulesItem();
    mi.Title = "LINQ to SharePoint";
    mi.Id = 301;
    dc.Modules.InsertonSubmit(mi);
    dc.SubmitChanges();
}

Updating items in lists is done by simply changing the property values in the item and then calling the SubmitChanges() method of the DataContext. The following code shows a simple example of an update operation:

using (Entities dc = new Entities(SPContext.Current.Web.Url))
{
    var q = (from m in dc.Modules
            where m.Id==1
            select m).First();

    q.Title = "Revised Title for Module 1";
    dc.SubmitChanges();
}

When updating items, LINQ to SharePoint uses optimistic concurrency. The provider will check to see whether the items in the list have been changed since your LINQ query was run before it will attempt to update them. If a discrepancy is found for any of the submitted entities, then no changes are committed. All discrepancies must be resolved before any change in the current batch can be committed.

When discrepancies are found during the update process, LINQ to SharePoint throws a Microsoft.SharePoint.Linq.ChangeConflictException. Additionally, the ChangeConflicts collection of the DataContext is populated with ObjectChangeConflict objects that contain data about fields in the item causing conflicts. The SubmitChanges() method supports overloads that allow you to specify whether update attempts should continue after the first conflict or whether update attempts should stop. The ChangeConflicts collection will only be populated with information about failed attempts, so electing to stop after the first failure will not provide complete data on all conflicts. Regardless of whether or not you continue update attempts, remember that no changes will be saved if any conflict occurs. The purpose of continuing update attempts is to completely populate the ChangeConflicts collection.

The ChangeConflicts collection contains a MemberConflicts collection that has the detailed information about the actual values causing the conflict. In particular, the MemberConflicts collection is populated with MemberChangeConflict objects. These objects each have OriginalValue, CurrentValue, and DatabaseValue properties. The OriginalValue is the value of the column when the LINQ query was run. The CurrentValue is the value that SubmitChanges() is attempting to write to the content database. The DatabaseValue is the current value of the column in the database. Trapping the ChangeConflictException and using the MemberChangeConflict objects allows you to display the conflicts to the end user. The code in Example 9-26 shows how to iterate the collection, build a list, and bind the list to a grid for display.

Example 9-26. A simple LINQ to SharePoint query
Try
{
    //Update code
}
catch (Microsoft.SharePoint.Linq.ChangeConflictException x)
{
    conflicts = new List<Conflict>();
    foreach (ObjectChangeConflict cc in dc.ChangeConflicts)
    {
        foreach (MemberChangeConflict mc in cc.MemberConflicts)
        {
            Conflict conflict = new Conflict();
            conflict.OriginalValue = mc.OriginalValue.ToString();
            conflict.CurrentValue = mc.CurrentValue.ToString();
            conflict.DatabaseValue = mc.DatabaseValue.ToString();
            conflicts.Add(conflict);
        }

    }
conflictGrid.DataSource = conflicts;
conflictGrid.DataBind();
}

Along with displaying the results, you can also resolve conflicts in code. After displaying the results to the end user in a grid, you can allow the user to select whether the pending changes should be forced or lost. The Resolve() method of the MemberChangeConflict class accepts a Microsoft.SharePoint.Linq.RefreshMode enumeration that can have a value of KeepChanges, KeepCurrentValues, or OverwriteCurrentValues. KeepChanges accepts every pending change, but gives the highest priority to the current user. KeepCurrentValues keeps only the changes made by the current user and loses all other changes. OverwriteCurrentValues loses the current changes and sets the values to what is in the database. After calling the Resolve() method, you must SubmitChanges() again to complete the operation. The following code shows an example of keeping the current changes and losing all other changes:

foreach (ObjectChangeConflict cc in dc.ChangeConflicts)
{
    foreach (MemberChangeConflict mc in cc.MemberConflicts)
    {
        mc.Resolve(RefreshMode.KeepCurrentValues);
    }
}
dc.SubmitChanges();

Summary

In this chapter, we covered the fundamental architecture of lists and document libraries. You learned about fields, field types, site columns, and content types, as well as how to create them by using the various APIs. This chapter also explored working with documents and document libraries, as well as the event infrastructure provided by SharePoint. You learned about the fundamentals of creating event receiver classes and remote event receivers, and registering event handlers. Finally, the chapter provided detailed information about querying lists with CAML and LINQ. Because lists and libraries are essential data stores in SharePoint, all of these areas are core skills for the SharePoint developer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.177.39