Part III

Developing Office 365

  • Chapter 5: SharePoint Online Development
  • Chapter 6: Exchange Online Development
  • Chapter 7: Lync Online Development

Chapter 5

SharePoint Online Development

What’s In This Chapter

  • Introducing SharePoint Online
  • Exploring development options
  • Understanding Cloud Scenarios
  • Understanding Office 365
  • Working with SharePoint Online Applications
  • Using List, View, and Event enhancements
  • Using SharePoint Web Services
  • Considering an overview of data technologies
  • Building Sandboxed Solutions
  • Building Declarative workflows
  • Using InfoPath forms
  • Using Access Services
  • How you connect BCS to Windows Azure
  • How you build the WCF service

If you have not heard about public or private clouds, you must not be reading the news or blogs, or looking at what the different vendors are doing. Software as a service (SAAS) is all the rage in the computing world. Although it has many merits, including ease of deployment, anywhere access, and quick upgrades, at the same time it has a number of obstacles, such as limited offline support, fewer mature development tools, and less control of customization. Regardless of these limitations, many people are looking to the cloud as the next major shift in the computing world. If you have not tried to build applications against a cloud service such as Microsoft Azure or SharePoint Online, start today.

SharePoint Online Overview

SharePoint Online is, as the name implies, a Microsoft-hosted version of SharePoint. One of the major differences between your on-premises deployment of SharePoint and SharePoint Online is that SharePoint Online does not support the full range of customizations or functionality that you can host on-premises. The reason for this is that your SharePoint deployment is hosted in a multitenant environment, so your SharePoint deployment is shared with other customer deployments. When building solutions on SharePoint Online, you must build nonfarm solutions rather than solutions that require full-trust or farm-level permissions because you do not own the farm in SharePoint Online. Table 5-1 shows examples of no-code solutions versus farm solutions.

Table 5-1: No-Code Versus Farm Solutions

No-Code Solution Farm Solution
Custom Markup (HTML, ASP.NET, XSLT) Custom Server Components (coded workflows, timer jobs, and Window Services)
SharePoint Designer solutions Visual Studio Solutions (except for Sandboxed Solutions)
Client-side code, such as Client OMs, including JavaScript and Silverlight Application pages
Coding against SharePoint Web Services, and REST Services Visual Web Parts without VS Power Tools

One other major difference between online and on-premises is that the cloud version is currently a subset of the full feature. In particular, SharePoint Online does not support PerformancePoint or FAST in the cloud. In addition, when dealing with search technologies, architecture and deployment become more critical as you deploying in the cloud. For example, if you have a hybrid environment in which you want to have SharePoint sites split between on-premises and online, where do you put which site? The answer is that it depends, but either place that you decide to place it, you deal with access across WANs.

Developing in the Cloud

As you can see by the previous summary, some of the ways you are used to developing on-premises do not translate to the multitenant cloud. Using server-side code is a no-no when it comes to building applications on which you run SharePoint with other tenants on the server. However, when developing for online use, you have a number of choices for development, including Sandboxed Solutions, SharePoint Designer (SPD), InfoPath, Access, or the SharePoint client object, REST and Web Services models in ASP.NET, Windows Form applications, or Silverlight applications.

Deploying and Debugging Your Solutions

Before diving into development on SharePoint Online, first consider deploying and debugging. Sometimes you might find deployment and debugging painful when developing for SharePoint Online, which provides a limited set of capabilities for these two functions as opposed to SharePoint on-premises. For example, because you cannot run Visual Studio on your SharePoint Online server and because SharePoint Online, today, does not support PowerShell, you must to manually copy your WSP files to SharePoint Online and activate them.

In addition, you cannot attach the debugger directly to your processes because VS is not running on your server, and you cannot set up remote debugging on SharePoint Online. Your only options for debugging are to use a local SharePoint Server, write to a list as a log file, or use the Developer Dashboard.

WSP Files and the Solution Gallery

To deploy solutions to SharePoint Online, you need to manually copy your WSP files, which you can have Visual Studio package up for you. Then, you need to activate the solution contained in the WSP file using the SharePoint user interface, as shown in Figure 5-1. From there, you can use your web part, workflow action, or whatever other component it contains that you have created for SharePoint Online.

Developer Dashboard

One feature that helps with debugging in SharePoint Online is that the Developer Dashboard is enabled by default. By clicking the Developer Dashboard icon in the upper-right corner of the SharePoint user interface, you can quickly see what’s happening. However, Sandboxed Solutions do not support SPMonitoredScope, which enables you to add richer tracking of your solution in the sandbox. Figure 5-2 shows the Developer Dashboard running in SharePoint.

Fiddler

Because you are dealing with an online service, you need to become adept at looking at HTTP packets when you debug. For this reason, you need to install Fiddler (or an equivalent tool) on your machines because Fiddler can help you understand the traffic between your client machine and SharePoint Online. When you can look at the traffic, you can discover what happens between the instances and also determine whether your problems are authentication- or identity-related, or are caused by other issues.

Client-side Code

For Silverlight or client-side code, your choices for debugging are better because you can use the tools built into Internet Explorer or Firefox to debug your solution.

Debugging Using Logging

The following Sandbox web part shows how to log on to a SharePoint list for debugging purposes:

download.eps
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
   
namespace SharePointOnlineLogging.WebPart1
{
    [ToolboxItemAttribute(false)]
    public class WebPart1 : WebPart
    {
   
        private Button logResultsButton = new Button() { Text = “Log Results” };
        private Label lbl = new Label();
   
        public WebPart1()
        {
        }
   
        protected override void CreateChildControls()
        {
            logResultsButton.Click += (object sender, EventArgs e) =>
            {
   
                SPContext context = SPContext.Current;
   
                SPWeb web = context.Web;
   
                SPList list = web.GetList(“/Lists/
                 SharePointOnlineLogging-ListInstance1“);
   
                string logResults = DateTime.Now.ToShortDateString() + “ “ + 
                 DateTime.Now.ToShortTimeString() 
                  + “: Logged from Sandbox Web Part!“;
   
                SPListItem newItem = list.AddItem();
                
   
                newItem[“Title“] = “New Log Result - “ +
                DateTime.Now.ToShortTimeString();
                newItem[“LoggingResult“] = logResults;
   
                newItem.Update();
   
                
                lbl.Text = “Logged Result: “ + logResults;
            };
            Controls.Add(logResultsButton);
   
            Controls.Add(lbl);
            
            //base.CreateChildControls();
        }
   
        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
        }
    }
}

code snippetWebPart1.cs

Example Cloud Scenarios

As you will find, for out-of-the-box (OOB) functionality, SharePoint Online is a viable solution. The areas to watch out for include custom code and unsupported features in the cloud or features that require administrative access to the server. To help understand areas in which using SharePoint in the cloud makes sense, the following scenarios describe where you can use the cloud and where you can’t:

  • An Extranet site: SharePoint Online in Office 365 has an offering for building extranets because the security for extranets is different than Intranet sites. Plus, sharing and invitations with external parties makes it easy for end users to invite their business partners from other companies. Extranets may be the first scenario that you undertake with SharePoint Online as a complement to your on-premises deployment of SharePoint. You may get a quick win by making it easy to share information with your business partners without worrying about access to your internal corporate networks. Make sure that the online service-level agreements (SLA) for availability and recovery meet your corporate standards and that the security in place for SharePoint Online meets your security guidelines for your company.
  • A typical team collaboration: In this scenario, you are creating a team site, sharing documents, and performing simple customization of the site. Because this case does not require high-end development or administrative access, this scenario is easily supported in the cloud. The only gotchas are to make sure that users who use Office on their desktop understand how to authenticate against SharePoint Online and how to determine the address for their sites because the address will be fully qualified domain names rather than Intranet-style short names.
  • A company portal: This is where it gets more complex because many portals require rich customization and publishing infrastructure. This is one scenario in which you must evaluate your needs versus what SharePoint Online provides from a development standpoint. If you find that your existing portal makes use of a lot of custom code (such as custom field controls or complex workflows), you may not want to run your solution in SharePoint Online or rewrite these solutions to use Sandboxed Solutions, which are supported in the Online environment.

Office 365 Overview

Office 365 is the next generation of the BPOS suite and is the latest offering in Microsoft’s cloud services infrastructure. With Office 365, you get Exchange, SharePoint, Lync, and Office Professional Plus as part of the bundle. Microsoft runs the infrastructure for you by providing you with hosting, billing, backup, antivirus, and anti-spam services, as well as a host of other services that an IT department would normally run on-premises.

Differences Between SharePoint On-Premises and Online

So, what’s the difference between SharePoint Online and SharePoint on-premises from a developer perspective? There are a number of differences, with the majority falling into the level of control and the extensibility scenario. For example, because SharePoint Online runs in a multitenant environment, you do not have the same level of access that you do when you run on-premises on your own server. You do not have access to SharePoint Central Administration. You cannot run full-trust solutions, and any pieces of SharePoint that require full server configuration changes, Central Administration access, or full-trust access will not work in SharePoint Online. So, if you find that you need full control of your environment or the ability to write code that accesses all the resources on the SharePoint Server, you may need to continue to deploy SharePoint on-premises or look at implementation options for those components, including possibly migration these to Azure-hosted solutions.

For example, Figure 5-3 shows the tenant administration page in SharePoint Online. This administration page is different and less functional than what you can do in SharePoint’s Central Administration site.

What’s in SharePoint Online in Office 365?

Because SharePoint Online is not an exact replica of SharePoint on-premises, you may be wondering what is supported in SharePoint Online. Table 5-2 shows the developer features available in SharePoint Online. There are also IT and end user features not available in SharePoint Online, so you need to check the SharePoint website for more information about which features are available in SharePoint Online.

Table 5-2: Developer Differences Between Online and On-Premises

Area SharePoint Online SharePoint On-Premises
Browser-only customization (for example, CQWP and CEWP) Supported Supported
SharePoint Designer Supported but limited BCS support Supported
SharePoint Solutions Sandbox only SandboxFarm/full trust
Client object models Supported Supported
Silverlight web part Supported Supported
Web Services Subset of Web ServicesREST APIExcel services REST API All Web ServicesREST APIExcel services REST API
InfoPath forms Sandbox only SandboxFull trust
Workflow and Workflow Activities DeclarativeSandbox workflow activities only DeclarativeSandbox and full-trust workflow activities
Anonymous access Not supported Supported
Mobile control adapters Not supported Supported
Visual Studio Supported Supported
Developer Dashboard Supported Supported
Business Connectivity Services Supported against WCF Services only Supported
Custom authentication Office 365 IDADFS Custom authentication supported

What About Hybrid Solutions?

Given the limitations of SharePoint Online, there may be times when you need to build a hybrid solution that encompasses both SharePoint Online and SharePoint on-premises. For example, given that SharePoint Online supports only a limited subset of the Business Connectivity Services (BCS) functionality found on-premises, bridging this gap is not as easy when it comes to data connectivity. However, for applications that need to span across the two environments when writing client-side code, such as Silverlight and JavaScript applications, hybrid solutions can make sense as long as you can overcome the connectivity and identity issues. Another very useful type of hybrid solution involves leveraging both Office 365 and Windows Azure. By combining the two clouds, you can build richer solutions that span the two environments. The rest of this chapter covers integrating SharePoint, both on-premises and online, with the Microsoft Azure technologies.

Writing SharePoint Online Applications

The rest of this chapter deals with writing SharePoint Online applications. Because SharePoint Online is the same software as SharePoint on-premises, except with some functionality and platform differences, many of the techniques you’ll learn throughout this book work with SharePoint Online. The main hurdle that you run into when writing SharePoint Online applications is in understanding the limitations of SharePoint Online and making sure you understand how to authenticate and authorize using SharePoint Online. Looking at some sample scenarios can help make this clear, so dive in.

Identity and Authentication in Office 365

One of the major components of Office 365 is the authentication system as we saw in Chapter 3. Office 365 supports direct authentication via a username and password. The Office 365 security system is known as OrgID, which is based off the LiveID technologies. Although it’s similar, you cannot use LiveID in the same way you can use OrgID. The following steps outline where OrgID differs:

1. When you log into the system, you request a token from the Office 365 STS, which is located at https://login.microsoftonline.com/extSTS.srf.
2. You pass your username and password using the SAML 1.1 protocol and, if successful, the STS returns a security token.
3. That security token is sent to the service you are trying to use, in this case SharePoint Online, and if validated, SharePoint Online returns two cookies that act as tokens. One is called FedAuth and the other is rtFa.
4. From that point, you must pass these two tokens with every call to SharePoint Online.

When writing SharePoint Online applications, you need to grab the two cookies that represent your tokens and add them to your calls as part of your CookieContainer.

Rather than writing your own code to do all this work, you have two choices.

Both samples are included in the sample applications of this chapter. The main difference is that the Microsoft sample does display a web browser control, as shown in Figure 5-4, and the other sample does not.

Developing for SharePoint Online

You may think you can just fire up Visual Studio and start working directly against SharePoint Online. Unfortunately, there is a limitation in the current SharePoint tools for Visual Studio such that it requires a locally-deployed SharePoint instance for development so that you can’t point your VS projects at your SharePoint Online tenant. Instead, you must develop and debug locally and then deploy your applications to SharePoint Online. With the next release of Visual Studio, Visual Studio 11 (currently in beta), you will be able to deploy SharePoint solutions to a remote SharePoint server.

Calling the Client Object Model

To make the authentication concepts clear, we will write a sample application that calls the client object model using both sets of samples for performing authentication. The sample can retrieve the title of the website and all the lists in the site, and display them on the screen.

Microsoft Authentication Sample

The Microsoft authentication sample can be compiled and referenced in your projects. The only issue with the Microsoft sample is that it does display its own form so you can run into issues trying to use the sample in Windows Forms applications. The following sample references the Microsoft sample and displays the name of the site and all the lists using the client object model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Client;
using MSDN.Samples.ClaimsAuth;

namespace SharePoint_Online_MSFT_Authentication
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {

            if (args.Length < 1) { Console.WriteLine
       ("Please enter a URL for SharePoint Online"); return; }

            string targetSite = args[0];
            using (ClientContext ctx = ClaimClientContext.GetAuthenticatedContext
             (targetSite))
            {
                if (ctx != null)
                {
                    ctx.Load(ctx.Web); 
                    ctx.ExecuteQuery(); 
                    
                    Console.WriteLine ("Your site name is:
                         " + ctx.Web.Title.ToString());

                    Console.WriteLine("Your lists are: ");
                    ListCollection listCollection = ctx.Web.Lists;
                    ctx.Load(
                        listCollection,
                        lists => lists
                            .Include(
                                list => list.Title,
                                list => list.Hidden)
                            .Where(list => !list.Hidden)
                         );
                    ctx.ExecuteQuery();
                    foreach (var list in listCollection)
                        Console.WriteLine(list.Title);

                    

                    
                }
            }
            Console.ReadLine();
        }
    }
}

Wictor Authentication Sample

Rather than using a web browser control to capture the username and password, Wictor’s sample takes the username and password as inputs and then uses the cookies to authenticate against the service. The following sample uses the same client object model code but with a different authentication code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Client;

namespace Wictor.Office365.ClaimsDemo {
    class Program {
        static void Main(string[] args) {
            if (args.Count() != 3) {
                Console.WriteLine("Syntax: Wictor.Office365.ClaimsDemo.exe
                                   url username password");
            }
            MsOnlineClaimsHelper claimsHelper = new MsOnlineClaimsHelper
                       (args[0], args[1], args[2]);
            using (ClientContext context = new ClientContext(args[0])) {
                context.ExecutingWebRequest +=
                   claimsHelper.clientContext_ExecutingWebRequest;

                context.Load(context.Web);
                
                context.ExecuteQuery();

                Console.WriteLine("Name of the web is: " + context.Web.Title);

                Console.WriteLine("Your lists are: ");
                ListCollection listCollection = context.Web.Lists;
                context.Load(
                    listCollection,
                    lists => lists
                        .Include(
                            list => list.Title,
                            list => list.Hidden)
                        .Where(list => !list.Hidden)
                     );
                context.ExecuteQuery();
                foreach (var list in listCollection)
                    Console.WriteLine(list.Title);

                
            }
        }
    }
}

List, View, and Event Enhancements

SharePoint 2010 offers a number of new list, view, and event enhancements that translates into developer enhancements for SharePoint Online. For example, there is support for referential integrity and formula validation in lists. In addition, all views of lists are now based on the XsltListViewWebPart, which makes customization easier. Finally, you can take advantage of new events with SharePoint—for example, when new sites and lists are added. Now dive into these new enhancements.

List Enhancements

Lists are the backbone of SharePoint. They’re where you create your data models and your data instances. They’re what your users understand are their documents or tasks. Without lists, your SharePoint site would cease to function because SharePoint uses lists for its own functionality and capability to run. With 2010, you can take advantage of new list enhancements and even new tools to work with your custom lists.

SharePoint Designer and Visual Studio Support

Before diving into the new enhancements in lists, you need to first look at the tools you can use to create your lists. The tools of choice are SharePoint Designer (SPD) and Visual Studio (VS). Both are good options, depending on what you want to do. If you want barebones, down to the metal, XML-style creation of lists, Visual Studio is your best bet. If you would rather work with a GUI, SPD provides a nice interface to work with your lists, whether it is creating columns or views, or customizing your list settings. Of course, you can use the built-in list settings in SharePoint to work with your lists, but SPD is a better choice if you are interested in a GUI editor.

SPD makes it easy to work with your lists, whether it’s creating new lists or modifying existing ones. SPD can make working with your columns, views, forms, content types, workflows, and even custom actions for your list easier. If you need to rapidly create a list or list definition, SPD is going to be the fastest and easiest way to work with your SharePoint lists. You must give up some control because SPD does not enable you to get down to the same level of customization that Visual Studio does, but you trade customizability for speed when you work with SPD. Figure 5-5 shows the List Settings user interface for SPD.

Also with SharePoint Designer, you can edit your list columns and permissions using a graphical interface. Figure 5-6 shows editing SharePoint columns using SharePoint Designer.

With Visual Studio, you can create list definitions and list instances. List definitions are a built-in project type for Visual Studio. Figure 5-7 shows the List definition project in Visual Studio.

warning.eps
Don’t expect nice designers when you create a list definition in Visual Studio 2010, but Visual Studio vNext may have them. Instead, with Visual Studio 2010, get ready to work with some XML. The nice thing about the list definition project in Visual Studio is that it enables you to create a list instance at the same time. Plus, your application is deployed as a feature, so you can reuse the list definition and instance in many sites. If you need ultimate flexibility, VS is your tool of choice for creating list definitions and customizing lists.

List Relationships with Cascade or Block

One common complaint about SharePoint is that it doesn’t behave like a relational database. For example, if you have a lookup between two lists and you want to have some referential integrity, SharePoint previously would not block or cascade your deletes between your lists. With 2010, SharePoint can now block or cascade your deletes between lists automatically. Now, don’t think SharePoint is going to become your replacement for SQL Server with this functionality. It is implemented more to make simple relationships work, and if you have a complex data model, you want to use SQL Server and surface its data through SharePoint using BCS and external lists.

The way that list relationships work is through the creation of a lookup between your lists. One new thing about lookups is that you can retrieve more than just the identifier, including additional properties from the list such as built-in or custom fields. On the list where you create your lookup, you can enforce the relationship behavior to either restrict deleting parent list items (if items exist in the list) related to the parent item, or cascade the delete from the parent list to the child list. Figure 5-8 shows the user interface for setting the properties of the lookup column to enforce relationship behaviors.

If you restrict the delete, SharePoint throws an error telling the user that an item is in the related list that exists and cancels deleting the error, as shown in Figure 5-9.

If you cascade the delete, SharePoint performs a transacted delete of the related items in the related list.

Through the user interface you cannot create cross-web lookups, but through the object model and by using site columns, you can. Cross-web lookups don’t support the referential integrity features such as cascading deletes. Also, referential integrity won’t be enforced for a lookup that has multiple values.

When working with the object model, you want to use the RelationshipDeleteBehavior property on your SPFieldLookup object. This property takes a value from the SPRelationshipDeleteBehavior enumerator of which the possible values are None, Cascade, or Restrict.

Two Properties affect relationships when you use them with the SPWebApplication class:

  • CascadeDeleteMaximumItemLimit: This enables you to specify as an integer the maximum number of cascaded items that SharePoint deletes. By default, this value is 1,000 items.
  • CascadeDeleteTimeoutMultiplier: This enables you to specify as an integer the timeout, which is 120 seconds by default.

To find lookup fields, you can use the GetRelatedFields method of your list, which returns an SPRelatedFieldCollection collection. From this collection, you can iterate through each related field. From there, you can retrieve properties, such as the LookupList that the field is related to, the ListID, the FieldID, or the relationship behavior when something is deleted from the list.

            using (SPSite site = new SPSite("http://intranet.contoso.com"))
            {
        
        
                SPList list = site.AllWebs[""].Lists["Orders"];
                SPRelatedFieldCollection relatedFields = list.GetRelatedFields();
                foreach (SPRelatedField relatedField in relatedFields)
                {
                    //Lookup the list for each
        
                    SPList relatedList = relatedField.LookupList;
                    MessageBox.Show(relatedField.ListId + " " +
                      relatedField.FieldId);
                    //MessageBox.Show("List Name: " +
                      relatedList.Title + " Relationship Behavior: " +
                      relatedField.RelationshipDeleteBehavior.ToString());
        
        
                }
}

Validation with Excel-Like Formulas

Another new list feature gives you the capability to do list validation using formulas. This is more of an end-user or power-user feature, but for simple validation scenarios, developers find this feature easy to use because it is quicker to write formulas than to write code. You can write validation at either the list level or the column level, depending on your needs. SharePoint also supports this approach for site columns that you add to your content types. Figure 5-10 shows setting the formula, and Figure 5-11 shows the custom error message that appears when the formula does not validate.

One of the easiest ways to understand which formulas you can enter into the validation rules is to connect Microsoft Access to your SharePoint list and use the formula editor in Access. SharePoint supports the same formula functions as Access, so you can use string manipulation, logic, financial, conversion, and date/time functionality. In the API, you can use the SPList.ValidationFormula and SPField.ValidationFormula properties to get and set your formulas.

Ensuring Uniqueness

Another new feature of lists is the capability to ensure uniqueness for the values in your columns. Previously, SharePoint didn’t require unique values so that multiple items could have the same value for a field. With uniqueness, SharePoint can use the field as an index to make lookups faster because the field is guaranteed to have a unique value.

List Joins

Just like a database, SharePoint supports list joins. Again, SharePoint won’t provide as much functionality as a relational database because its data model sits above the bare-metal database, but compared to 2007 the join functionality is a welcome addition. SharePoint can perform left and inner joins but not right joins. An inner join is where you combine the values from the data sources based on the join predicate, such as “show me all employees who are in a particular department based on their department ID,” which joins an employee list and a department list, both of which have department IDs in them. A left join or left outer join just means that anything that appears in the leftmost list, even if it does not have matching entries in the other list, is returned in the result set.

The following code performs a join across two lists on a lookup field. You need to set the Joins property on your SPQuery object with the join you want to perform. In the code, you join on the Customers list, where the customer is the same as the Customer in the Orders list.

Beyond setting the Joins property, you must specify a value for the ProjectedFields property. This property gets fields from the lookup list. You can alias the field by using the Name attribute and tell SharePoint the field name by using the ShowField attribute. When you get your results, you must use the SPFieldLookupValue object to display the values for your projected fields.

SPList OrderList = web.Lists["Orders"];
            SPQuery CustomerQuery = new SPQuery();
            CustomerQuery.Joins =
                "<Join Type='INNER' ListAlias='Customers'>" +
                        "<Eq>" +
                            "<FieldRef Name='Customer' RefType='Id' />" +
                            "<FieldRef List='Customers' Name='ID' />" +
                        "</Eq>" +
                "</Join>";
            StringBuilder ProjectedFields = new StringBuilder();
            ProjectedFields.Append("<Field Name='CustomerTitle'
              Type='Lookup' List='Customers' ShowField='Title' />");
            ProjectedFields.Append("<Field Name='CustomerAddress'
              Type='Lookup' List='Customers' ShowField='CustomerNum' />");
            CustomerQuery.ProjectedFields = ProjectedFields.ToString();
            SPListItemCollection Results = OrderList.GetItems(CustomerQuery);
            foreach (SPListItem Result in Results)
                {
                SPFieldLookupValue CustomerTitle = new
                  SPFieldLookupValue(Result["CustomerTitle"].ToString());
                SPFieldLookupValue CustomerAddress = new
                  SPFieldLookupValue(Result["CustomerAddress"].ToString());
        
                MessageBox.Show(Result.Title + " " + CustomerTitle.LookupValue + "
                  " + CustomerAddress.LookupValue);
        
                }

Customize Default Forms Using Web Parts or InfoPath

One of the new features of lists is that you can customize the default forms for your list items. SharePoint 2010 moved to using web part pages for the default forms, so your customization can be as easy as adding new web parts to the existing default forms, or you can even replace the default forms with your own custom InfoPath forms. With SharePoint 2010, you can modify the New, Display, and Edit forms.

When you use the Ribbon option to edit the form in InfoPath, InfoPath automatically launches. Connect to your list and display your form, as shown in Figure 5-12.

View Enhancements

The biggest change with views in 2010 is the technology used to display them. 2010 uses the SharePoint Designer XsltListViewWebPart as the default web part for viewing lists. This is much better than 2007 for a number of reasons:

  • XSLT views enables you to replace your use of CAML to create views. You can also use standards-based XSLT to define your view.
  • With the addition of the new XSLT view, performance is better than 2007.
  • Editing with SPD is easier because the XSLT view technology is originally an SPD technology so SPD has extensive capabilities to edit it.
  • The same view technology is used for all SharePoint lists, including standard SharePoint lists and external lists.

The easiest way to understand, prototype, and get sample code is to use SPD to design your views and then view the code that SPD creates to work with your XSLT views. For example, you may want to create a view that makes any numbers that meet or exceed a limit turn red, yellow, or green and implements a custom mouseover event. With SPD, this is as easy as using the conditional formatting functionality and the IntelliSense built-in to modify the view. Figure 5-13 shows the editing of the view in SPD.

The following code shows the conditional formatting XSLT that SPD generates for you:

        <div align="right" onmouseover="javascript:alert('You moused over!'),">
                       <xsl:attribute name="style">
                              <xsl:if test="$thisNode/@Rating. = 3" 
                                   xmlns:ddwrt="http://schemas.microsoft.com
           /WebParts/v2/DataView/runtime" ddwrt:cf_explicit="1">background-color:
           #FFFF00;</xsl:if>
                              <xsl:if test="$thisNode/@Rating. &gt;= 4"
            xmlsn:ddwrt="http://schemas.microsoft.
            com/WebParts/v2/DataView/runtime" ddwrt:cf_explicit="1">
            background-color:#71B84F;</xsl:if>
                              <xsl:if test="$thisNode/@Rating. &lt;= 2" 
                                   ddwrt:cf_explicit="1" xmlns:ddwrt="
            http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
                    background-color:#FF0000;</xsl:if>
        
                       </xsl:attribute>

To work with views programmatically, use the SPView object and SPViewCollection. You can add new views, modify existing views, or delete views. The following are a few properties in which you should be interested:

  • DefaultView off the SPList object: This property returns an SPView object, which is the default view for your list.
  • RenderAsHTML: This method returns the HTML that your view renders.
  • PropertiesXml, Query, SchemaXml, and Xsl: These return the properties, query, schema, and XSL used in your list, respectively.

Events Enhancements

With 2010, you can take advantage of six new events, including WebAdding, WebProvisioned, ListAdding, ListAdded, ListDeleting, and ListDeleted. This is in addition to the events that were introduced in SharePoint 2007, such as ItemAdding, ItemUpdating, and ItemUpdated. There are also other enhancements beyond new events, including new registration scopes to support the new events, new tools support in Visual Studio, support for post-synchronous events, and custom error pages and redirection.

New Events

As part of SharePoint 2010, you can take advantage of ten new events. These events enable you to capture creation and provisioning of new webs, and the creation and deletion of lists. Table 5-3 goes through each of the events and what you can use them for.

Table 5-3: New 2010 Events

Name Description
WebAdding A synchronous event that happens before the web is added. Some URL properties may not exist yet for the new site because the new site does not exist yet.
WebProvisioned A synchronous or asynchronous after-event that occurs after the web is created. You make the event synchronous or asynchronous by using the Synchronization property and setting it to Asynchronous or Synchronous. This is located under the Receiver node in the elements.xml file for your feature.
ListAdding A synchronous event that happens before a list is created.
ListAdded A synchronous or asynchronous after-event that happens after a list is created but before it is presented to the user.
ListDeleting A synchronous event that happens before a list is deleted.
ListDeleted A synchronous or asynchronous after-event that happens after a list is deleted.
WorkflowStarting An event that occurs before a workflow starts. All Workflow events get SPWorkflowEventProperties object that contains information about the workflow.
WorkflowStarted An event that occurs after a workflow has started.
WorkflowPostponed An event that occurs if a workflow is postponed.
WorkflowCompleted An event that occurs after a workflow is completed.

Using these events is the same as writing event receivers for any other type of events in SharePoint. The nice thing about writing event receivers with SharePoint 2010 is that you have Visual Studio 2010 support for writing and deploying your event receivers. Figure 5-14 shows the new event receiver template in Visual Studio, where you can select the type of event receiver you want to create and the events you want to listen for in your receiver. After you finish the wizard inside of Visual Studio, you can modify your feature definition or your code using the standard Visual Studio SharePoint tools. Plus, with on-click deployment and debugging, it’s a lot easier to get your receiver deployed and start debugging it.

The code that follows shows you how to use the new web events in SharePoint. The code writes the properties for the event to an event log list. The sample applications with this book include the same sample for the new list events, but for brevity only the web event sample code is shown. If you wanted to, you could cancel the before-events, such as WebAdding, ListDeleting, or ListAdding, by setting the Cancel property to false. These events fire even in the Recycle Bin, so if you restore a list or delete a list, you get an event for those actions.

download.eps
namespace WebEventReceiver.EventReceiver1
{
    /// <summary>
    /// Web Events
    /// </summary>
    public class EventReceiver1 : SPWebEventReceiver
    {
        /// <summary>
        /// A site is being provisioned.
        /// </summary>
        public override void WebAdding(SPWebEventProperties properties)
        {
            LogWebEventProperties(properties);
        
            base.WebAdding(properties);
        }
        
        /// <summary>
        /// A site was provisioned.
        /// </summary>
        public override void WebProvisioned(SPWebEventProperties properties)
        {
            LogWebEventProperties(properties);
            base.WebProvisioned(properties);
        }
        
        
        private void LogWebEventProperties(SPWebEventProperties properties)
        {
        
            StringBuilder sb = new StringBuilder();
        
            try
            {
                sb.AppendFormat("{0} at {1}

", properties.EventType,
                  DateTime.Now);
                sb.AppendFormat("Cancel: {0}
", properties.Cancel);
                sb.AppendFormat("ErrorMessage: {0}
", properties.ErrorMessage);
                sb.AppendFormat("EventType: {0}
", properties.EventType);
                sb.AppendFormat("FullUrl: {0}
", properties.FullUrl);
                sb.AppendFormat("NewServerRelativeUrl: {0}
",
                  properties.NewServerRelativeUrl);
                sb.AppendFormat("ParentWebId: {0}
", properties.ParentWebId);
                sb.AppendFormat("ReceiverData: {0}
", properties.ReceiverData);
                sb.AppendFormat("RedirectUrl: {0}
", properties.RedirectUrl);
                sb.AppendFormat("ServerRelativeUrl: {0}
",
                  properties.ServerRelativeUrl);
                sb.AppendFormat("SiteId: {0}
", properties.SiteId);
                sb.AppendFormat("Status: {0}
", properties.Status);
                sb.AppendFormat("UserDisplayName: {0}
",
                  properties.UserDisplayName);
                sb.AppendFormat("UserLoginName: {0}
", properties.UserLoginName);
                sb.AppendFormat("Web: {0}
", properties.Web);
                sb.AppendFormat("WebId: {0}
", properties.WebId);
            }
            catch (Exception e)
            {
                sb.AppendFormat("Exception accessing Web Event Properties: {0}
",
                 e);
        
            }
        
        
        
        }
        
    }
}

code snippetWebEventReceiver/Receiver1.cs

One quick comment regarding this code is that with the AfterProperties property, which you can use to determine if the item has been modified during an update, you must decode the column name because SharePoint encodes spaces and other special characters in column name, for example a Hello World column having an internal column name of Hello_x0020_World. You need to use the System.XML.XmlConvert.DecodeName to decode and System.XML.XmlConvert.EncodeName to encode these names.

New Event Registration Feature

To support the new events, SharePoint now provides a new mechanism for registering your event receivers, using the <Receivers> XML block. This new feature enables you to register your event receiver at the site collection level by using the new Scope attribute and to set the receiver either to Site or Web, depending on the scope that you want for your event receiver. If you set it to Web, your event receiver works across all sites in your site collection, as long as your feature is registered across all these sites as well. You can tell SharePoint to have the receiver work on only the root site by using the RootWebOnly attribute on the <Receivers> node. The last new enhancement is the ListUrl attribute, which enables you to scope your receiver to a particular list by passing in the relative URL.

Post-Synchronous Events

With SharePoint 2007, all your after-events were asynchronous, so if you wanted to perform some operations after the target—such as an item—was created but before it was presented to the user, you couldn’t. Your event receiver would fire asynchronously, so the user might already see the target, and then if you modified properties or added values, the user experience might not be ideal. With SharePoint 2010, there is support for synchronous after-events, such as listadded, itemadded, or webprovisioned. To make the events synchronous, you need to set the Synchronization property either through the SPEventReceiverDefinition object model if you are registering your events programmatically or by creating a node in your <Receiver> XML that sets the value to synchronous or asynchronous. That’s it if you run SharePoint on-premises. Unfortunately, post-synchronous events are not supported by SharePoint Online and Sandboxed Solutions.

Custom Error Pages

With SharePoint 2007, you could only cancel events and return an error message to the user, but that provided limited interactivity and not much help to the user beyond what your error message said. With 2010 events, you can cancel the event on your synchronous events and redirect the user to a custom error page that you create. This enables you to have more control over what users see, and you can try to help them figure out why their action is failing. The custom error pages and redirection work only for presynchronous events, so you cannot do this for post-synchronous events such as ListAdded. Plus, this works only with browser clients. Office clients show an error message if you cancel the event.

The way to implement custom error pages is to set the Status property on your property bag for your event receiver to SPEventReceiverStatus.CancelWithRedirectUrl, set the RedirectUrl property to a relative URL for your error page, and set the Cancel property to true, as shown in the following code:

properties.Cancel = true;
properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
properties.RedirectUrl = "/_layouts/mycustomerror.aspx";

The Ribbon

In SharePoint, one of the major changes to which you must acclimate yourself is the new Ribbon user interface. The Ribbon provides a contextual tab model and a fixed location at the top of the page so that it never scrolls out of view. In terms of controls, if you have worked with the Office client Ribbon, the SharePoint Ribbon has near parity with the client. The areas missing between the client and the server are controls that provide more complex functionality. The best example of a control that is on the client but not on the server is the in-Ribbon gallery control. This gallery is used, for example, when you click Styles button on the Home tab in Word and you can see all the styles in the document, or in Excel, when you click the Cell Styles button in the Home tab right from the gallery control.

The Ribbon does support the majority of controls that you need, and the main unit of organization for these controls is tabs. You can build custom tabs that contain your custom controls. Even though the server can support up to 100 tabs, it is recommended that you try to limit the tabs to 4–7 to avoid confusing your users. Table 5-4 lists the different controls supported by SharePoint with a description of each.

Table 5-4: SharePoint Ribbon Controls

Name Description
Button A simple button that a user can click.
Checkbox A check box that can either have a label or not.
ColorPicker A grid of colors/styles that the user can use to choose a color.
ComboBox A menu of selections that the user can type or select.
DropDown A menu of selections that the user can click to select.
FlyoutAnchor An anchor button that includes a button that triggers a drop-down menu.
InsertTable A 10 x 10 grid of boxes where a user can specify dimensions for a table.
Label A line of text.
Menu A container for showing pop-ups. You can place it inside other controls that show menus, such as the FlyoutAnchor.
MenuSection A section of a menu. You can give the menu a title and controls.
MRUSplitButton A split button control that remembers the last item that a user most recently used out of its submenu with that item bubbling up into the “button” part.
Spinner Enables the user to enter values and “spin” through them using the up and down arrows.
SplitButton A control with a button and a menu.
Textbox A box where a user can enter text.
ToggleButton A button with an on/off state.

If you look at the architecture for the Ribbon in SharePoint, you find that SharePoint uses Ajax, on-demand JavaScript, caching, and CSS layout to implement the Ribbon. In addition, the Ribbon uses no tables, so it is all constructed from CSS styling and hover effects that make the Ribbon function. For this reason, you should investigate the CSS classes that the Ribbon uses, especially corev4.css. Look through the styles beginning with ms-cui, which is the namespace for the Ribbon in the CSS file.

Ribbon Extensibility

The Ribbon is completely extensible in that you can add new tabs or controls, or you can remove the OOB controls on existing tabs. You can entirely replace the Ribbon just by using your own custom Master Page. The Ribbon does support backward compatibility, so any custom actions you created for 2007 toolbars appear in a custom commands tab in the Ribbon.

To understand how to customize the Ribbon, look through the different actions you normally want to perform and the way to achieve those actions. Before diving in, though, you need to get a little bit of grounding in how the architecture of the Ribbon works.

The architecture of the Ribbon enables you to perform your customizations by creating XML definition files. At run time, the Ribbon merges your XML definitions with its own to add your custom Ribbon elements and code to handle interactions. For more complex customizations, such as writing more complex code, consider using a JavaScript page component. The next section looks at both options.

If you want to understand how SharePoint implements its Ribbon XML elements, go to %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATEGLOBALXML on your local SharePoint Server and find the file cmdui.xml. In that file, you see the SharePoint default Ribbon implementation, and it is a good template to look at as you implement your own Ribbon controls because it can help you to understand how certain controls work inside the SharePoint environment.

If all the different types, elements, and attributes are confusing, take a look at the XSD for the Ribbon by browsing to %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATEXML and look at cui.xsd and wss.xsd. These XSD files can help you understand what SharePoint expects for structure and content to make your custom user interface.

XML-Only Operations

When you write your custom XML to define your Ribbon, SharePoint combines your XML changes with its own definitions in cmdui.xml, and the merged version displays the new Ribbon interface. Even though you use XML, you want to deploy your custom Ribbon XML with a SharePoint feature. The easiest way to create a feature is using Visual Studio 2010. To do so, follow these steps:

1. Create an empty SharePoint project and customize the feature name and deployment path. Ribbon extensions can be Sandboxed Solutions, which you learn about later in this chapter, so they can run in a restricted environment like SharePoint Online.
2. Create a new feature. You do this by adding a new file to your project and by creating an empty elements file. This is where you place the XML for your new Ribbon interface. To understand the XML, break it down piece by piece.

The snippet that follows shows some XML from a custom Ribbon:

download.eps
<?xml version="1.0" encoding="utf-8"?>
        
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        
  <CustomAction
        
    Id="CustomRibbonTab"
        
    Location="CommandUI.Ribbon.ListView"
        
    RegistrationId="101"
        
    RegistrationType="List"
        
    Title="My Custom UI"
        
    Sequence="5"
        
>
        
        
        
  </CustomAction>
        
</Elements>

code snippetElements.xml

First, all XML for the Ribbon will be wrapped in a CustomAction node. This tells SharePoint that you want to perform customization of the user interface. For the node, you can set attributes to give the specifics for your customization. One key node is the Location attribute, which tells SharePoint where your customization should appear, such as on a list view, a form, or everywhere. The pattern match for the Location attribute is Ribbon.[Tab].[Group].Controls._children. Table 5-5 outlines the options for the Location attribute.

Table 5-5: Location Attribute Settings

Name Description
CommandUI.Ribbon Customization appears everywhere.
CommandUI.Ribbon.ListView Customization appears when ListView is available.
CommandUI.Ribbon.EditForm Customization appears on the edit form.
CommandUI.Ribbon.NewForm Customization appears on the new form.
CommandUI.Ribbon.DisplayForm Customization appears on the display form.

One other piece to notice in the XML is the RegistrationID. When combined, the registration ID and the registration type define the set of content you want your custom UI to appear for. The registration type can be a list, content type, file type, or a progID. The registration ID is mostly used with the content type registration type, and it’s where you specify the name of your content type. This enables you to customize even further when your custom UI appears, depending on what displays in SharePoint.

The Sequence attribute, which is optional, enables extensions to be placed in a particular order within a set of subnodes of a node. The built-in tab controls use a sequence of 100, so you want to avoid using any multiples of 100 for your sequence in tabs. Groups use a sequence in multiples of 10, so you should avoid multiples of 10. For example, if you had a Ribbon tab with the following groups—Clipboard, Font, Paragraph—you could set their sequence attributes to 10, 20, and 30, respectively. Then, you could insert a new group between the Clipboard and the Font groups via the feature framework by setting its sequence attribute to 15. A node without a Sequence attribute is sorted last.

Because the current XML does nothing (you haven’t added any new commands to the user interface), you can expand your XML. To add commands, you create a CommandUIExtension element. This CommandUIExtension is a wrapper for a CommandUIDefinitions element, which is a container for a CommandUIDefinition.

The CommandUIDefinition element has an attribute that enables you to set the location for your UI. In the example, you add a new button to the new set of controls in a document library. You can see _children as part of the location that tells SharePoint not to replace a control, but instead add this as a child control on that user interface element.

The CommandUIDefinition element is where you create your user interface elements, whether they are tabs, groups, or individual controls. In this simple example, you create a button that has the label Click me! which has two images. The image that is used depends on whether the button is rendered in 16 × 16 or 32 × 32 size (because the Ribbon has the ability to scale based on the screen resolution), and SharePoint calls JavaScript code to perform the action when the button is pressed. The attributes are self-explanatory, except for one — TemplateAlias. TemplateAlias controls whether your control is displayed in 16 × 16 or 32 × 32. If you set it to o1, you get a 32 × 32 icon, and o2 makes it 16 × 16. You can define your own template, but most times you use the built-in values of o1 or o2.

So, how do you call code from your Ribbon code? You wrap your code in a CommandUIHandler, where you can put in the CommandAction attribute, which is inline JavaScript that handles the action for your button. If you do not want to place your JavaScript inline, you can use the ScriptSrc attribute and pass a URL to your JavaScript file. Figure 5-15 shows the new custom button on the top-left of the Documents Ribbon.

download.eps
<CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.Documents.New.Controls._children">
          <Button
              Id="Ribbon.Documents.New.RibbonTest"
              Alt="Test Button"
              Sequence="5"
              Command="Test_Button"
              LabelText="Click me!"
              Image32by32="/_layouts/images/ribbon_blog_32.png"
              Image16by16="/_layouts/images/ribbon_blog_16.png"
              TemplateAlias="o1" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
        
      <CommandUIHandlers>
        <CommandUIHandler
          Command="Test_Button"
          CommandAction="javascript:alert('I am a test!')," />
      </CommandUIHandlers>
        
    </CommandUIExtension>

code snippetElements.xml

Replacing Existing Controls

At times you may want to replace existing, built-in controls from your SharePoint deployment and put your own controls in place. You can replace the entire Ribbon if you want. The way to replace an existing control is to insert your own custom control that overwrites the ID of the control you want to replace and also has a lower sequence number. The key point is you must get the Location attribute set to the same ID as the control ID you want to replace.

The following code replaces the new folder button for a document library. There are a couple of things to highlight in this code:

  • Notice the Location attribute in the CommandUIDefinition element. It maps exactly to an ID in the cmdUI.XML file. SharePoint parses both files, and if it finds the same ID, it places the one with the lower sequence into the final XML that is parsed and used to create the Ribbon layout.
  • Notice the use of the $Resources for globalization and pulling from a compressed image. The format map on your server contains lots of icons, and the XML code contains the coordinates to pull the new folder icon from the larger image.
download.eps
<CustomAction Id="Ribbon.Documents.New.NewFolder.ReplaceButton"
      Location="CommandUI.Ribbon"
      RegistrationId="101"
      RegistrationType="List"
      Title="Replace Ribbon Button"
  >
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.Documents.New.NewFolder">
          <Button Id="Ribbon.Documents.New.NewFolder.ReplacementButton"
            Command="MyNewButtonCommand"
            Image16by16="/_layouts/$Resources:core,Language;/images/
            formatmap16x16.png?vk=4536"
            Image16by16Top="-240" Image16by16Left="-80"
            Image32by32="/_layouts/$Resources:core,Language;/images/
            formatmap32x32.png?vk=4536"
            Image32by32Top="-352" Image32by32Left="-448"
            ToolTipTitle="Create a New Folder"
            ToolTipDescription="Replaced by XML Custom Action"
            LabelText="My New Folder"
            TemplateAlias="o1" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="MyNewButtonCommand"
          CommandAction="javascript:alert('New Folder Replaced.')," />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>

code snippetElements.xml

Figure 5-16 shows the replaced button. Even though the icon image is the same, the action performed when the user clicks the icon is the custom code.

Using URL Actions

You may be wondering how you use just URLs with token replacements, rather than having to write JavaScript as the payload for your controls. To do this, you use the URLAction node in your CustomAction node. Your URL actions can be simple URLs, or you can use token replacement, such as ListID or ItemID. You can also place inline JavaScript if you want. When you use URL actions, you can make a simple CustomAction node to handle your changes, as shown in the following listing, which adds a new toolbar item to the new announcements form:

download.eps
<CustomAction
      Id="SimpleAction"
      RegistrationType="List"
      RegistrationId="104"
      ImageUrl="/_layouts/images/saveas32.png"
      Location="NewFormToolbar"
      Sequence="10"
      Title="Custom Button"
      Description="This is an announcement button."
                         >
    <UrlAction Url="javascript:alert('Itemid={ItemId} and Listid={ListId}'),"/>
        
  </CustomAction>

code snippetElements.xml

Why Doesn’t Your Button Show Up?

Troubleshooting your custom Ribbon user interface is not as easy as you would think. If you get something wrong, your customizations just do not appear. Even though this can be frustrating, there are a couple of places to start looking to troubleshoot your issues:

  • Fire up your JavaScript debugger and set a breakpoint. Because the Ribbon is implemented in JavaScript, you can set breakpoints in the code in SP.Ribbon.debug.js. Also, make sure to look at the XML in cmdui.xml to see if there is a pattern your code resembles so you can model your code on that pattern.
  • Check that the sequence is set correctly and does not collide with other controls. SharePoint uses sequences in multiples of 10 or 100, so make sure that you are not using those multiples.
  • Check that the name of your function is the same as your command attribute on your control definition and your CommandUIHandler. If you get the name wrong, even with the same spelling but different cases, your commands don’t fire.
  • Check the registration for your CustomAction. Did you register your UI on a document library? When you test your code, are you in a document library? Or did you register on an edit form for announcements?
  • Check the toolbar type property. This ties in with the previous tip and applies when you are wondering why your user interface does not appear if you select your list instance as a web part in another page. For example, suppose that you are on your home page and you added your Shared Documents library as a web part on that page. When you select the document library as the web part, your button does not appear on the menu. The culprit behind this is the toolbar type property for the web part under web part properties. By default, this property is set to summary toolbar, and you want it to be set to full toolbar because the summary toolbar doesn’t load any of the customizations for the toolbar.

Rights and Site Administrators

As part of the definition of your CustomAction, you can also specify the rights required to view your custom interface. You can specify a Rights attribute, which takes a permissions mask that SharePoint will logically AND together, so the user must have all the permissions to view the new user interface. Permissions can be any from SPBasePermissions, such as ViewListItems or ManageLists.

Beyond permissions, you can also specify whether a person must be a site administrator to view the new user interface. To do this, create a Boolean RequireSiteAdministrator attribute, and set it to true to require the user to be a site administrator. This is useful for an administration-style UI that you do not want every user to see.

Hiding Existing Controls

Sometimes you want to hide controls rather than replace them. For example, the control may not make sense in the context of your application. To hide UI controls, use the HideCustomAction element and set the attributes to the nodes you want to hide, as shown in the following code:

<HideCustomAction
    Id="HideNewMenu"
    Location="Microsoft.SharePoint.StandardMenu"
    GroupId="NewMenu"
    HideActionId="NewMenu">
  </HideCustomAction>

Writing Code to Control Menu Commands

If you prefer to write code instead of XML, you can use the SharePoint object model to make changes to menu items. This hasn’t changed from the EditControlBlock (ECB) technologies in 2007 and is shown here for completeness:

using (SPSite site = new SPSite("http://intranet.contoso.com"))
            {
            using (SPWeb web = site.RootWeb)
                {
                SPUserCustomAction action = web.UserCustomActions.Add();
                action.Location = "EditControlBlock";
                action.RegistrationType =
                SPUserCustomActionRegistrationType.FileType;
                action.RegistrationId = "docx";
                action.Title = "Custom Edit Command For Documents";
                action.Description = "Custom Edit Command for Documents";
                action.Url = "{ListUrlDir}/forms/editform.aspx?Source={Source}";
                action.Update();
                web.Update();
                site.Close();
                }
            }

Creating New Tabs and Groups

Beyond just creating buttons, you may want to add new tabs and groups. To do this, you just need to create Tab and Group elements in your code. The process is close to the same as adding a button with some minor tweaks. Figure 5-17 shows a custom tab and group with three controls: two buttons and a combo box.

The following code shows the beginning of the new tab and group. As you can see, the XML looks similar to the earlier XML when you create a button. This code also defines a tab:

<!~DHCreate new Tab and Group~DH>
  <CustomAction
  Id="MyCustomRibbonTab"
  Location="CommandUI.Ribbon.ListView"
  RegistrationId="101"
  RegistrationType="List">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.Tabs._children">
          <Tab
            Id="Ribbon.CustomTabExample"
            Title="My Custom Tab"
            Description="This holds my custom commands!"
            Sequence="501">
            <Scaling
              Id="Ribbon.CustomTabExample.Scaling">
              <MaxSize
                Id="Ribbon.CustomTabExample.MaxSize"
                GroupId="Ribbon.CustomTabExample.CustomGroupExample"
                Size="OneLargeTwoMedium"/>
              <Scale
                Id="Ribbon.CustomTabExample.Scaling.CustomTabScaling"
                GroupId="Ribbon.CustomTabExample.CustomGroupExample"
                Size="OneLargeTwoMedium" />
            </Scaling>
            ...

Some things to note about this code:

  • Tabs support scaling, so if the page is resized, you can control how your buttons look. Scaling has a MaxSize node that is the maximum size your buttons will be and a Scaling node that’s used if the page is resized. You need to know a couple of things about the Scaling node.
    • It has a GroupID attribute, which should point to the group that the scaling affects.
    • It has a Size attribute, which has a descriptor of the style of your group. For example, you can have LargeLarge if you have two buttons and want both to be large buttons, or LargeMedium if you want a large and a medium button.
  • After creating the tab, the code then creates the group. A group can have commands, descriptions, and all the standard attributes that other controls have. A group is a logical container for your controls and physically lays out the controls in your group with your description at the bottom of the group user interface. Your Group node contains the definition of your controls that live within that group.
    <Groups Id="Ribbon.CustomTabExample.Groups">
                  <Group
                    Id="Ribbon.CustomTabExample.CustomGroupExample"
                    Description="This is a custom group!"
                    Title="Custom Group"
                    Sequence="52"
                    Template="Ribbon.Templates.CustomTemplateExample">
              <Controls>
    ...

After you have your tab and group, you create your controls just as you would if the control were an extension of an existing group.

A combo box has more commands than a button has because users can interact more with a combo box by selecting options from its list. Also, you can populate a combo box either statically, as the code does, by creating menu options in the XML, or by passing a function that SharePoint calls to populate the combo box dynamically. Look at the PopulateDynamically, PopulateOnlyOnce, and PopulateQueryCommand sections of the code because these operate the combo box options.

In addition, you can set attributes, such as AllowFreeForm and InitialItem, to control whether users can type values into the combo box and select the initial item in the combo box.

The code earlier showed you how to create a button, so the following code shows you how to create a combo box as a control in your group:

download.eps
<ComboBox
 Id="Ribbon.CustomTabExample.CustomGroupExample.
 Combobox"  Sequence="18"
 Alt="Ribbon.CustomTabExample.CustomGroupExample.
 Combobox_Alt"
 Command="Ribbon.CustomTabExample.CustomGroupExample.
  Combobox_CMD"
 CommandMenuOpen="Ribbon.CustomTabExample.
  CustomGroupExample.Combobox_Open_CMD"
 CommandMenuClose="Ribbon.CustomTabExample.
  CustomGroupExample.Combobox_MenuClose_CMD"
 CommandPreview="Ribbon.CustomTabExample.
  CustomGroupExample.Combobox_Preview_CMD"
CommandPreviewRevert="Ribbon.CustomTabExample.
 CustomGroupExample.Combobox_PreviewRevert_CMD"
                                InitialItem="StaticComboButton1"
                                AllowFreeForm="true"
                                PopulateDynamically="false"
                                PopulateOnlyOnce="true"
                                PopulateQueryCommand="Ribbon.CustomTabExample.
                                CustomGroupExample.Combobox_PopQuery_CMD"
                                Width="125px"
                                TemplateAlias="cust3">
                    <Menu Id="Ribbon.CustomTabExample.CustomGroupExample.
                     Combobox.Menu">
                      <MenuSection
                        Id="Ribbon.CustomTabExample.CustomGroupExample.Combobox.
                         Menu.MenuSection"
                        Sequence="10"
                        DisplayMode="Menu32">
                        <Controls Id="Ribbon.CustomTabExample.CustomGroupExample.
                          Combobox.Menu.MenuSection.Controls">
                          <Button
                            Id="Ribbon.CustomTabExample.CustomGroupExample.
                            Combobox.Menu.MenuSection.Button1"
                            Sequence="10"
                            Command="Ribbon.CustomTabExample.CustomGroupExample.
                             Combobox.Menu.MenuSection.Button1_CMD"
                            CommandType="OptionSelection"
                            Image16by16="/_layouts/$Resources:core,Language;
                            /images/formatmap16x16.png?vk=4536"
                            Image16by16Top="-48" Image16by16Left="-112"
                            Image32by32="/_layouts/$Resources:core,Language;
                            /images/formatmap32x32.png?vk=4536"
                            Image32by32Top="-192" Image32by32Left="-32"
                            LabelText="StaticComboButton1"
                            MenuItemId="StaticComboButton1"/>
                          <Button
                            Id="Ribbon.CustomTabExample.CustomGroupExample.
                            Combobox.Menu.MenuSection.Button2"
                            Sequence="20"
                            Command="Ribbon.CustomTabExample.CustomGroupExample.
                            Combobox.Menu.MenuSection.Button2_CMD"
                            CommandType="OptionSelection"
                            Image16by16="/_layouts/$Resources:core,Language;
                           /images/formatmap16x16.png?vk=4536"
                           Image16by16Top="-32" Image16by16Left="-112"
                            Image32by32="/_layouts/$Resources:core,Language;
                            /images/formatmap32x32.png?vk=4536"
                            Image32by32Top="-384" Image32by32Left="-352"
                            LabelText="StaticComboButton2"
                            MenuItemId="StaticComboButton2"/>
                        </Controls>
                      </MenuSection>
                    </Menu>
                  </ComboBox>

code snippetElements.xml

After your controls, you can handle the commands to which your controls need to respond in the CommandUIHandlers node. For the complete listing for all the code for the commands, tab, and group, refer to the sample code for this chapter on www.wrox.com.

ToolTips and Help

Your user interface should guide the user on how to use your controls. To aid in this, the Ribbon supports ToolTips and also linking to help topics. Both of these are set using the ToolTip* set of commands such as ToolTipTitle and ToolTipDescription. The following code sets the title, description, help topic, and shows the keyboard shortcut for your control:

ToolTipTitle="Tooltip Title"
        
ToolTipDescription="Tooltip Description"
        
ToolTipShortcutKey="Ctr-V, P"
        
ToolTipImage32by32="/_layouts/images/PasteHH.png"
        
ToolTipHelpKeyWord="WSSEndUser"

Writing a Page Component

So far, you have been writing code inline in your XML to handle your control commands. However, SharePoint does enable you to write more complex handlers for your user interface if you need to. You should try to keep your code in the XML definition if you create simple buttons with simple code. However, if you create Ribbon extensions dynamically populated via code; if your Ribbon requires variables beyond the default ones you can get with {SiteUrl}, {ItemId}, or other similar placeholders; or if your code is so long that it makes sense from a manageability standpoint to break it out separately, you should consider creating a page component.

A page component is a set of JavaScript code that can handle commands from your user interface customizations. Your JavaScript must derive from the CUI.Page.PageComponent and implement the functions in the prototype definition of the RibbonAppPageComponent. As part of this code, you can define the global commands that your page component works with. These are the tabs, groups, and commands, such as buttons, that you handle in your page component. In addition, you can define whether your global commands should be enabled through the canHandleCommand function. This is the function you use to enable or disable your control. For example, you may want to enable your control only if the context is correct for your control to work, such as when an item is selected in the user interface or when the right variables are set. If you return false to this function, your user interface is grayed out.

In addition, the page component enables you to handle the command, so if someone clicks your button, you can run code to handle that click.

After you define all this JavaScript, you need to register your script with the PageManager that SharePoint creates so that SharePoint knows to call the script when actions are performed on the user interface.

Notice a couple of points about the following code:

  • You can get the selected items by using the SP.ListOperation.Selection.getSelectedItems() method. This is a good way to determine whether any items are selected in the user interface so that you can enable or disable your control. You can go a step further and look for particular properties or item types by writing some more code.
  • You can write more functions to do things such as populate your drop-downs dynamically or change the buttons on your user interface. You can write your Ribbon component to perform a postback to the server that a custom .NET program can handle so that you can avoid writing JavaScript. If you do this, you want the command action to be a postback command such as CommandAction=“javascript:__doPostBack('CustomButton', ‘{ItemUrl}')“. Then, on the backend that captures the postback, you can handle the postback in two ways:
    • You can look at the __EVENTTARGET variable in the page request variables to see if your custom command caused the postback.
    • You can spin up a Ribbon object—make sure to reference Microsoft.SharePoint.WebControls—and implement the IPostBackHandler interface. You can then check if your custom button generated the postback by deserializing the postback event using the SPRibbonPostBackCommand.DeserializePostBackEvent method, and then checking the ID of the control that generated the event with the ID of the control you were looking for. If they match, handle the event.
The first method is simpler and requires less code than the second method.

Figure 5-18 shows the custom button on the Ribbon. Also, the figure has a custom color picker and other buttons. You can see the code to implement these other buttons in the sample code for this chapter.

download.eps
<CustomAction
  Id="SharedDocAction"
  RegistrationType="List"
  RegistrationId="101"
  Location="CommandUI.Ribbon.ListView">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
         Location="Ribbon.Documents.New.Controls._children">
          <Button
           Id="CustomContextualButton"
           Alt="MyDocumentsNew Alt"
           Command="MyDocumentsNewButton"
           LabelText="ScriptBlock Button"
           ToolTipTitle="Tooltip Title"
           Image16by16="/_layouts/$Resources:core,Language;
           /images/formatmap16x16.png?vk=4536" Image16by16Top="-80"
           Image16by16Left="0"
           Image32by32="/_layouts/$Resources:core,Language;
           /images/formatmap32x32.png?vk=4536"
           Image32by32Top="-96" Image32by32Left="-64"
           ToolTipDescription="Tooltip Description"
           TemplateAlias="o1"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
    </CommandUIExtension>
  </CustomAction>
        
  <CustomAction
   Id="MyScriptBlock"
   Location="ScriptLink"
   ScriptBlock="
ExecuteOrDelayUntilScriptLoaded(_registerMyScriptBlockPageComponent,
                                'sp.ribbon.js'),
        
function _registerMyScriptBlockPageComponent()
{
    Type.registerNamespace('MyScriptBlock'),
        
    MyScriptBlock.MyScriptBlockPageComponent =
function MyScriptBlockPageComponent_Ctr() {
        MyScriptBlock.MyScriptBlockPageComponent.initializeBase(this);
    };
        
    MyScriptBlock.MyScriptBlockPageComponent.prototype = {
        
        init: function MyScriptBlockPageComponent_init() {
        },
        
        _globalCommands: null,
        
        
        buildGlobalCommands:
function MyScriptBlockPageComponent_buildGlobalCommands() {
            if (SP.ScriptUtility.isNullOrUndefined(this._globalCommands)) {
                this._globalCommands = [];
                this._globalCommands[this._globalCommands.length] = 'DocumentTab';
                this._globalCommands[this._globalCommands.length]
                 = 'DocumentNewGroup';
                this._globalCommands[this._globalCommands.length]
                 = 'MyDocumentsNewButton';
            }
            return this._globalCommands;
        },
        
        getGlobalCommands: function MyScriptBlockPageComponent_getGlobalCommands()
        {
            return this.buildGlobalCommands();
        },
        
        canHandleCommand: function
MyScriptBlockPageComponent_canHandleCommand(commandId) {
            var items = SP.ListOperation.Selection.getSelectedItems();
            if (SP.ScriptUtility.isNullOrUndefined(items))
                return false;
            if (0 == items.length)
                return false;
            if (commandId === 'DocumentNewTab'){
                return true;
            }
            if (commandId === 'DocumentNewGroup'){
                return true;
            }
            if (commandId === 'MyDocumentsNewButton'){
                return true;
            }
            return false;
        },
        
        handleCommand: function
        MyScriptBlockPageComponent_handleCommand(commandId, properties, sequence) {
            alert('You hit my button!'),
            return true;
        }
    }
        
    MyScriptBlock.MyScriptBlockPageComponent.get_instance =
     function MyScriptBlockPageComponent_get_instance() {
        if (SP.ScriptUtility.isNullOrUndefined(MyScriptBlock.
          MyScriptBlockPageComponent._singletonPageComponent)) {
            MyScriptBlock.MyScriptBlockPageComponent._singletonPageComponent
             = new MyScriptBlock.MyScriptBlockPageComponent();
        }
        return MyScriptBlock.MyScriptBlockPageComponent._singletonPageComponent;
    }
    MyScriptBlock.MyScriptBlockPageComponent.registerWithPageManager
         = function MyScriptBlockPageComponent_registerWithPageManager() {
        SP.Ribbon.PageManager.get_instance().addPageComponent
            (MyScriptBlock.MyScriptBlockPageComponent.get_instance());
    }
    MyScriptBlock.MyScriptBlockPageComponent.unregisterWithPageManager =
    function MyScriptBlockPageComponent_unregisterWithPageManager() {
        if (false == SP.ScriptUtility.isNullOrUndefined(
         MyScriptBlock.MyScriptBlockPageComponent._singletonPageComponent)) {
            SP.Ribbon.PageManager.get_instance().removePageComponent(
         MyScriptBlock.MyScriptBlockPageComponent.get_instance());
        }
    }
        
    MyScriptBlock.MyScriptBlockPageComponent.registerClass(
    'MyScriptBlock.MyScriptBlockPageComponent', CUI.Page.PageComponent);
    MyScriptBlock.MyScriptBlockPageComponent.registerWithPageManager();
}">
  </CustomAction>

code snippetElements.xml

Adding Buttons with SPD

The easiest way to add a button to the Ribbon or your items is to use SharePoint Designer. Built right into SPD is the ability to add custom actions to your list. SPD can create these actions on the Ribbon forms, such as the display, edit, or new form for a list item, and also on a list item drop-down menu. You can customize the action performed by the button, for example, navigating to a form such as the edit form for the item, initiating a workflow, or launching a URL. In addition, you can use SPD to assign graphics to your icons, set your sequence number, and even set your Ribbon location in the same way you set the Location attribute in the Ribbon XML you saw earlier. Figure 5-19 shows the form used to tell SPD how to customize the Ribbon for your list.

Contextual Tabs and Groups with Web Parts

You may want to build a Ribbon user interface and have it automatically appear when a user selects a web part. This is the way the media-player web part works; it displays a new tab in the Ribbon when you select the web part in the user interface. To perform this functionality, you need to add a contextual tab and contextual group to the Ribbon interface through code. You do not use the declarative XML file, but instead place the XML in code and add it programmatically to the Ribbon.

To build a contextual web part, you create your web part as you normally do, but you want your web part to inherit from the IWebPartPageComponentProvider interface. You need to implement the WebPartContextualInfo method of the interface. This method tells the Ribbon which group and tab to activate when the web part is selected.

download.eps
public WebPartContextualInfo WebPartContextualInfo
        {
            get
            {
                WebPartContextualInfo info = new WebPartContextualInfo();
                info.ContextualGroups.Add(
                    new WebPartRibbonContextualGroup
                    {
                        Id = "Ribbon.MyContextualGroup",
                        VisibilityContext = "WebPartSelectionTest",
                        Command = "MyContextualGroupCMD"
                    }
                );
                info.Tabs.Add(
                    new WebPartRibbonTab
                    {
                        Id = "Ribbon.MyContextualGroup.MyTab",
                        VisibilityContext = "WebPartSelectionTest"
                    }
                );
        
                info.PageComponentId = SPRibbon.GetWebPartPageComponentId(this);
                return info;
            }
        }

code snippetCustomWebPart.cs

Then you need to implement a custom page component using JavaScript. This is similar to the code you saw earlier in the chapter where you added and registered the custom page component. The code that follows uses the executeOrDelayUntilScriptLoaded command, which is part of the SharePoint infrastructure, to load and run the script on-demand.

download.eps
private string DelayScript
        {
            get
            {
                string wppPcId = SPRibbon.GetWebPartPageComponentId(this);
                return @"
                <script type=""text/javascript"">
  //<![CDATA[
        
            function _addCustomPageComponent()
            {
                SP.Ribbon.PageManager.get_instance().addPageComponent(new
                CustomPageComponent.TestPageComponent(" + wppPcId + @"));
            }
        
            function _registerCustomPageComponent()
            {
                RegisterSod(""testpagecomponent.js"",
                ""/_layouts/TestPageComponent.js"");
                var isDefined = ""undefined"";
                try
                {
                    isDefined = typeof(CustomPageComponent.TestPageComponent);
                }
                catch(e)
                {
                }
                EnsureScript(""testpagecomponent.js"",isDefined,
_                    addCustomPageComponent);
            }
            ExecuteOrDelayUntilScriptLoaded(_registerCustomPageComponent,
                ""sp.ribbon.js"");
//]]>
</script>";
            }
        }

code snippetCustomWebPart.cs

Contextual tabs always exist on the Ribbon but are hidden. To add your tabs and groups to the Ribbon, you need to use the server-side Ribbon API to get the Ribbon and add your custom Ribbon elements, as shown in the following code.

download.eps
private void AddCustomTab()
        {
        
            Microsoft.Web.CommandUI.Ribbon ribbon = SPRibbon.GetCurrent(this.Page);
            XmlDocument xmlDoc = new XmlDocument();
        
            //Contextual Tab
            xmlDoc.LoadXml(this.CuiDefinitionCtxTab);
            ribbon.RegisterDataExtension(xmlDoc.FirstChild,
              "Ribbon.ContextualTabs._children");
            xmlDoc.LoadXml(this.CuiDefinitionScaling);
            ribbon.RegisterDataExtension(xmlDoc.FirstChild,
              "Ribbon.Templates._children");
            exists = true;
        
        }

code snippetCustomWebPart.cs

To tie it all together, you need to implement the OnPreRender method, which allows the code to add the new Ribbon elements to the page before the page renders. The following code calls the AddCustomTab method to do this; this method also registers the script block that implements the custom page component with the SharePoint client script manager:

protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
        
            //RegisterDataExtensions; add Ribbon XML for buttons
            this.AddCustomTab();
        
            ClientScriptManager csm = this.Page.ClientScript;
            csm.RegisterClientScriptBlock(this.GetType(),
               "custompagecomponent", this.DelayScript);
        }

The last piece to look at is the custom page component, which implements the functionality to tell SharePoint which commands the page component implements, as well as when to focus on the contextual tab, and when to yield focus depending on whether the web part is selected.

download.eps
Type.registerNamespace('CustomPageComponent'),
        
////////////////////////////////////////////////////////////////////////////////
// CustomPageComponent.TestPageComponent
var _myWpPcId;
CustomPageComponent.TestPageComponent = function
CustomPageComponent_TestPageComponent(webPartPcId) {
    this._myWpPcId = webPartPcId.innerText;
    CustomPageComponent.TestPageComponent.initializeBase(this);
}
CustomPageComponent.TestPageComponent.prototype = {
        
    init: function CustomPageComponent_TestPageComponent$init() {
    },
        
    getFocusedCommands: function
CustomPageComponent_TestPageComponent$getFocusedCommands() {
        return ['MyTabCMD', 'MyGroupCMD', 'CommandMyJscriptButton'];
    },
        
    getGlobalCommands: function
CustomPageComponent_TestPageComponent$getGlobalCommands() {
        return [];
    },
        
    isFocusable: function CustomPageComponent_TestPageComponent$isFocusable() {
        return true;
    },
        
    receiveFocus: function CustomPageComponent_TestPageComponent$receiveFocus() {
        return true;
    },
        
    yieldFocus: function CustomPageComponent_TestPageComponent$yieldFocus() {
        return true;
    },
        
    canHandleCommand: function
CustomPageComponent_TestPageComponent$canHandleCommand(commandId) {
        //Contextual Tab commands
        if ((commandId === 'MyTabCMD') || (commandId === 'MyGroupCMD') ||
            (commandId === 'CommandMyButton') ||
        (commandId === 'CommandMyJscriptButton')) {
            return true;
        }
    },
        
    handleCommand: function CustomPageComponent_TestPageComponent$handleCommand
      (commandId, properties, sequence) {
        
        if (commandId === 'CommandMyJscriptButton') {
            alert('Event: CommandMyJscriptButton'),
        }
    },
        
    getId: function CustomPageComponent_TestPageComponent$getId() {
        return this._myWpPcId;
    }
}
        
        
CustomPageComponent.TestPageComponent.registerClass(
'CustomPageComponent.TestPageComponent', CUI.Page.PageComponent);
if(typeof(NotifyScriptLoadedAndExecuteWaitingJobs)!="undefined")
NotifyScriptLoadedAndExecuteWaitingJobs("testpagecomponent.js");

code snippetCustomWebPart.cs

Status Bar and Notification Area

Two new additions to the user interface for 2010 are the status bar, which appears right below the Ribbon tab; and the notification area, which is transient in nature, that appears below and to the right of the Ribbon tab. You should use both to give your users contextual information so long as it’s not distracting. For example, if you are editing a page and have not checked it in or published it, the status bar tells you that only you can see the page. The status bar is for more permanent information, whereas the notification area is similar to instant message pop-ups or Windows system tray notifications because notifications pop up and then disappear after a certain amount of time. Notification area messages are inherently more transient in nature than status bar messages.

Customizing the Status Bar

The status bar is extensible through client- and server-side code. The message you can deliver in the bar is HTML, so it can be styled and contain links and images. In addition, the bar can have four preset colors, depending on the importance of the message. To work with the status bar, you use the SP.UI.Status class, which is a simple client-side API because it contains only six methods, and you can find their definitions in SP.debug.js. On the server side, you should use the SPPageStatusSetter class, which is part of the Microsoft.SharePoint.WebControls namespace. That API is even simpler because you call one method—addStatus. You can also use the SPPageStateControl class to work with the status bar on the server side. Table 5-6 outlines the methods for the client side.

Table 5-6: SP.UI.Status Methods

Name Description
addStatus Enables you to pass a title, the HTML payload, and a Boolean specifying whether to render the message at the beginning of the status bar. This function returns a status ID that you can use with other methods.
appendStatus Appends status to an existing status. You need to pass the status ID for the existing status being updated, and the title, and HTML you want appended.
updateStatus Updates an existing status message. You need to pass the status ID and the HTML payload for the new status message.
setStatusPriColor Enables you to set the priority color to give a user a visual indication of the status messages’ meanings, such as green for good or red for bad. You need to pass the status ID of the status you want to change color and one of four color choices: red, blue, green, or yellow.
removeStatus Removes the status specified by the status ID you pass to this method from the status bar.
removeAllStatus Removes all status messages from the status bar. You can pass a Boolean that specifies whether to hide the bar. Most times, you want this Boolean to be true.

Programming the status bar is straightforward. The sample code with this chapter includes a snippet that you can add to the HTML source for a Content Editor web part. When you do this, you can see what appears in Figure 5-20.

download.eps
<script type="text/javascript">
        
var sid;
var color="";
        
function AppendStatusMethod()
{
   SP.UI.Status.appendStatus(sid, "Appended:", "<HTML><i>My Status Append to "
        + sid + " using appendStatus</i></HTML>");
}
        
function UpdateStatus()
{
   SP.UI.Status.updateStatus(sid, "Updated: HTML updated for " + sid +
        " using updateStatus");
}
        
function RemoveStatus()
{
   SP.UI.Status.removeStatus(sid);
}
        
function RemoveAllStatus()
{
   SP.UI.Status.removeAllStatus(true);
}
        
        
function SetStatusColor()
{
   if (color=="")
   {
     color="red";
   }
   else if (color=="red")
   {
     color="green";
   }
   else if (color=="green")
   {
     color="yellow";
   }
   else if (color=="yellow")
   {
     color="blue";
   }
   else if (color=="blue")
   {
     color="red";
   }
        
   SP.UI.Status.setStatusPriColor(sid, color);
}
        
function AppendStatus()
{
   SP.UI.Status.addStatus("Appended:", "<HTML><i>
     My Status Message Append using atBeginning</i></HTML>", false);
}
        
        
function CreateStatus()
{
    return SP.UI.Status.addStatus(SP.Utilities.HttpUtility.htmlEncode(
      "My Status Bar Title"), "<HTML><i>My Status Message</i></HTML>", true);
}
        
}
</script>
<input onclick="sid=CreateStatus();alert(sid);" type="button" value="
   Create Status"/> <br/>
<input onclick="AppendStatus()" type="button"
   value=”Append Status using atBeginning”/> <br/>
<input onclick=”AppendStatusMethod()” type=”button” 
   value=”Append Status using appendStatus”/> <br/>
<input onclick=”UpdateStatus()” type=”button” 
   value=”Update Status using updateStatus”/> <br/>
<input onclick=”SetStatusColor()” type=”button” value=”Cycle Colors”/> <br/>
<input onclick=”RemoveStatus()” type=”button” value=”Remove Single Status”/> <br/>
<input onclick=”RemoveAllStatus()” type=”button” value=”Remove All Status”/> <br/>

code snippetCustomDialogandNotifications.txt

Customizing the Notification Area

Beyond working with status information, you can also customize the notification area on the upper-right side of the screen below the Ribbon. Given that SharePoint is now leveraging a lot of Ajax, there is a need to give users feedback that their pages and actions are completed. The notification area does this by telling the user that the page is loading or that a save was successful, for example, which used to be indicated by a postback and page refresh.

The notification API is limited in that you can create and remove only notifications. Table 5-7 describes the methods for SP.UI.Notify that work with notifications, with sample code following the table.

Table 5-7: SP.UI.Notify

Name Description
addNotification Enables you to pass your HTML payload, whether the notification is sticky (which when set to false means the notification disappears after 5 seconds), your ToolTip text, and the name of a function to handle the onclick event. The last two parameters are optional. This function returns an ID that you can use with the removeNotification method to remove your notification.
removeNotification Removes the notification specified by the notification ID that you pass to this method.
download.eps
<script type="text/javascript">
        
var notifyid;
function CreateNotification()
{
   notifyid = SP.UI.Notify.addNotification("My HTML Notification", true,
     "My Tooltip", "HelloWorld");
   alert("Notification id: " + notifyid);
}
        
function RemoveNotification()
{
   SP.UI.Notify.removeNotification(notifyid);
}
</script>
<input onclick="CreateNotification()" type="button"
    value="Create Notification"/><br/>
<input onclick="RemoveNotification()" type="button" value="Remove Notification"/>

code snippetCustomDialogandNotifications.txt

Working with Dialogs

Beyond working with notifications and status bars, SharePoint now also offers a dialog framework that you can write code to. The purpose of the new dialog framework is to keep the user in context and focus the user on the dialog rather than all the surrounding user interface elements. With the new dialog framework, dialogs are modal and gray out the screen, except for the dialog that is displayed. Figure 5-21 shows a custom dialog in SharePoint 2010.

The implementation of the dialog takes the form of your contents being loaded into an iframe in a floating div. The dialog is modal, so the user can’t get to other parts of SharePoint from it. Plus, the dialog can be dragged to other parts of the browser window and can be maximized to the size of the browser window.

If you look in SP.UI.Dialog.debug.js, you see the implementation for the dialog framework. The framework has a JavaScript API against which you can program to have SharePoint launch and load your own dialogs. You do this by calling the SP.UI.showModalDialog method and passing in the options you want for your dialog, such as height, width, page to load, and other options. You can see the full set of options in Table 5-8.

Table 5-8: Parameters for the SP.UI.showModalDialog method

Name Description
Width The width of the dialog box as an integer. If you don’t specify a width, SharePoint autosizes the dialog.
Height The height of the dialog box as an integer. If you don’t specify a height, SharePoint autosizes the dialog.
autoSize A Boolean that specifies whether to have SharePoint autosize the dialog.
X The x coordinate for your dialog.
Y The y coordinate for your dialog.
allowMaximize A Boolean that specifies whether to allow the Maximize button in your dialog.
showMaximized A Boolean that specifies whether to show your dialog maximized by default.
showClose A Boolean that specifies whether to show the Close button in the toolbar for the dialog.
url A URL for SharePoint to load as the contents for your dialog.
Html A DOMElement that contains the HTML you want SharePoint to load as the contents for your dialog. This DOMElement is destroyed after use, so make a copy before passing it to SharePoint if you need it after the dialog is destroyed.
Title The title of your dialog.
dialogReturnValueCallback The function SharePoint calls back to when the dialog is closed. You create a delegate to this function for this option with the createDelegate function in JavaScript.

Now that you know the options you can pass to the showModalDialog function, programming a dialog is straightforward. A couple of tips before you look at the code:

  • If you use URLs, take a look at the SP.Utilities.Utility namespace. This namespace has a number of utilities to help you find the right places from which to grab your URLs no matter where your code is running. For example, one utility you will see in the code is SP.Utilities.Utility.getLayoutsPageUrl('customdialog.htm'), which gets the URL to the _layouts folder so that the custom dialog HTML file can be retrieved.
  • Dialogs support the Source=url querystring variable like the rest of SharePoint. So, if you want to have SharePoint redirect to another page, you can specify the source along the query string, and SharePoint respects that.

The following code contains the OpenDialog function. As part of this function, a variable called options is created, which uses the SP.UI.$create_DialogOptions method. This method returns a DialogOptions object that you can utilize to specify your options. In the code, all the options are specified, including the creation of the delegate that points to the function—CloseCallback—that is called after the dialog is called. Then, the code calls the SP.UI.ModalDialog.showModalDialog with the options object that contains the specified options for the dialog.

The CloseCallback function gets the result and any return value. The result is the button the user clicked. SharePoint has an enumeration for the common buttons OK and Cancel that you can check against with the result value—for example, SP.UI.DialogResult.OK or SP.UI.DialogResult.cancel.

download.eps
function OpenDialog()
{
  var options = SP.UI.$create_DialogOptions();
        
  options.url = SP.Utilities.Utility.getLayoutsPageUrl('customdialog.htm'),
  options.url += "?Source=" + document.URL;
  alert('Navigating to dialog at: ' + options.url);
  options.width = 400;
  options.height = 300;
  options.title = "My Custom Dialog";
        
  options.dialogReturnValueCallback = Function.createDelegate(null, CloseCallback);
  SP.UI.ModalDialog.showModalDialog(options);
}
        
function CloseCallback(result, returnValue)
{
  alert('Result from dialog was: '+ result);
  if(result === SP.UI.DialogResult.OK)
  {
    alert('You clicked OK'),
  }
  else if (result == SP.UI.DialogResult.cancel)
  {
    alert('You clicked Cancel'),
  }
}

code snippetCustomDialogandNotifications.txt

Now that you have seen the code for calling the dialog and evaluating the result, look at what the HTML for the dialog body looks like. The code that follows is for SharePoint to load the dialog. The code contains two buttons for OK and Cancel. The onclick event handlers for the buttons use methods from the window.frameElement object. By using this object, you can get methods from the dialog framework. commitPopup returns OK, and cancelPopUp returns Cancel as the result of your dialog. Table 5-9 shows the methods you want to use from the frameElement.

download.eps
<p>
<img src="/_layouts/1033/images/DefaultPageLayout.tif" alt="Default Page"
    style="vertical-align: middle"/>
<B>Text for your dialog goes here</B>
</p>

<input type="button" name="OK" value="OK"
   onclick="window.frameElement.commitPopup();
   return false;" accesskey="O" class="ms-ButtonHeightWidth" target="_self" />

<input type="button" name="Cancel" value="Cancel"
   onclick="window.frameElement.cancelPopUp();
   return false;" accesskey="C" class="ms-ButtonHeightWidth" target="_self" />

code snippetCustomDialogandNotifications.txt

Table 5-9: Methods for frameElement for Dialogs

Name Description
commitPopup Returns OK as the result of your dialog
cancelPopUp Returns Cancel as a result of your dialog
navigateParent Navigates to the parent of the dialog

Calling SharePoint Web Services

SharePoint’s Web Services have been around for a number of releases of SharePoint and continue to work with SharePoint Online, with the caveat that you need to pass the correct cookies for authentication. Also, you want to make sure that when you add a reference to the SharePoint Online Web Services, such as lists.asmx, that you add them as a web reference and not a service reference. You can add a web reference by using the Advanced dialog as part of the Add Service Reference dialog. By using the older-style web references, you can access the cookie container, and add the authentication cookies to your calls to SharePoint Online. The following code shows how to add the cookie container and call a SharePoint web service. Whenever possible you should use the SharePoint Client Object Model rather than the legacy SharePoint Web Services. Please remember that the Client Object Model is a subset of the full functionality of the SharePoint API.

//Call Lists Web Service
listWS.Lists spList = new listWS.Lists();
spList.Url = "https://thomrizdom.sharepoint.com/_vti_bin/lists.asmx";
spList.CookieContainer = claimsHelper.CookieContainer;
XmlNode spLists = spList.GetList("Announcements");

Overview of Data Technologies

When it comes to SharePoint, working with, manipulating, and displaying data are the important tasks you do as a developer. If you break SharePoint down to its simplest form, it is just an application that sits on top of a database. Because SharePoint can reveal its data in so many ways—whether through the browser, inside Office, in applications running on the SharePoint server, or in applications running off the SharePoint server—there are a number of data technologies you can take advantage of when working with SharePoint. Which one you use depends on your comfort level with the technology required and also whether you write your application to run on or off the SharePoint server. Your choices in 2010 are:

  • LINQ
  • Server Object Model
  • Client Object Model
  • REST-style services

Of course, you can continue to use the Web Services APIs of SharePoint, but for the bulk of your operation you should look at moving to the Client OM or the SharePoint Data Services (an OData service) rather than using that technology. Table 5-10 goes shows the pros and cons of each data-access technology.

Table 5-10: Data Access Technologies

Name Pros Cons
LINQ to SharePoint Entity-based programmingStrongly typedSupports joins and projectionsGood tools support and IntelliSense Server-side onlyNew API, so new skills requiredPre-processing of list structure required, so changing list could break application
Server OM Familiar APIWorks with more than just list data Server-side onlyStrongly typed
Client OM Works off the serverEasier than Web Services APIWorks in Silverlight, JavaScript, and .NETMore than just list data New APIWeakly typed
SharePoint Data Service Standards-basedURL-based commandsStrongly typed Only works with lists and Excel
Web Services Simple to programStandards-based Weakly typedLegacy API

SharePoint LINQ Support

With SharePoint 2007, you had to use CAML queries to write queries against the server, using the SPQuery or SPSiteDataQuery objects. You would write your CAML as a string and pass it to those objects, so there were no strongly-typed objects or syntax checking as part of the API. Instead, you would either have to cross your fingers that you got the query right, or use a third-party tool to try to generate your CAML queries. To make this easier, SharePoint 2010 introduces SharePoint LINQ (SPLINQ). By having a LINQ provider, 2010 enables you to use LINQ to write your queries against SharePoint in a strongly typed way with IntelliSense and compile-time checking. Under the covers, the SharePoint LINQ provider translates your LINQ query into a CAML query and executes it against the server. As you will see, you can retrieve the CAML query that the LINQ provider generated to understand what is passed back to the server.

Getting Started with SharePoint LINQ: SPMetal

The first step in starting with SPLINQ is generating the entity classes and properties for your lists. Rather than writing these by hand, you can use the command-line tool that ships with SharePoint, called SPMetal. SPMetal parses your lists and generates the necessary classes that you can import into your Visual Studio projects. You can find SPMetal in the %ProgramFiles%Common FilesMicrosoft Sharedweb server extensions14BIN directory. You can run SPMetal from the command prompt, but if you prefer, you can write a batch file that you have Visual Studio run as part of your prebuild for your project so that the latest version of your entity classes are always included.

Using SPMetal is straightforward for performing common tasks that you want to do. It does support XML customization, but most of the time you don’t need to customize the default code generation. Table 5-11 shows the SPMetal command-line parameters that you can pass.

Table 5-11: SPMetal Command-Line Parameters

Name Description
Web Absolute URL of the website you want SPMetal to generate entity classes for.
Code The relative or absolute path and filename of the location where you want the outputted code to be placed.
Language The programming language you want for the generated code. The value for this can be either csharp or vb. SPMetal can look at your code parameter and infer the language you want by the extension of the filename you specify.
Namespace The namespace you want to use for the generated code. If you do not specify this property, SPMetal uses the default namespace of your VS project.
Useremoteapi SPMetal uses the client object model if you specify this parameter.
User Enables you to specify the DOMAINusername, such as /user:DOMAINusername, that SPMetal runs as.
Password The password that SPMetal uses to log on as the user specified in the /user parameter.
Serialization Specifies whether you want your objects to be serializable. By default, this parameter is none, so they are not. If you specify unidirectional, SPMetal puts in the appropriate markup to make the objects serializable.
Parameters Specifies the XML file used to override the parameters for your SPMetal settings. This is for advanced changes.

The following code snippet shows you some of the generated code, but to give you an idea of the work SPMetal does for you, the complete code for even a simple SharePoint site is more than 3,000 lines! You definitely want to use SPMetal to generate this code and tweak SPMetal to meet your requirements.

        /// <summary>
        /// Use the Announcements list to post messages on the home page of your 
        /// site.
        /// </summary>
        [Microsoft.SharePoint.Linq.ListAttribute(Name="Announcements")]
        public Microsoft.SharePoint.Linq.
         EntityList<AnnouncementsAnnouncement> Announcements {
               get {
                       return this.
                        GetList<AnnouncementsAnnouncement>("Announcements");
               }
        }
        
/// <summary>
/// Create a new news item, status or other short piece of information.
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Announcement", Id="0x0104")]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute
(Type=typeof(AnnouncementsAnnouncement))]
public partial class Announcement : Item {
        
       private string _body;
        
        private System.Nullable<System.DateTime> _expires;
        
       #region Extensibility Method Definitions
       partial void OnLoaded();
       partial void OnValidate();
       partial void OnCreated();
       #endregion
        
       public Announcement() {
               this.OnCreated();
        }
        
        [Microsoft.SharePoint.Linq.ColumnAttribute(Name="Body",
          Storage="_body", FieldType="Note")]
        public string Body {
               get {
                       return this._body;
               }
               set {
                       if ((value != this._body)) {
                              this.OnPropertyChanging("Body", this._body);
                              this._body = value;
                              this.OnPropertyChanged("Body");
                      }
               }
       }
        
        [Microsoft.SharePoint.Linq.ColumnAttribute(Name="Expires", 
           Storage="_expires",FieldType="DateTime")]
       public System.Nullable<System.DateTime> Expires {
               get {
                      return this._expires;
               }
               set {
                      if ((value != this._expires)) {
                              this.OnPropertyChanging("Expires", this._expires);
                              this._expires = value;
                              this.OnPropertyChanged("Expires");
                      }
               }
       }
}

What About Default Fields?

One thing you may realize is that SPMetal, by default, does not generate all the fields for your content types. So, you may find that fields such as Created or ModifiedBy do not appear in the types created by SPMetal. To add these fields, you can specify them in a Parameters.XML. The example that follows adds some new fields to the contact content type:

<?xml version="1.0" encoding="utf-8"?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal">
  <ContentType Name="Contact" >
    <Column Name="CreatedBy" />
    <Column Name="ModifiedBy"/>
  </ContentType>
  </Web>

Adding References in VS

After you generate SPMetal code imported into VS, it’s time to make sure you have the right references set up to use that code. You want to add two references at a minimum:

  • A reference to Microsoft.SharePoint, which is the general SharePoint namespace.
  • A reference to the specific SharePoint LINQ assembly using Microsoft.SharePoint.Linq.

This adds all the necessary dependent LINQ assemblies to your project.

Working with the DataContext Object

The DataContext object provides the heart of your LINQ programming. Your DataContext object is named after whatever you named the beginning of your generated file from SPMetal. For example, if you had SPMetal create a LINQDemo.cs file for your generated code, your DataContext object becomes LINQDemoDataContext, but you can override this in your parameter.xml file. To create your DataContext object, you can pass along the URL of the SharePoint site to which you want to connect.

When you have your DataContext object, you can start working with that object’s methods and properties. The DataContext contains all your lists and libraries as EntityList properties. You can retrieve these lists and libraries and then work with them. Table 5-12 lists the other methods and properties you can use from the DataContext object.

Table 5-12: Common Methods and Properties of the DataContext Object

Name Description
GetList<T> Returns the list of the specified type, for example,
GetList<AnnouncementsItems>(“Announcements”)
.
Refresh Refreshes the datasource.
RegisterList Enables you to register a list by registering a new name and new URL (if needed) as a replacement for the old name. This is helpful if a list has been renamed or moved and you do not want to rewrite your code.
ChangeConflicts Returns a ChangeConflictCollection, which is a list of conflicts from your transactions.
DeferredLoadingEnabled A Boolean that gets or sets whether LINQ should defer loading your objects until they are needed.
Log Gets the CAML query generated by LINQ. This is a good way to view what LINQ is generating on your behalf.
ObjectTrackingEnabled Gets or sets whether changes to objects are tracked. If you are just querying your site, for performance reasons, you should set this to false.
Web Gets the full URL of the SharePoint website the DataContext object is connected to.

Typed Data Classes and Relationships

As part of SPMetal, you get autogenerated typed data classes and relationships using the Association attribute. This enables you to use strongly typed objects for your lists and also to do queries across multiple lists that are related by lookup fields. This makes programming much cleaner and also enables you to catch compile-time errors when working with your objects, rather than runtime errors.

Querying and Enumerating Data and Inefficient Queries

To query and enumerate your data, you need to write LINQ queries. When you write your queries, you need to understand that LINQ translates the query into CAML, so if you try to perform LINQ queries that cannot be translated into CAML, SharePoint throws an error. SharePoint considers these inefficient queries, and the only way to work around them is to use LINQ to Objects and perform the work yourself. The following is a list of the unsupported operators that cause errors in SharePoint:

  • Aggregate
  • All
  • Any
  • Average
  • Distinct
  • ElementAt
  • ElementAtOrDefault
  • Except
  • Intersect
  • Join (in complex instances)
  • Max
  • Min
  • Reverse
  • SequenceEqual
  • Skip
  • SkipWhile
  • Sum

The simplest query you can write is a select from your list. The following code performs a select from a list and then enumerates the results:

            var context = new LinqDemoDataContext("http://intranet.contoso.com");
            var orderresults = from orders in context.Orders
                               select orders;
        
            foreach (var order in orderresults)
            {
                MessageBox.Show(order.Title + " " + order.Customer);
            }

From the following in the code, you can see that you need to:

  • Get your DataContext object.
  • Define your LINQ query as you do against any other datasource.
  • Enumerate the results, once you have them, using a foreach loop.
  • Use the ToList method to return a generic list that you can perform LINQ to Object operations on, if you want to.
  • Add where clauses to your queries to perform selection. For example, if in the previous query you wanted to select only orders that were more than $1,000 dollars, you would change the query to the following one:
    var orderresults = from orders in context.Orders
                                   where orders.Total > 1000
                                   select orders;

For the next example, the query performs a simple inner join between two lists that share a lookup field. Because CAML now supports joins, this is supported in LINQ as well. In the code that follows, you’ll notice:

  • You get the EntityList objects for the two lists that you will join, so you can use them in the query.
  • In the query, you just use the join operator to join the two lists together on a lookup field.
  • The code then uses the ToList method on the query results so that you can get back a LINQ to Object collection that you can iterate over.
                var context = new LinqDemoDataContext("http://intranet.contoso.com");
            
            
                EntityList<OrdersItem> Orders = context.GetList<OrdersItem>("Orders");
                EntityList<CustomersItem> Customers = context.GetList<CustomersItem>
                 ("Customers");
            
                var QueryResults = from Order in Orders
                                   join Customer in Customers on Order.Customer.Id
                                   equals Customer.Id
                                   select new { CustomerName = Customer.Title,
                                   Order.Title, Order.Product };
            
                var Results = QueryResults.ToList();
                if (Results.Count > 0)
                {
            
                    Results.ForEach(result => MessageBox.Show(result.Title + " " +
                     result.CustomerName + " " + result.Product));
                }
                else
                {
                    MessageBox.Show("No results");
                }
            
                    }

Adding, Updating, and Deleting Data and Dealing with Conflicts

LINQ enables you to add, delete, and update your data in SharePoint and even send items to the recycle bin. Because LINQ is strongly typed, you can just create new objects that map to the type of the new objects you want to add. After the new object is created, you can call the InsertOnSubmit method and pass the new object or the InsertAllOnSubmit method and pass a collection of new objects. Because LINQ works asynchronously from the server with a local cache, you need to call SubmitChanges after all modifications to data through LINQ to actually have the changes propagated back to SharePoint.

Updating items involves updating the properties on your objects and then calling SubmitChanges. For deleting, you need to call DeleteOnSubmit or DeleteAllOnSubmit, passing either the object or a collection of objects, and then call SubmitChanges. Recycling works much the same as deleting.

Because LINQ does not directly work against the SharePoint store, changes could be made to the backend while your code is running. To handle this, SharePoint LINQ provides the ability to catch exceptions if duplicates are present, if conflicts are detected, or if general exceptions occur. The code that follows shows how to code for all these cases.

One thing to note is that the code uses the ChangeConflict exception and then enumerates all the ObjectChangeConflict objects. Then, it looks through the MemberConflict objects, which contain the differences between the database fields and the LINQ object fields. After you decide what to do about the discrepancies, you can resolve the changes with the ResolveAll method. The ResolveAll method takes a RefreshMode enumeration, which can contain one of three values:

  • KeepChanges: Keeps the new values since retrieval, even if they are different than the database values, and keeps other values the same as the database.
  • KeepCurrentValues: Keeps the new values since retrieval, even if they are different from the database values, and keeps other values the same as they were retrieved, even if they conflict with the database values.
  • OverwriteCurrentValues: Keeps the values from the database and discards any changes since retrieval.
note.eps
You need to call SubmitChanges after resolving any conflicts to save changes.
try
            {
                EntityList<OrdersItem> Orders =
                  context.GetList<OrdersItem>("Orders");
                OrdersItem order = new OrdersItem();
                order.Title = "My LINQ new Order";
                order.Product = "Chai";
        
                //Add a lookup to Customers
                EntityList<CustomersItem> Customers =
                  context.GetList<CustomersItem>("Customers");
                var CustomerTempItem = from Customer in Customers
                               where Customer.Title == "Contoso"
                               select Customer;
        
                CustomersItem CustomerItem = null;
                foreach (var Cust in CustomerTempItem)
                    CustomerItem = Cust;
        
                order.Customer = CustomerItem;
        
                Orders.InsertOnSubmit(order);
                context.SubmitChanges();
        
                //Delete the item
                Orders.DeleteOnSubmit(order);
                context.SubmitChanges();
        
            }
            catch (ChangeConflictException conflictException)
            {
                MessageBox.Show("A conflict occurred: " +
                  conflictException.Message);
                foreach (ObjectChangeConflict Items in context.ChangeConflicts)
                {
                    foreach (MemberChangeConflict Fields in Items.MemberConflicts)
                    {
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine("Item Name: " + Fields.Member.Name);
                        sb.AppendLine("Original Value: " + Fields.OriginalValue);
                        sb.AppendLine("Database Value: " + Fields.DatabaseValue);
                        sb.AppendLine("Current Value: " + Fields.CurrentValue);
                        MessageBox.Show(sb.ToString());
                    }
                }
        
                //Force all changes
                context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
                context.SubmitChanges();
        
            }
            catch (SPDuplicateValuesFoundException duplicateException)
            {
                MessageBox.Show("Duplicate value found: " +
                 duplicateException.Message);
            }
            catch (SPException SharePointException)
            {
                MessageBox.Show("SharePoint Exception: " +
                  SharePointException.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception: " + ex.Message);
            }
        
        }

Inspecting the CAML Query

If you want to inspect the CAML query that LINQ is generating, you can use the Log property and set that to a TextWriter or an object that derives from the TextWriter object, such as a StreamWriter object. If you are writing a console application, the easiest way to set the Log property is to set it to the Console.Out property. From there, you can retrieve the CAML query that SharePoint LINQ executes on your behalf. The following code shows how to write a log file for your LINQ query using the Log property and then shows the CAML query generated by LINQ:

var context = new LinqDemoDataContext("http://intranet.contoso.com");
        
            context.Log = new StreamWriter(File.Open("C:\SPLINQLog.txt",
              FileMode.Create));
        
        
        
            EntityList<OrdersItem> Orders = context.GetList<OrdersItem>("Orders");
            EntityList<CustomersItem> Customers =
              context.GetList<CustomersItem>("Customers");
        
            var QueryResults = from Order in Orders
                               join Customer in Customers on Order.Customer.Id
                               equals Customer.Id
                               select new { CustomerName = Customer.Title,
                               Order.Title, Order.Product };
        
            context.Log.WriteLine("Results :");
        
            var Results = QueryResults.ToList();
            if (Results.Count > 0)
            {
        
                Results.ForEach(result => context.Log.WriteLine(result.Title
                  + " " + result.CustomerName + " " + result.Product));
            }
            else
            {
                context.Log.WriteLine("No results");
            }
        
            context.Log.Close();
            context.Log = null;

Please note that the log will only work for queries and not for inserts, updates, or deletes. The output for the previous code is as follows:

<View><Query><Where><And><BeginsWith><FieldRef Name="ContentTypeId" /><Value
Type="ContentTypeId">0x0100</Value></BeginsWith><BeginsWith><FieldRef
Name="CustomerContentTypeId" /><Value
Type="Lookup">0x0100</Value></BeginsWith></And></Where><OrderBy
Override="TRUE" /></Query><ViewFields><FieldRef Name="CustomerTitle"
 /><FieldRef Name="Title" /><FieldRef Name="Product"
/></ViewFields><ProjectedFields><Field Name="CustomerTitle" Type="Lookup"
List="Customer" ShowField="Title" /><Field Name="CustomerContentTypeId"
Type="Lookup" List="Customer" ShowField="ContentTypeId"
/></ProjectedFields><Joins><Join Type="INNER" ListAlias="Customer"><!~DHList
Name: Customers~DH><Eq><FieldRef Name="Customer" RefType="ID" /><FieldRef
List="Customer" Name="ID" /></Eq></Join></Joins><RowLimit
Paged="TRUE">2147483647</RowLimit></View>

Best Practice: Turning Off Object Change Tracking

One best practice is to turn off object tracking if you just query the list and do not plan to add, delete, or update items in the list. This makes your queries perform better because LINQ doesn’t have the overhead of trying to track changes to the SharePoint objects. The way to turn off object tracking is to set the ObjectTrackingEnabled property to false on your DataContext object.

If you do need to make changes to the list, you can open another DataContext object to the same list with object change tracking enabled. LINQ allows two DataContext objects to point at the same website and list, so you can have one DataContext object for querying and another for writing to the list.

When to Use CAML and LINQ
You’ll still have times when you should revert to using CAML directly. One scenario is when performance is paramount. LINQ makes CAML programming much easier, but no matter how LINQ is optimized, it adds some overhead to your code. Another example is if you have large amounts of adds, deletes, or updates that you need to perform. CAML provides better performance in this scenario.
Tools: LINQPad
If you do not want to write your LINQ queries by hand, give the tool LINQPad a try. It makes writing your queries easier by giving you a graphical designer. You can find it at http://www.linqpad.net.

Managed Client OM

With SharePoint 2007, if you wanted to program on the client side, in reality you had only one API choice—the ASMX Web Services API. Although functional, the Web Services API was not the easiest API to program against, and although it was easy to program from managed code, programming against it from JavaScript or Silverlight was difficult at best. With the growth of client-side technologies, such as .NET CLR–based clients (for example, Windows Presentation Framework [WPF] or Silverlight); new technologies for programming in JavaScript, such as JSON; and the introduction of REST, moving from the Web Services API to a richer API was sorely needed in SharePoint. Welcome the managed client object model, which this chapter refers to as the Client Object Model (Client OM).

The Client OM is actually two object models:

  • One works with .NET-based clients, such as Windows Forms, WPF, or Silverlight because these clients can handle the results in .NET objects
  • The other works with ECMAScript/JavaScript clients to get back the JSON response.

Figure 5-22 shows the way the Client OM works.

One principle of the Client OM is to minimize network chatter. So Fiddler is a key tool for helping you troubleshoot any issues because the Client OM batches its commands and sends them all at once to the server at your request. This minimizes the round-trips and network bandwidth used by the object model and makes your application perform better. In addition, you should write asynchronous code with callbacks so that your user interface doesn’t block when users perform actions.

In terms of API support, the Client OM supports a subset of the server OM, so access to lists, libraries, views, content types, web parts, and users/groups is part of the OM. However, the OM does not provide coverage of all features, such as the taxonomy store or BI data. Figure 5-23 shows the major objects in the Client OM.

There is also a difference in the namespaces provided by the .NET and ECMAScript OMs. Because you extend the Ribbon using script, the ECMAScript OM has a Ribbon namespace, while the managed Client OM does not. Plus, there is a difference in naming conventions for the foundational part of the namespaces. For example, if you wanted to access a site, in the .NET API you would use the Microsoft.SharePoint.Client.Site object, but in ECMAScript you would use SP.Site. Table 5-13 shows the different namespaces for the two Client OMs.

Table 5-13: Supported Namespaces in Client OMs

.NET Managed ECMAScript
Microsoft.SharePoint.Client.Application N/A
N/A SP.Application.UI
N/A SP.Ribbon
N/A SP.Ribbon.PageState
N/A SP.Ribbon.TenantAdmin
N/A SP.UI
N/A SP.UI.ApplicationPages
N/A SP.UI.ApplicationPages.Calendar
Microosft.SharePoint.Client.Utilities SP.Utilities
Microsoft.SharePoint.Client.WebParts SP.WebParts
Microsoft.SharePoint.Client.Workflow SP.Workflow

To convert your understanding of server objects to the client, Table 5-14 shows how server objects would be named in the Client OMs.

Table 5-14: Equivalent Objects in Server and Client OMs

table5-14

Which DLLs Implement the Client OM?

Before you dive into writing code with the Client OM and adding references in VS, you first need to understand where these DLLs are located and what some of the advantages of the DLLs, especially size. As with other SharePoint .NET DLLs, you can find the .NET DLLs for the Client OM located under %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14ISAPI. There are two DLLs for the managed OM, Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime. If you look at these DLLs in terms of size, combined they are under 1MB. Compare that with Microsoft.SharePoint, which weighs in at more than a hefty 15 MB.

Because the ECMAScript implementation is different from the .NET one and needs to live closer to the web-based code for SharePoint, this DLL is located in %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATELAYOUTS. There, you can find four relevant JS files: SP.js, SP.Core.js, SP.Ribbon.js, and SP.Runtime.js. Of course, when you debug your code, you use the debug versions of these files, such as SP.debug.js, because the main versions are crunched to save on size and bandwidth. Also, you can set your SharePoint deployment to use the debug versions of these files automatically by changing the web.config file for your deployment located at %inetpub%wwwrootwssVirtualDirectories80 and adding to the system.web section the following line <deployment retail=“false” />. Again, these files are less than 1 MB.

Lastly, Silverlight is a little bit different in that it has its own specific implementation of the Client OM for Silverlight. You can find the Silverlight DLLs at %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATELAYOUTSClientBin. You can find two files, Microsoft.SharePoint.Client.Silverlight and Microsoft.SharePoint.Client.Silverlight.Runtime. Combined, the files also come under 1 MB in size.

Microsoft has redistributable versions of the .NET and Silverlight object models to install on your client machines. If you have Office 2010 installed on your machine, you don’t need the redistributable version, but if you don’t, you can retrieve the 32-bit and 64-bit versions of the redistributable object model at www.microsoft.com/downloads/en/details.aspx?FamilyID=b4579045-b183-4ed4-bf61-dc2f0deabe47. Given the long URL, it is probably easier to search for SharePoint Client Object Model redistributable.

Adding References Inside VS

Depending on the type of application you write, the way you reference the different Client OMs varies. With WPF or WinForms, you use the VS Add Reference user interface to add a reference to the DLLs discussed earlier. From there, you can use the proper statements to leverage the namespaces in your code. The same process is true for Silverlight. Figure 5-24 shows how to add a reference inside of Visual Studio for a regular managed code project.

When it comes to referencing the ECMAScript OM, you use the ScriptLink control, which is part of the Microsoft.SharePoint.WebControls namespace. The following code snippet shows you how to do this:

<SharePoint:ScriptLink ID="ScriptLinkSPDebug" Name="sp.debug.js"
LoadAfterUI="true" Localizable="false" runat="server" />

Authentication

Before you write your first line of code, you need to understand the context that your code will run in. With the Client OM, by default, your code runs in the context of the currently logged-on user. Because many web applications support forms-based authentication, the Client OM supports this as well. You must provide the username and password for the Client OM to use and also set the authentication mode to forms-based authentication on your ClientContext object. The following code shows you how to set the Client OM to use forms-based authentication and set the correct properties to send a username and password:

clientContext.AuthenticationMode = ClientAuthenticationMode.FormsAuthentication;
FormsAuthenticationLoginInfo formsAuthInfo = new
FormsAuthenticationLoginInfo("User", "Password");
clientContext.FormsAuthenticationLoginInfo = formsAuthInfo;

Remember that SharePoint Online requires that you pass the custom authentication cookies with your Client OM requests so that you cannot use the forms authentication in the previous code to log on to SharePoint Online. Instead, you must use the methods described earlier in this chapter.

ClientContext Object

At the heart of all your code is the ClientContext object. This is the object that you instantiate first to tell SharePoint what site you want to connect to in order to perform your operations. With the .NET API, you must pass an absolute URL to the client context to open your site, but with the ECMAScript API, you can pass a relative or blank URL in your constructor and SharePoint finds the relative site or uses the current site as the site you want to open.

One quick note on ClientContext is that in managed code, it inherits from IDisposable, which you can tell by looking at the implementation. This means that you must properly dispose of your ClientContext objects by wrapping your code with using statements or by calling Dispose explicitly. If you don’t dispose correctly, you may run into memory leaks and issues. One tool you should use to make sure you dispose of your objects correctly is SPDisposeCheck, which you can find at http://archive.msdn.microsoft.com/SPDisposeCheck.

When working with the constructor for the ClientContext, you can pass in either a string that is the URL to your site or a URI object that contains the URL to your site.

Table 5-15 shows the important methods and properties for the ClientContext class.

Table 5-15: Methods and Properties for the ClientContext Class

Name Description
Dispose Call this method to dispose of your object after you finish using it.
ExecuteQuery After loading all the operations for your site, such as queries, call this method to send the commands to the server.
executeQueryAsync Available in the ECMAScript object model, this enables you to call a query and pass two delegates to call back to. One is for when the query succeeds, and the other is used when the query fails.
Load Enables you to load your query using the method syntax of LINQ and fills the object you pass. You can also pass an object without a query to return just the object, such as Site.
LoadQuery Use this to return a collection of objects as an IQueryable collection. This supports both the method and query syntax for LINQ.
AuthenticationMode Gets or sets the authentication mode for your object. The values can be Default, FormsAuthentication, or Anonymous. FormsAuthentication is not applicable for SharePoint Online development.
FormsAuthenticationLoginInfo Use this property to set the username and password for your forms authentication to authenticate against your site. This property is not applicable for SharePoint Online development.
RequestTimeout Gets or sets the timeout for your requests.
Site Gets the site collection associated with the ClientContext.
URL Gets the URL of the site that the ClientContext is associated with.
Web Gets the website that the ClientContext is associated with.

As demonstrated in the sample code throughout this section, you use the Load or LoadQuery method on the ClientContext object and then call the ExecuteQuery or executeQueryAsync method to execute your query. The rest of this section goes through the different programming tasks you perform with the Client OM to show you how to use it.

Retrieving Items from SharePoint

To retrieve your list items from SharePoint, you use the Load method to load the object into the Client OM. For example, if you want to load a Web object into the Client OM and then access the properties from it, you would use the following code:

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
       "http://intranet.contoso.com");
            Web site = context.Web;
            context.Load(site);
            context.ExecuteQuery();
            MessageBox.Show("Title: " + site.Title + " Relative URL: " +
             site.ServerRelativeUrl);
            context.Dispose();

If you try to use any of the other objects below the requested site, you get an error message saying that the collection is not initialized. For example, if you try to retrieve the lists in the site, an error occurs. With the Client OM, you need to be explicit about what you want to load. The following modified sample shows you how to load the list collection and then iterate over the objects in the collection:

//Load the List Collection
                ListCollection lists = context.Web.Lists;
                context.Load(lists);
                context.ExecuteQuery();
        
                MessageBox.Show(lists.Count.ToString());
        
                foreach (Microsoft.SharePoint.Client.List list in lists)
                {
                    MessageBox.Show("List: " + list.Title);
                }

Properties Returned and Requesting Properties

By default, SharePoint returns a large set of properties and hydrates your objects with these properties. For performance reasons, you may not want to have it do that if you are using only a subset of the properties. Plus, certain properties are not returned by default, such as permission properties for your objects. As a best practice, you should request the properties that you need rather than let SharePoint retrieve all properties for you. This is similar to the best practice of not doing a SELECT * in SQL Server.

The way to request properties is in your load method. As part of this method, you need to request the properties you want to use in your LINQ code. The following example changes the previous site request code to retrieve only the Title and ServerRelativeURL properties, and for the lists only the Title property, because that is all that’s used in the code:

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  "http://intranet.contoso.com");
        
                Web site = context.Web;
                context.Load(site, s => s.Title, s => s.ServerRelativeUrl);
                ListCollection lists = site.Lists;
                context.Load(lists, ls => ls.Include(l => l.Title));
        
                context.ExecuteQuery();
        
        
        
                MessageBox.Show("Title: " + site.Title + " Relative URL: " +
                 site.ServerRelativeUrl);
        
        
                MessageBox.Show(lists.Count.ToString());
        
                foreach (Microsoft.SharePoint.Client.List list in lists)
                {
                    MessageBox.Show("List: " + list.Title);
                }
        
        
                context.Dispose();

Load Versus LoadQuery

You may wonder what the difference is between Load and LoadQuery.

  • Load hydrates the objects in-context, so if you pass a Web object to your Load method, SharePoint fills in that object with the properties of your SharePoint web. With the Load method, the objects are tied to the client context, so they are destroyed and are eligible for garbage collection only when the client context is destroyed.
  • LoadQuery does not fill in the objects in-context, so it returns an entirely new collection. This method is more complex, but it’s also more flexible. In certain cases, it enables the server to be more effective in processing your queries. Plus, you can query the same object collection multiple times and have different result sets for each query. For example, you can have one query that returns all lists with a certain title, whereas another collection returns lists with a certain number of items. You can also destroy these objects out of context.

The LoadQuery method is similar to the Load method, except that it returns a new collection. The other key difference is that the properties for objects off the client context are not populated with LoadQuery after your LoadQuery call. You need to call Load method to populate these. The following code shows you a good example of this:

    ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
      "http://intranet.contoso.com");
        
                Web site = context.Web;
                ListCollection lists = site.Lists;
        
        
                IEnumerable<List> newLists = context.LoadQuery(lists.Include(
                 list => list.Title));
                context.ExecuteQuery();
        
                foreach (List list in newLists)
                {
                    MessageBox.Show("Title: " + list.Title);
                }
        
                //This will error out because lists is not populated
                MessageBox.Show(lists.Count.ToString());
        
                context.Dispose();

Nesting Includes in Your LoadQuery

In your LoadQuery calls, you can nest Include statements so that you can load fields from multiple objects in the hierarchy without making multiple calls to the server. The following code shows how to do this:

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
   "http://intranet.contoso.com");
        
                Web site = context.Web;
                ListCollection lists = site.Lists;
        
        
                IEnumerable<List> newLists = context.LoadQuery(lists.Include(
                  list => list.Title, list => list.Fields.Include(Field 
                  => Field.Title)));
                context.ExecuteQuery();
        
                foreach (List list in newLists)
                {
                    MessageBox.Show(" List Title: " + list.Title);
                    foreach (Field field in list.Fields)
                    {
                        MessageBox.Show("Field Title: " + field.Title);
                    }
                }
        
                context.Dispose();

Using CAML to Query Lists

In the Client OM, you can use CAML to query the server as part of the GetItems method. As you see in the code that follows, you create a new CamlQuery object and pass the CAML query that you want to perform into the the ViewXml property. From there, you call the GetItems on your ListCollection object and pass in your CamlQuery object. You still need to call the Load and ExecuteQuery methods to have the client object model perform your query. Also, CAML does support row limits, so you can also pass a <RowLimit> element in your CAML query and page over your results. In the OM, on the ListItemCollection object, there is a property ListItemCollectionPosition. You need to set your CAMLQuery object’s ListItemCollectionPosition to your own ListItemCollectionPosition object to keep track of your paging, and then you can position your query starting point before querying the list and iterate through the pages until there are no pages of content left, as shown here:

            ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
             "http://intranet.contoso.com");
        
        
            List list = context.Web.Lists.GetByTitle("Announcements");
        
            ListItemCollectionPosition itemPosition = null;
            while (true)
            {
                CamlQuery camlQuery = new CamlQuery();
                camlQuery.ListItemCollectionPosition = itemPosition;
                camlQuery.ViewXml = @"
                    <View>
                        <Query>
                            <Where>
                                <IsNotNull>
                                    <FieldRef Name='Title' />
                                </IsNotNull>
                            </Where>
                        </Query>
                        <RowLimit>1000</RowLimit>
                    </View>";
                ListItemCollection listItems = list.GetItems(camlQuery);
                context.Load(listItems);
                context.ExecuteQuery();
        
                itemPosition = listItems.ListItemCollectionPosition;
        
                foreach (ListItem listItem in listItems.ToList())
                {
                    MessageBox.Show("Title: " + listItem["Title"]);
                }
        
        
                if (itemPosition == null)
                {
                    break;
                }
        
                MessageBox.Show("Position: " + itemPosition.PagingInfo);
        
            }

Using LINQ with Queries

If you don’t want to use CAML to query your lists, you can use LINQ. To do this:

1. Create your query and put it in a variable.
2. Use the LoadQuery method you pass your LINQ query.
3. Call the ExecuteQuery method to execute your query and iterate through the results.
ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  "http://intranet.contoso.com");
        
            var query = from list
                        in context.Web.Lists
                        where list.Title != null
                        select list;
        
            var result = context.LoadQuery(query);
            context.ExecuteQuery();
        
            foreach (List list in result)
            {
                MessageBox.Show("Title: " + list.Title);
            }
        
            context.Dispose();

Creating Lists, Fields, and Items

Using the Client OM, you can create lists and items. To do this, you need to use the ListCreationInformation object and set the properties, such as the title and the type, for your list. Your ListCollection object has an Add method that you can call and pass your ListCreationInformation object to in order to create your list.

To create a field, follow these steps:

1. Use the Fields collection for your list and define the XML in the AddFieldAsXml property. This property takes your XML, a Boolean value that specifies whether to add the field to the default view, and AddFieldOptions, such as adding the field to the default content type.
2. Create list items by creating a ListItemCreationInformation object, and passing it to the AddItem method, which returns a ListItem object representing your new item. Using this object, you can set the properties for your item. Make sure to call the Update method when you finish modifying your properties.
3. Make sure to call the ExecuteQuery method to have the Client OM send your changes back to the server.
ClientContext context = new Microsoft.SharePoint.Client.ClientContext
  ("http://intranet.contoso.com");
            Web site = context.Web;
        
            ListCreationInformation listCreationInfo = new
  ListCreationInformation();
        
            listCreationInfo.Title = "New List";
            listCreationInfo.TemplateType = (int)ListTemplateType.GenericList;
            List list = site.Lists.Add(listCreationInfo);
        
            Field newField = list.Fields.AddFieldAsXml(@"
                 <Field Type='Text'
                        DisplayName='NewTextField'>
                 </Field>", true, AddFieldOptions.AddToDefaultContentType);
        
            ListItemCreationInformation itemCreationinfo = new
  ListItemCreationInformation();
            ListItem item = list.AddItem(itemCreationinfo);
            item["Title"] = "My New Item";
            item["NewTextField"] = "My Text";
            item.Update();
        
        
        
            context.ExecuteQuery();
        
            context.Dispose();

Deleting Lists and Items

To delete lists and items, you can use the DeleteObject method. One caveat to remember is that when you delete items from a collection, you should materialize your collection into a List<T> object, using the ToList method, so you can iterate through the list and delete without errors.

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  "http://intranet.contoso.com");
        
        
            List list = context.Web.Lists.GetByTitle("New List");
        
            CamlQuery camlQuery = new CamlQuery();
        
            camlQuery.ViewXml = @"
                <View>
                    <Query>
                        <Where>
                            <IsNotNull>
                                <FieldRef Name='Title' />
                            </IsNotNull>
                        </Where>
                    </Query>
                </View>";
            ListItemCollection listItems = list.GetItems(camlQuery);
        
            context.Load(listItems, items => items.Include(item => item["Title"]));
            context.ExecuteQuery();
            foreach (ListItem listItem in listItems.ToList())
            {
                listItem.DeleteObject();
            }
        
            context.ExecuteQuery();
            context.Dispose();

Working with Users and Groups

Another feature of the Client OM, beyond enabling you to work with lists, libraries, and items, is that it gives you the ability to work with users and groups. The Client OM includes the GroupCollection, Group, UserCollection, and User objects to make working with users and groups easier. Just as you iterate on lists and items, you can iterate on users and groups using these collections. The Client OM also has access to built-in groups such as the Owners, Members, and Visitors groups. You can access these from your context object using the AssociatedOwnerGroup, AssociatedMemberGroup, and AssociatedVisitorGroup properties, which return a Group object. Remember to hydrate these objects before trying to access properties or User collections on the objects.

To add a user to a group:

1. Use the UserCreationInformation object and set the properties, such as Title, LoginName, and others, on that object.
2. Call the Add method on your UserCollection object to add the user and ExecuteQuery to submit the changes.

Because this is similar to the steps used to create items, the sample code that follows shows you how to query users and groups but not create users:

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  "http://intranet.contoso.com");
        
            GroupCollection groupCollection = context.Web.SiteGroups;
        
            context.Load(groupCollection,
                groups => groups.Include(
                    group => group.Users));
        
            context.ExecuteQuery();
        
            foreach (Group group in groupCollection)
            {
        
                UserCollection userCollection = group.Users;
        
                foreach (User user in userCollection)
                {
                    MessageBox.Show("User Name: " + user.Title + " Email: " +
                     user.Email + " Login: " + user.LoginName);
        
                }
            }
            //Iterate the owners group
            Group ownerGroup = context.Web.AssociatedOwnerGroup;
        
            context.Load(ownerGroup);
            context.Load(ownerGroup.Users);
            context.ExecuteQuery();
            foreach (User ownerUser in ownerGroup.Users)
            {
                MessageBox.Show("User Name: " + ownerUser.Title + " Email: " +
                  ownerUser.Email + " Login: " + ownerUser.LoginName);
            }
        
            context.Dispose();

Working Asynchronously

All the code shown so far is synchronous code running in a .NET client, such as a WPF, console, or Windows Forms application. You may not want to write synchronous code, even in your .NET clients, so your application can be more responsive to your users, rather than having them wait for operations to complete before continuing to use your application. ECMAScript and Silverlight are asynchronous by default, so you can program them separately, but for .NET clients, you need to do a little bit of work to make your code asynchronous. The main change is that you need to use the BeginInvoke method to execute your code and pass a delegate to that method, which .NET calls when your code is done executing asynchronously. Then, you can do other work while you are polling to see if the asynchronous call is complete. When it’s complete, call the EndInvoke method to get back the result.

public delegate string AsyncDelegate();
        
        public string TestMethod()
        {
            string titleReturn = "";
            using (ClientContext context = new
             Microsoft.SharePoint.Client.ClientContext
             ("http://intranet.contoso.com"))
            {
        
                List list = context.Web.Lists.GetByTitle("Announcements");
                context.Load(list);
                context.ExecuteQuery();
                titleReturn = list.Title;
            }
            return titleReturn;
        }
        
        private void button1_Click(object sender, EventArgs e)
        {
        
            // Create the delegate.
            AsyncDelegate dlgt = new AsyncDelegate(TestMethod);
        
            // Initiate the asychronous call.
            IAsyncResult ar = dlgt.BeginInvoke(null, null);
        
            // Poll while simulating work.
            while (ar.IsCompleted == false)
            {
                //Do work
            }
        
            // Call EndInvoke to retrieve the results.
            string listTitle = dlgt.EndInvoke(ar);
        
            //Print out the title of the list
            MessageBox.Show(listTitle); 

Working with ECMAScript

Using ECMAScript with the client object model is similar to using the .NET object model. The main differences are that you use server-relative URLs for your ClientContext constructor, and the ECMAScript object model does not accept LINQ syntax for retrieving items from SharePoint. Instead, you use string expressions to define your basic queries. Also, ECMAScript is always asynchronous, so you need to use delegates and create callback functions for the success and failure of your call into the Client OM. The final piece, as shown in the code that follows, is that you need to:

  • Reference the WebControls namespace from the Microsoft.SharePoint assembly
  • Reference the ECMAScript Client OM in SP.js or SP.debug.js using a SharePoint:ScriptLink control
  • Put a SharePoint:FormDigest on your page for security reasons, if you want to write or update to the SharePoint database
download.eps
<%@ Page Language="C#" %>
<%@ Register Tagprefix="SharePoint"
    Namespace="Microsoft.SharePoint.WebControls"
    Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral,
    PublicKeyToken=71e9bce111e9429c" %>
        
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>ECMAScript Client OM</title>
      <script type="text/javascript">
        
        
          function CallClientOM() {
              var context = new SP.ClientContext.get_current();
              this.website = context.get_web();
              this.listCollection = website.get_lists();
        
              context.load(this.listCollection, 'Include(Title, Id)'),
              context.executeQueryAsync(Function.createDelegate(this,
               this.onQuerySucceeded), 
               Function.createDelegate(this, this.onQueryFailed));
          }
        
        
          function onQuerySucceeded(sender, args) {
        
          var listInfo = '';
        
          var listEnumerator = listCollection.getEnumerator();
        
          while (listEnumerator.moveNext())
          {
              var list = listEnumerator.get_current();
              listInfo += 'List Title: ' + list.get_title() + ' ID: ' +
               list.get_id() + '
';
          }
    alert(listInfo);
        
          }
        
          function onQueryFailed(sender, args) {
              alert('request failed ' + args.get_message() + '
' +
                args.get_stackTrace());
          }
        
      </script>
  </head>
  <body>
    <form id="form1" runat="server">
      <SharePoint:ScriptLink ID="ScriptLink1" Name="sp.debug.js" LoadAfterUI="true"
        Localizable="false" runat="server" />
        
<a href="#" onclick="CallClientOM()">Click here to Execute</a>
        
      <SharePoint:FormDigest runat="server" />
    </form>
  </body>
</html>

code snippetClientOM.aspx

Working in Silverlight

You must use the right Client OM DLLs. Silverlight has special DLLs in the %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATELAYOUTSClientBin directory for the core OM and the run time.

To deploy your Silverlight application, the application needs to run in a trusted area of SharePoint, which could be in a SharePoint library or in the ClientBin directory. The easiest way to get your code into the ClientBin directory is to make the output of your project go to this directory. For deploying to SharePoint document libraries, you could manually upload your XAP file to SharePoint and point the Silverlight web part at your manually uploaded XAP. Another way is to use a Sandbox Solution, which you learn about later in this chapter, to create a feature that copies the file to your SharePoint site using a Module with a File reference in your Elements manifest.

Watch out for the caching of your Silverlight application while you develop it. Update the AssemblyVersion and FileVersion in your AssemblyInfo file in VS to ensure that an old version is not loaded. You may also have to clear your browser cache. Another recommendation is to change something in the UI so you can recognize visually that your application is the latest version.

If you use anonymous access and Silverlight, you must modify the SPClientCallableSettings.AnonymousRestrictedTypes property. For example, if you attempt to retrieve list items as an anonymous user, you get an error message stating that the GetItems method has been disabled by the administrator. To enable this method or other methods, you can use the following PowerShell command.

$webapp = Get-SPWebApplication -Identity "http://sharepointsite"
$webapp.ClientCallableSettings.AnonymousRestrictedTypes.Remove
  ([Microsoft.SharePoint.SPList], "GetItems")
$webapp.Update()

Also, if you work across domains, you need to understand how to create cross-domain policies using a ClientAccessPolicy.XML file that you host at the root of your website. If you work in the same domain, you do not need to write this policy file, but if you go across domains (for example, if your Silverlight application runs in your domain but calls a service in another domain) you must use the ClientAccessPolicy.XML file to allow those calls. Figure 5-25 shows the Silverlight application in action.

download.eps
using SP = Microsoft.SharePoint.Client;
        
namespace SPSilverlight
{
    public partial class MainPage : UserControl
    {
        
        IEnumerable<SP.List> listItems = null;
        public MainPage()
        {
            InitializeComponent();
        }
        
        private void getItemsSucceeded(object sender,
         Microsoft.SharePoint.Client.ClientRequestSucceededEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                //Code to display items
                //Databind the List of Lists to the listbox
        
                listBox1.ItemsSource = listItems;
                listBox1.DisplayMemberPath = "Title";
            });
        }
        
        
        private void getItemsRequestFailed(object sender,
         Microsoft.SharePoint.Client.ClientRequestFailedEventArgs e)
        {
            Dispatcher.BeginInvoke(() =>
            {
                MessageBox.Show("Error:  " + e.ErrorCode + " " + e.ErrorDetails + "
                 " + e.Message + " " + e.StackTrace.ToString());
            });
        }
        
        private void button1_Click(object sender, RoutedEventArgs e)
        {
        
            ClientContext context = null;
        
            if (App.Current.IsRunningOutOfBrowser)
            {
                context = new ClientContext(
                    "http://intranet.contoso.com");
            }
            else
            {
                context = ClientContext.Current;
            }
        
            var query = from listCollection
        in context.Web.Lists
                        where listCollection.Title != null
                        select listCollection;
        
            listItems = context.LoadQuery(query);
        
        
                ClientRequestSucceededEventHandler success = new
                 ClientRequestSucceededEventHandler(getItemsSucceeded);
                ClientRequestFailedEventHandler failure = new
                 ClientRequestFailedEventHandler(getItemsRequestFailed);
                context.ExecuteQueryAsync(success, failure);
        
            }
        
        }
    }

code snippetMainPage.xaml.cs

Programming Using REST

With SharePoint 2010, you can program against SharePoint and Excel Services using Representational State Transfer (REST). This section covers the core SharePoint REST Services.

SharePoint REST services are implemented using the WCF Data Services, formerly known as the ADO.NET Data Services, and formerly known as Astoria. The easiest way to think about REST is that it provides URL-accessible functionality, so you can query, create, and delete lists and items using just the standard HTTP protocol.

Here are a couple of best practices before getting started with REST in SharePoint 2010:

  • REST is implemented in your _vti_bin directory by accessing http://yourserver/_vti_bin/ListData.svc, so if you connect to that URL and get a 404 error, you do not have the WCF Data Services technologies installed.
  • If you connect to your REST services for SharePoint from Internet Explorer (IE), turn off Feed Reading View in IE so that you get the raw XML returned from SharePoint. You can find this under Tools ⇒ Internet Options ⇒ Content ⇒ Feeds and Web Slices.

The easiest way to start with REST in SharePoint is to look at what is returned when you connect to http://yourserver/yoursite/_vti_bin/ListData.svc, as shown in Figure 5-26. You see the XML returned for all your lists in your site.

REST offers two ways to return your data in SharePoint:

  • Using ATOM, an XML format and a recognized standard.
  • Using JavaScript Object Notation (JSON), which returns your data using JSON markup so that you can parse that data using JavaScript objects.

JSON is especially good if you want to turn the returned data into JavaScript objects, but it can be useful in other situations as well. You can specify the type of data you want returned by using the Content-Type header in your request. The tools that work with REST, such as Visual Studio, use ATOM, not JSON, so you need to request JSON specifically if you want your results in that format.

Because REST uses a standard URL-addressable format and standard HTTP methods, such as GET, POST, PUT, and DELETE, you get a predictable way to retrieve or write items in your SharePoint deployment. Table 5-16 lists some examples of URL addresses.

Table 5-16: Methods and Properties for the ClientContext Class

Type Example
List of lists ../_vti_bin/listdata.svc
List listdata.svc/Listname
Item listdata.svc/Listname(ItemID)
Single column listdata.svc/Listname(ItemID)/Column
Lookup traversal listdata.svc/Listname(ItemID)/LookupColumn
Raw value access (no markup) listdata.svc/Listname(ItemID)/Column/$value
Sorting listdata.svc/Listname?$orderby=Column
Filtering listdata.svc/Listname?$filter=Title eq ‘Value’
Projection listdata.svc/Listname?$select=Title,Created
Paging listdata.svc/Listname?$top=10&$skip=30
Inline expansion (lookups) listdata.svc/Listname?$expand=Item

Using REST in Visual Studio

Because Visual Studio has built-in support for using WCF Data Services, programming with REST starts with adding a service reference in your code. Follow these steps to use REST in Visual Studio:

1. Add the service reference. In this reference, point to your ListData.svc URL in _vti_bin.
2. Create new proxy classes by running in a command prompt DataSvcUtil.exe /uri:”http://URL/_vti_bin/ListData.svc” /out:Reference.cs. This creates a C# file that you use to replace the existing Reference.cs in your project. You can find Reference.cs in the file directory for your project, not in the user interface, unless you turn on Show All Files in Solution Explorer.
3. You should see your lists in the Data Sources window inside of Visual Studio, as shown in Figure 5-27. If you do not see your datasource in the window, right-click your service reference and select Update Service Reference.
4. Add a new Object datasource to your project, so you can work with a subset of the lists, such as binding the datasource to a data grid in your code. You can do this by creating a new Object datasource and selecting the lists you are interested in, as shown in Figure 5-28.
5. Drag and drop your datasource onto your form. Visual Studio creates and binds a grid to your datasource. You can also use LINQ to program against your REST datasource. Figure 5-29 shows a databound grid against a SharePoint REST datasource.

Because the code for programming using REST is similar to programming using the Client OM, a quick example of adding an item to your SharePoint list using REST follows. Notice that it uses a call to generate a context for the rest of your calls to leverage so that you can batch commands and send them to server when you need to.

For adding, you call the specific AddTo method for your list, such as AddToAnnouncements. For updating and deleting, you use the UpdateObject and DeleteObject methods and pass in the object you want to delete, which is derived from your item type, such as AnnouncementItem.

RESTReference.HomeDataContext context = new RESTReference.HomeDataContext(
  new Uri("http://intranet.contoso.com/_vti_bin/listdata.svc"));
        
        private void button1_Click(object sender, EventArgs e)
        {
            //Populate grid using LINQ
            context.Credentials = CredentialCache.DefaultCredentials;
        
            var q = from a in context.Announcements
                    select a;
        
            this.announcementsItemBindingSource.DataSource = q;
        }
        
        private void button2_Click(object sender, EventArgs e)
        {
            //Add a new Announcement
            RESTReference.AnnouncementsItem newAnnounce = new
             RESTReference.AnnouncementsItem();
            newAnnounce.Title = "My New Announcement! " + DateTime.Now.ToString();
        
            context.AddToAnnouncements(newAnnounce);
            context.SaveChanges();
        }
External List Support and REST
Unfortunately, external lists are not supported with the WCF Data Services and REST. If you look at your lists using REST, you find that your external lists do not appear in your list results. This is a deficiency that you must work around by using other methods, such as the Client OM, to access external lists.

jQuery and SharePoint

You may also be wondering about jQuery support in SharePoint because REST supports putting out JSON objects that you can load with jQuery, as do other parts of SharePoint. Although SharePoint does not include a jQuery library, you can easily link to jQuery in your SharePoint solutions. This linking does require connectivity to the Internet. Microsoft has made jQuery and a number of other libraries available via the Microsoft Ajax Content Delivery Network. To get the jQuery library from the CDN, use the following statement in your code:

<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.5.2.js"
type="text/javascript"></script>

Deploying Your jQuery Applications

One of the big complaints about script-based applications is that deployment and maintenance is difficult. This is where the power of SharePoint comes into play. You can create a central script library in SharePoint and use the native functionality of SharePoint to maintain your scripts, such as versioning and meta data. Because SharePoint is URL accessible, you can then link to your script using the URL.

If you would rather store your scripts in the file system, you can use solution packages to deploy your scripts. Where you store your scripts is up to you, but the SharePoint option is most likely easier.

When it comes to leveraging your script, there are a number of options to link to your script. You could add the script link to your Master Page definition so that you know jQuery is referenced on every page, but that presents issues if you have lots of pages that don’t use jQuery. You pay the cost of loading the script without using the script.

jQuery Basics

If you have never used jQuery before, you are missing out. It is a library that makes programming in JavaScript a lot easier, especially when it comes to manipulating your HTML objects. When executing your jQuery methods, you can use JQuery() or the shorthand $(). Most people use the shorthand $() in their code.

To interact with elements, place the element in parentheses. For example, if you want to append some HTML to an element, you would use the ID of the element with the jQuery Append function, such as $(‘#elementid’).append(‘HTML to Append’);.

Table 5-17 lists some other examples of jQuery functions. This is not the exhaustive list of jQuery functionality, so pick up a resource on jQuery to learn more.

Table 5-17: Basic jQuery Methods

Example Description
$(‘#elementID’) Accesses any element.
$(‘#elementID’).append(‘value’); Appends text to the element.
$(‘#elementID’).html(); Gets or retrieves the HTML within an element.
$(‘#elementID’).val(); Gets the value of inputs.
$(‘#elementID’).hide(); Hides the element.
$(‘#elementID’).show(); Shows the element.
var divs = $(‘div’); Returns all instances of element specified, in this case all divs.
$(‘#elementID’).addClass(‘className’); Adds a CSS class to the element.
$(‘#elementID’).removeClass(‘className’); Removes the specified class.
$(‘#elementID’).attr(‘attribute’); Retrieves the attribute.
$(‘table[class=”className”]’).each( function () { $(‘#’ + this.id).show(); } ); Performs an action on multiple elements of the same class.

jQueryUI

After you start using jQuery, you can expand your repertoire of jQuery libraries. One key library to learn more about is jQueryUI, which adds visual elements that you can program using jQuery, such as accordions, datepickers, dialogs, and progress bars. It also includes interactions such as drag and drop and sorting.

Bringing It All Together

Rather than talk about programming with jQuery, this section presents an example. The first example combines jQuery and jQueryUI to add an accordion visual element that turns SharePoint announcements into a nicer visual representation. Notice from this example that you can leverage the SharePoint Client OM with jQuery so all your knowledge of the Client OM moves easily into your jQuery solutions. Figure 5-30 shows the final solution hosted in SharePoint.

The following listing contains the sample code for the application. jQueryUI does most of the heavy lifting so that you do not need to write a lot of code. The key point is to look at how the scripts are linked into the application via the <script> tags. Rather than going to the Internet for the scripts, the application pulls the script from a central script library under the SiteAssets folder.

<!~DH Load JQuery ~DH>
<script src= "/SiteAssets/jquery/jquery-1.5.2.js" type= "text/javascript"></script>

<!~DH Load JQuery UI ~DH>

<link type="text/css" href="/SiteAssets/jQueryUI/css/ui-lightness/
jquery-ui-1.8.11.custom.css" rel="stylesheet" />
<script type="text/javascript" src="/SiteAssets/jQueryUI/js/
jquery-1.5.1.min.jss"></script>/>
<script type="text/javascript" src="/SiteAssets/jQueryUI/js/
jquery-ui-1.8.11.custom.min.js"></script>/>

    <B>Demo - Accordion with JQuery</B>
            <div class="demoControls">
                <button id="btnDemo2" type="button">Accordion me!</button>
            </div>
            <div id="accordion">
            </div>

   
<script type="text/javascript">

    var allDocs;


    $('#btnDemo2').click(function () {

        var ctx = new SP.ClientContext.get_current();

        var targetList = ctx.get_web().get_lists().getByTitle('Announcements'),

        var query = SP.CamlQuery.createAllItemsQuery();

        allDocs = targetList.getItems(query);

        ctx.load(allDocs);

        ctx.executeQueryAsync(Function.createDelegate(this, getAllItemsSuccess),

        Function.createDelegate(this, getAllItemsFailure));

    });



    function getAllItemsSuccess(sender, args) {

        var listEnumerator = allDocs.getEnumerator();

        while (listEnumerator.moveNext()) {

            $('#accordion').append('<div><h3><a href="#">' +
              listEnumerator.get_current().get_item("Title") 
              + '</a></h3><div>' + listEnumerator.get_current().
              get_item("Body") + '</div></div>'),
 

        }


        accordionContent();

      

    }

    function accordionContent() {
        
        $("#accordion").accordion({ header: "h3" });

    }


    function getAllItemsFailure(sender, args) {

        alert('Failed to get list items. 
Error: ' + args.get_message() + 
         '
StackTrace: ' + args.get_stackTrace());

    }

</script>

What About CSS3 and HTML5?

With all the excitement around CSS3 and HTML5, you may wonder how either one plays in the SharePoint environment. Because both are browser-dependent, a lot of the answer depends on your browser, especially for HTML5. SharePoint 2010 does not leverage HTML5, but if you create content using HTML5 and place it in a SharePoint site, as long as you browse that content with an HTML5-compatible browser, your content should work seamlessly. To see some CSS3 working inside of SharePoint, Figure 5-31 shows a Polaroid sample that takes images from a SharePoint picture library and turns them into dragable Polaroid images. The image URLs are retrieved using the Client OM. Manipulating the images leverages CSS3 and jQuery.

Sandboxed Solutions

Often developers who want to build solutions for SharePoint can’t because they require access to the SharePoint server directly, and their solutions must be deployed as full-trust solutions, which could affect the stability of the server if there are bugs in the code. For these reasons, IT administrators may not allow developers to deploy their solutions against SharePoint 2007. With Sandboxed Solutions in SharePoint 2010, the server administrator can allow site administrators to deploy solutions while still maintaining the integrity of the server. Sandboxed Solutions are self-regulating because there are quotas for resource usage, and the server shuts down any solutions that exceed their quota.

Types of Solutions You Can Build

With Sandboxed Solutions, you can build a subset of the solutions you can build in SharePoint. Solutions that require extensive privileges are not allowed in the sandbox because of its limited nature. The following list gives you the types of solutions you can build with Sandboxed Solutions.

  • Content types
  • Site columns
  • Custom actions
  • Declarative workflows
  • Event receivers
  • Feature receivers
  • InfoPath forms services (not admin-approved, that is, without code-behind)
  • JavaScript, Ajax, jQuery, REST, or Silverlight applications
  • List definitions
  • Site pages (but no application pages with code-behind)
  • Web parts (but not visual web parts without the VS Power Tools installed)

Executing Code in the Sandbox

Before a Sandbox Solution can run, a site administrator must upload the solution and activate it in the site. When you upload a Sandbox Solution, you upload it to the Solution gallery.

The Solution gallery contains all your Sandboxed Solutions and displays the resource quota that your solutions are taking both for the current day and averaged over the past 14 days. The Solution gallery is located in _catalogs/solutions.

The Sandboxed Solutions architecture provides three main components to use when executing your solution. Please note that this is for on-premises SharePoint deployments since SharePoint Online does not provide access to this functionality or architecture.

  • User Code Service (SPUCHostService.exe): This service decides whether the server where this service is running will participate in Sandboxed Solutions. SharePoint has a modular architecture for Sandboxed Solutions where you can run them on your WFEs or dedicate separate servers for executing your sandbox code. If the User Code Service runs on a machine, Sandboxed Solutions can run on that machine. When you troubleshoot your Sandboxed Solutions, the first thing to check is that this service runs on a SharePoint server in your farm.
note.eps
From an architectural standpoint, SharePoint enables you to pin the execution of the Sandbox Solution to the server that received the web request. This means that the User Code Service must run on all your WFEs in your farm. Although this provides for easy administration, because you don’t need to create separate servers for Sandboxed Solutions or remember which servers the service runs on, it does limit your scalability because the WFEs must process other web requests while running the Sandboxed Solutions.
Your other option is to run requests by solution affinity. You set up application servers in your SharePoint farm that run the User Code Service and are not processing web requests. SharePoint routes Sandboxed Solutions to these servers rather than have the solution run on your WFE.
  • Sandbox Worker Process (SPUCWorkerProcess.exe): This is the process in which your code executes. As you can tell, it is not part of w3wp.exe, which is one reason you don’t need to reset your entire site when you deploy a Sandbox Solution. If debugging does not work for your sandbox, you can always manually attach the debugger to this process, but be forewarned that SharePoint may kill your debugging session in the middle if you take too long or exceed one of the quotas set on the sandbox.
  • Sandbox Worker Proxy (SPUCWorkerProcessProxy.exe): Given that SharePoint has the service application architecture, this proxy enables Sandboxed Solutions to tie into that infrastructure.

Subset Object Model

The Sandbox implements only a subset of the Microsoft.SharePoint namespace. Sandboxed Solutions enable you to use full trust proxies to access other APIs or capabilities, for example, accessing network resources. However, out-of-the-box (OOB) the following capabilities from the Microsoft.SharePoint namespace are supported:

  • Microsoft.SharePoint, except
  • SPSite constructor
  • SPSecurity object
  • SPWorkItem and SPWorkItemCollection objects
  • SPAlertCollection.Add method
  • SPAlertTemplateCollection.Add method
  • SPUserSolution and SPUserSolutionCollection objects
  • SPTransformUtilities
  • Microsoft.SharePoint.Navigation
  • Microsoft.SharePoint.Utilities, except
  • SPUtility.SendEmail method
  • SPUtility.GetNTFullNameandEmailFromLogin method
  • Microsoft.SharePoint.Workflow
  • Microsoft.SharePoint.WebPartPages, except
  • SPWebPartManager object
  • SPWebPartConnection object
  • WebPartZone object
  • WebPartPage object
  • ToolPane object
  • ToolPart object

What About Accessing External Data?

One question people commonly ask is, “If I can’t access local resources (such as the hard drive on the server or network resources except for SharePoint), how can I access external data (such as a database, Twitter, or some other external datasource)?” Well, you can use external lists and BCS in SharePoint to access external data because Sandboxed Solutions can access external lists. Of course, you need to have permissions to set up BCS and external lists, but if there are already BCS solutions set up with access to the external datasources that you need, you can use the external lists in your Sandboxed Solutions to read and write quickly to that external data.

What About iframes?

Sandboxed Solutions do support iframes, so you can add a literal control to your nonvisual web part and make the text the iframe that you want to display in the control. This enables you to connect to many solutions on the Internet, such as Silverlight or web pages that expose information that you want to display in your environment. Using Sandboxed Solutions for this, rather than content editor web parts, makes the control reusable and easier to distribute.

Visual Studio Support

VS 2010 supports Sandboxed Solutions. When you create a new SharePoint project, for project types that support Sandboxed Solutions, VS gives you the option to create and deploy your solution as a Sandbox Solution. In addition, VS limits the API set in IntelliSense to only those APIs that work for Sandboxed Solutions. VS does not do a compile-time check to determine if you are using restricted APIs because you program against the full SharePoint namespace, and it’s only at run time that your code is limited. So, if you ignore IntelliSense and write to APIs not supported in the sandbox, you don’t get a compile-time error but instead get a run-time error. One tip to avoid this is to reference the Microsoft.SharePoint.dll under the Assemblies folder, under the UserCode folder in the SharePoint hive. That limits the APIs you can use. You must remember to change the reference back to the full Microsoft.SharePoint.dll before deployment.

Visual Studio 2010 SharePoint Power Tools

One of the deficiencies of Visual Studio 2010 OOB is that it doesn’t enable you to use the visual design capabilities to create web parts in the sandbox. Instead, you must hand code all the components of your user interface. The Visual Studio 2010 SharePoint Power Tools add this capability and provide pre-compilation support so that you can view any errors for types or members not allowed in a SharePoint sandbox environment. Figure 5-32 shows how to create a sandboxed visual web part in Visual Studio.

Building Declarative Workflows

Another option for SharePoint Online development is building declarative workflows in SharePoint Designer. SharePoint Designer enables you to build workflows that you can associate with a list, a site, or a content type. In addition, you can have globally-reusable workflows that allow you to share the workflow to many lists or content types.

Beyond using SharePoint Designer to build your workflow, you can also use Visio to prototype your workflow. Then import that prototype into SharePoint Designer to add your business logic to implement the workflow. Because SharePoint Online supports Visio Services, the Visio representation of the workflow is used on the status page to visualize where the workflow is in process. Figure 5-33 shows prototyping a workflow in Visio and Figure 5-34 shows the Visio visualization status page.

SharePoint Designer Workflow Designer

Using the SharePoint Designer workflow designer is straightforward. SharePoint Designer provides a graphical user interface designer to build your workflow. This designer enables you to create your steps in your workflow, set the conditions and actions for the workflow, and pass parameters and initialization variables to your workflow.

The SharePoint Designer workflow designer also enables you to have parallel blocks and embedded steps in your workflow. The only deficiency you will find with the workflow designer is that you cannot have looping inside of your workflow.

In addition, the SharePoint Designer workflow designer enables you to use InfoPath to customize your workflow forms. This means you can customize your input parameters and the forms that appear for your workflow. Figure 5-35 shows the SPD workflow designer.

Building Workflow Actions in the Sandbox

Although the SharePoint Designer workflow designer comes with a large set of workflow actions OOB, such as sending e-mails, checking in and out documents, setting permissions, looking up users, and other actions, you may still want to extend the designer with your own custom workflow activities that you implement as a Sandbox Solution. Your workflow activities are limited to what Sandboxed Solutions can do, so you cannot access networking, disk, or any other highly elevated tasks.

When creating sandbox workflow actions, you must create a class with a method that accepts a SPUserCodeWorkflowContext as the first parameter. This method must return back a Hashtable object. This hashtable contains the return parameters that you want the workflow to receive from your action. One parameter should be success or failure, and the other parameters can be customized to your business scenario. If you add custom parameters, you need to define these in the WorkflowActions element of your feature’s elements.xml file. The following XML shows how to pass in the workflow context and return back a success or failure string:

<Parameters>
  <Parameter Name="__Context" 
             Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, 
                   Microsoft.SharePoint.WorkflowActions" 
                   Direction="In" 
                   DesignerType="Hide"/>
  <Parameter Name="Result" 
             Type="System.String, mscorlib" 
             Direction="Out" 
             DesignerType="ParameterNames" 
             Description="Success or failure result"/>
</Parameters>

Building InfoPath Forms

SharePoint Online supports InfoPath forms so that you can design the forms that run either in the InfoPath client, in the web browser, or in both. You can use InfoPath forms in SharePoint Online as a standalone form or in a web part using the InfoPath Forms web part. A key change from previous versions of SharePoint is that you can customize the OOB list forms directly in InfoPath.

note.eps
Describing all the capabilities of InfoPath Forms development is beyond the scope of this book, but you should review the InfoPath Forms chapter in the Professional SharePoint 2010 Development Wrox book for more information.

InfoPath does include a compatibility checker, so you can check your InfoPath forms before you convert them to web-based forms. This checks to make sure that you do not use any capabilities that do not translate to the web-based version of InfoPath.

To give you a taste of what InfoPath can do, Figure 5-36 shows an InfoPath form running in SharePoint Online and Figure 5-37 shows an InfoPath form running as a web part in SharePoint Online.

Using Access Services

Another option for application development is using Access and Access Services. If you are already a proficient Access database developer, you can save your Access applications to SharePoint and, similar to InfoPath, have a web-based version of your Access application. Access includes a compatibility checker to see which pieces of your application can convert to Access Services, and which pieces need to continue to run inside of the Access client. Most times, the issues you run into are VBA macros and reporting because Access Services does not support VBA, and SharePoint Online has not enabled Access Services reporting. Figure 5-38 shows a sample Access Services application running in SharePoint Online.

Connecting BCS to Windows Azure

One of the newest features to Office 365, and one that was added after the initial launch of the services, was the ability to connect BCS to external datasources using WCF endpoints. These endpoints can exist in Windows Azure or other locations. The endpoints do not need to connect solely to databases, but can also connect to other types of data such as Web Services and REST datasources. This section shows you the steps necessary to leverage this capability in SharePoint Online.

Creating the WCF Service

The first step is to create your WCF service for BCS to connect to. Your endpoint needs to implement methods for SharePoint to call in order to, at a minimum, to read from your datasource, but you can also provide capabilities to write to your datasource.

To create the WCF service, follow these steps:

1. Create a new WCF Service Web Role in Azure using Visual Studio. Figure 5-39 shows this project type.
2. After you create your project, you need to connect to your datasource. Although this example connects to a SQL Azure datasource, you can connect to any datasource that you want. To connect to SQL Azure, you need to add a new datasource to the project and use the Entity Data Model Wizard to configure your datasource, as shown in Figure 5-40.
note.eps
Make sure that you set your Build Action to Entity Deploy for your Entity Model and the Copy to Output Directory to Copy Always so that it is always deployed to Azure with your project.
3. Write code that represents your dataset that you want returned. To do this, add a new class to your service project that will be a customer record. The following code implements the customer record class called SharePointBCSRecord:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WCFServiceWebRole1
{
    public class SharePointBCSRecord
    {
        public string CustomerID { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string City { get; set; }

    }
}
4. Add code to your WCF service that will read a single item and also read all items so that the BCS service can call these methods to return both all items and a single item by its unique identifier. The following code implements this functionality:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;


namespace WCFServiceWebRole1
{


   
    public class Service1 : IService1
    {
  
        
        public SharePointBCSRecord[] GetCustomers()
        {
            using (CustomerEntities customerRecords = new CustomerEntities())
            {
                var customerRecordItems = (from c 
                in customerRecords.Customers 
                select c);

                SharePointBCSRecord[] customerArray = 
                new SharePointBCSRecord[customerRecordItems.Count()];

                int i = 0;

                foreach (Customer item in customerRecordItems)
                {
                    customerArray[i] = new SharePointBCSRecord();
                    customerArray[i].CustomerID = item.CustomerID;
                    customerArray[i].CompanyName = item.CompanyName;
                    customerArray[i].City = item.City;
                    customerArray[i].ContactName = item.ContactName;

                    i++;
                }

                return customerArray;
            }

        }

        public SharePointBCSRecord GetCustomer(string customerID)
        {
            using (CustomerEntities customerRecords = new CustomerEntities())
            {
                var customerRecord = (from c in customerRecords.Customers 
where c.CustomerID == customerID 
select c).FirstOrDefault();


                SharePointBCSRecord returnCustomerRecord = 
new SharePointBCSRecord();
                returnCustomerRecord.CustomerID = customerRecord.CustomerID;
                returnCustomerRecord.ContactName = 
customerRecord.ContactName;
                returnCustomerRecord.CompanyName = 
customerRecord.CompanyName;
                returnCustomerRecord.City = customerRecord.City;


                return returnCustomerRecord;
            }
        }
    }
}
5. After this code is added, modify the default interface contract for your service so that WCF knows what you implemented as methods as well as the required parameters to those methods. You can find the contract in the IService1.cs file in your project.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFServiceWebRole1
{
    [ServiceContract]
    public interface IService1
    {

        [OperationContract]
        SharePointBCSRecord[] GetCustomers();

        [OperationContract]
        SharePointBCSRecord GetCustomer(string customerID);

      }
}
6. Save the project and deploy it to Azure. When deployed, browse to our site to see the service running, as shown in Figure 5-41.
7. Move to SharePoint Online to configure SharePoint Online to use your service and call your methods. The first step is to set the permissions for your external content type meta data so that you can modify the meta data using SharePoint Designer. You can do this through the Office 365 administration console, as shown in Figure 5-42. You must add an account so that it can edit the metadata models.
8. Use SharePoint Designer to create your external content type just like you would if you were connecting to an on-premises SharePoint Server.
9. In SharePoint Designer, connect to your SharePoint Online team site.
10. Click the External Content Types option in the left navigation pane, and click External Content Type in the new menu.
11. Give your external content type a name, and then click to navigate datasources.
12. Select WCF as the type of connection, and in the connection properties, under the Service Metadata URL, put in the URL to your service but end it with ?wsdl.
13. In the Service Endpoint URL, put in the URL to your service, as shown in Figure 5-43.
14. Navigate the methods available from your WCF service and map them to SharePoint’s BCS methods.
15. Right-click your read item method.
16. Select New Read Item Operation and follow the wizard.
17. Do the same thing for your read items operation and map that to the new Read List Operation, as shown in Figure 5-44.
18. You must map the unique identifier from your backend system as part of the wizard for creating your operations, so you need to make sure you return that unique ID as part of your dataset from your WCF service. Figure 5-45 shows this mapping using the read item operation.
19. Now, you can create your external list by clicking the Create Lists & Forms button on the top menu, and you now have connected SharePoint Online to your datasource using WCF.

Summary

In this chapter, you learned how to build solutions with SharePoint Designer, Visual Studio, and SharePoint Online. SharePoint Online provides a robust development platform with a few limitations because you run in a multitenant environment. However, the possibilities of what you can build on SharePoint Online far outweigh the limitations that you will run into.

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

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