Chapter 4. SharePoint Platform

WHAT'S IN THIS CHAPTER?

  • An overview of the SharePoint 2010 platform

  • Programming using the new Ribbon user interface

  • Working with new functionality: Event Receivers

  • How to use the new object models, such as LINQ and the client object model

  • Programming using the new Sandbox Solutions architecture

The SharePoint 2010 platform is a large platform. If you think about all the capabilities that SharePoint provides, you can see why it needs to be big. SharePoint provides a user interface, data query and modeling, data storage, and application services as part of the product. For the developer, this provides a sea of riches, but navigating the technologies and establishing when to use one versus the other can be difficult. This chapter presents an overview of these technologies so that you can understand which API or service makes sense for the problem you are trying to solve.

PLATFORM OVERVIEW

One of the best overviews of the developer ecosystem of SharePoint comes from Microsoft itself. Figure 4-1 shows a diagram from a Microsoft poster that covers the possibilities of the SharePoint platform, the surrounding tools and ecosystem, and the target applications.

Figure 4-1

Figure 4.1. Figure 4-1

The SharePoint surface area is huge, since you not only have the SharePoint set of platform services to build on, but also the Office client integration functionality and APIs, ASP.NET, and web technology to learn, as well as the .NET Framework and its set of technologies. For this reason, being a great web or .NET developer is a good first step to becoming a great SharePoint developer, since both of those technologies are foundational technologies for SharePoint. One thing to note is that SharePoint does not support .NET 4.0, so while you can have it installed on your SharePoint Server, SharePoint or your SharePoint applications cannot take advantage of it.

NEW USER INTERFACE

The first new platform area to explore is the user interface. SharePoint has moved to using the Office Ribbon user interface as part of its web experience. You can turn off the Ribbon by changing the Master Page in your SharePoint environment if you want your own user interface, or want to use SharePoint on the Internet where a Ribbon might not be appropriate. Beyond the Ribbon, SharePoint also implements a new AJAX-style user interface so that multi-select list views, using no refreshes, and other streamlined user interface operations are possible without refreshing the page. In addition, your UI changes now can be applied not only to your application pages but also to the administration pages under the _layouts folder. There is a new dialog framework as well to make the SharePoint dialogs modal in the web experience. Plus, a new theming infrastructure makes it easy for end users or developers to customize the theme of the site. Lastly, the user interface implements a new status bar and notification area so that end users know what operations SharePoint or your application is performing on your behalf. Let's step through each of these areas and examine the changes.

General Platform Improvements

In terms of platform improvements, one of the biggest areas of investment in SharePoint 2010 was refactoring both Cascading Style Sheets (CSS) and JavaScript used in SharePoint. If you look at the CSS for 2007, it is a large CSS file that is hard to decipher and also is a big payload for the browser to download. With 2010, the CSS has been split into multiple files and supports on-demand downloading so that the CSS file will be downloaded and parsed only when the CSS class is needed for the particular HTML rendering.

Same thing goes for the JavaScript (JS) files in SharePoint. Before, you would have to download a large JavaScript file for your SharePoint site, but with 2010 the JavaScript files have been split apart and also support on-demand downloading. Plus, they have been minified, which means that all the spaces have been removed from the file. Minified files are harder to read for developers, which is why SharePoint ships a debug version of the JavaScript files. SharePoint initializes a ScriptManager control from ASP.NET AJAX to allow the new SharePoint UI to take advantage of the AJAX features in .NET.

Now, you may be wondering how you debug a minified version of the JS files. Navigate to %ProgramFiles%Common FilesMicrosoft Sharedweb server extensions14TEMPLATELAYOUTS, and you will find the debug versions of the JavaScript files in the same directory as the minified ones. The debug versions will have debug in their name — for example, sp.debug.js or sp.core.debug.js. You can browse through these JavaScript files to see the source code. In addition, you can force SharePoint to use these JavaScript files rather than the minified versions by modifying your web.config, located at %inetpub%wwwrootwssVirtualDirectories80 and adding <deployment retail="false"/>, to the system.web section.

Beyond the size changes, the JavaScript has been refactored. A big change is the naming of the JavaScript code to try to delineate between public JavaScript and internal-use-only JavaScript. So, if you see a function called SP.UI.UtilityInternal.CreateButton, that is not supposed to be used by your code.

Master Pages and _Layouts

One of the biggest pain points in SharePoint 2007 was that you could skin the application pages in your site with your own Master Page, but the administration user interface and any pages under _Layouts would not use the applied Master Page. Rather, they would use the standard SharePoint Master Page affectionately known as "blue and white." With 2010, you can now set whether the pages under _Layouts use the same Master Page as the rest of your site. This will reduce the confusion for your end users when they go from the site to the administration pages for your site and the pages look different, as in 2007. Figure 4-2 shows the setting you need to check in order to enable or disable this functionality through the web application settings in Central Administration. It is on by default and that is probably the way you want to keep it, unless you want your application pages to look different than your site.

Figure 4-2

Figure 4.2. Figure 4-2

The _Layouts pages will use a dynamic Master Page based on the site they are being accessed from. You can use the tokens ~masterurl/default.master or ~masterurl/custom.master to reference System or Site Master Pages that you want to use for your pages. If you want to create your own application page, the page must derive from Microsoft.SharePoint.WebControls.LayoutsPageBase.

For security reasons, there are seven pages that do not reference your custom Master Page unless you explicitly change this through PowerShell or the SharePoint API. The seven pages are accessdenied.aspx, confirmation.aspx, error.aspx, login.aspx, reqacc.aspx, signout.aspx, and webdeleted.aspx.

To change the Master Page using code, you will want to use the CustomMasterUrl and MasterUrl properties on your SPWeb and set those to the Master Page that you want to put in place of the current Master Page. Also, remember to change any CSS that you may need to in order to support your Master Page by using the AlernateCSSUrl property.

using (SPSite siteCollection = new SPSite("http://intranet.contoso.com"))
            {
                using (SPWeb web = siteCollection.RootWeb)
                {
                    MessageBox.Show(web.CustomMasterUrl.ToString());
                    web.MasterUrl = "/_catalogs/masterpage/minimal.master";
                    web.CustomMasterUrl = "/_catalogs/masterpage/minimal.master";
web.Update();
                }
            }

If SharePoint cannot find your referenced Master Page for any of your pages, it will default to the standard SharePoint Master Page so that the site does not break.

There are a few new Master Pages that you should be aware of with SharePoint 2010. Some were added to support the new user interface and some are added to support the older 2007 user interface running in SharePoint 2010. The first is V4.Master. This Master Page is the default Master Page for the 2010 user interface. It supports the Ribbon and all the new visuals in the 2010 product. One other reason to use V4.Master is that it implements the ability to display both the full chrome and also no chrome, depending on whether your page is displayed in context in the site with navigation and a Ribbon or as a dialog using the new dialog framework without chrome and without a Ribbon. There is nothing you have to do in order to get this functionality if you use V4.Master, since SharePoint automatically uses the right CSS classes on your behalf, based on the context your page is being called in. This allows you to use the same page for an application page as well as a dialog page, which is great for reusability.

The next is default.master. This Master Page is used to support the 2007 user interface and is used by visual upgrade to make your 2010 sites look like 2007 sites. This will make your site appear without a Ribbon and perform like a 2007 site.

One welcome addition is minimal.master. Every developer either downloads or builds their own minimal.master so that they can start with a simple Master Page and then build on top of it. Now, minimal.master ships in the SharePoint box, and you do not have to build it yourself. It is a very stripped Master Page that includes no navigation, so you will want to start adding pieces to the Master Page if you intend to use it in your site.

The last Master Page is simple.master. It is used on the seven pages that we talked about earlier, and it cannot be customized.

The Ribbon

One of the major changes you will have to get used to 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 that are 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. It is used, for example, when you click on styles in Word and you can see all the styles, or in Excel, where you can select cell styles right from the gallery control.

The Ribbon does support the majority of controls that you will 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 in order not to confuse your users. Table 4-1 lists the different controls supported by SharePoint with a description of each.

Table 4.1. SharePoint Ribbon Controls

NAME

DESCRIPTION

Button

A simple button that can be pushed.

Checkbox

A checkbox that either can have a label or not.

ColorPicker

A grid of colors/styles that can be used to choose a color.

ComboBox

A menu of selections that can be typed or selected.

DropDown

A menu of selections that can be selected by clicking.

FlyoutAnchor

An anchor button that includes a button that triggers a fly-out menu.

InsertTable

A 10x10 grid of boxes used to specify dimensions for a table.

Label

A line of text.

Menu

A container for showing popups. It can be put inside of other controls that show menus, such as the FlyoutAnchor.

MenuSection

A section of a menu. It can have a title and controls.

MRUSplitButton

A split button control that remembers the last item that was chosen out of its submenu and bubbles it up into its "button" part.

Spinner

Allows the entering of values and "spinning" through them using the up and down arrows.

SplitButton

A control with a button and a menu.

Textbox

A box where text can be entered.

ToggleButton

A button with an on/off state.

If you look at the architecture for the Ribbon in SharePoint, you will find that SharePoint makes a lot of usage of AJAX, on-demand JavaScript, caching and CSS layout to implement the Ribbon. One thing you will find is that the Ribbon uses no tables, so it is all 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 out-of-the-box (OOB) controls on existing tabs. In fact, you can entirely replace the Ribbon just by using your own custom Master Page. The Ribbon does support backward compatibility in that any custom actions you created for 2007 toolbars will appear in a custom commands tab in the Ribbon.

To understand how to customize the Ribbon, look through the different actions you normally would 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 allows you to perform your customizations by creating XML definition files. At runtime, the Ribbon runtime 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, you will want to look at using a JavaScript Page Component. The section below 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 SharePoint Server and find the file cmdui.xml. In that file, you will see the SharePoint default Ribbon implementation, and it is a good template to look at as you implement your own Ribbon controls, because it will help you to understand how certain controls work inside of the SharePoint environment.

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

XML-Only Operations

The first way you will look at customizing the Ribbon is by using only XML. 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 is used to display the new Ribbon interface. Even though you are using XML, you want to deploy your custom Ribbon XML using a SharePoint feature. So, the easiest way to get started creating a feature is by using Visual Studio 2010. Make sure to create an Empty SharePoint Project and customize the feature name and deployment path. Ribbon extensions can be Sandbox Solutions, which you will learn about later, so they can run in a restricted environment.

Once you have created your Visual Studio project, you want to create a new feature. Add a new file to your project and create an empty elements file. This is where you will 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:

<?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>
XML-Only Operations

First, all of your 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, there are attributes that you can set to specify the specifics for your customization. One key one is the Location attribute. The Location attribute tells SharePoint where your customization should appear, such as on a list view, on a form, or everywhere. The pattern match for the Location attribute is Ribbon.[Tab].[Group].Controls._children. Table 4-2 outlines the options for the Location attribute.

Table 4.2. 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. Combined together, the registration ID and the registration type define what 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 allows you to customize even further when your custom UI will appear, depending on what is displayed in SharePoint.

The Sequence attribute, which is optional, allows 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, and groups use a sequence in multiples of 10, so avoid multiples of 10. For example, if there is a Ribbon tab with the following groups: Clipboard, Font, Paragraph, then their sequence attributes could be set to 10, 20, and 30, respectively. Then, a new group could be inserted 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.

Let's expand the XML a bit more, since the current XML does nothing because you haven't added any new commands to the user interface. To add commands, you want to 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 allows you to set the location for your UI. In the example, you're adding 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 to not 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!, has two images for use depending on whether it's rendered in 16x16 or 32x32, and calls some JavaScript code to perform the action when the button is pressed. The attributes are self-explanatory, except for 1 - TemplateAlias. TemplateAlias controls whether your control is displayed in 16x16 or 32x32. If you set it to o1, you will get a 32x32 icon, and o2 makes it 16x16. You can define your own template, but most times you will use the built-in values of o1 or o2.

So, how do you call code from your Ribbon code? You will want to wrap your code in a CommandUIHandler, where you can put in the CommandAction attribute, which is inline JavaScript that will handle the action for your button. If you do not want to place your JavaScript inline, you can instead use the ScriptSrc attribute and pass a URL to your JavaScript file. Figure 4-3 shows our new custom button.

Figure 4-3

Figure 4.3. Figure 4-3

<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>
Figure 4-3

Replacing Existing Controls

There may be times when you want to replace existing, built-in controls from your SharePoint deployment and put your own control in its place. In fact, 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 thing is you have to get the Location attribute set to the exact 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. First, notice the Location attribute in the CommandUIDefinition element. It maps exactly to an ID in the cmdUI.XML file. SharePoint will parse both files and if the same ID is found, the one with the lower sequence will be put into the final XML that is parsed and used to create the Ribbon layout. Also, notice the use of the $Resources for globalization and pulling from a compressed image. If you look at the formatmap on your server, you will see that it contains lots of icons and the XML code contains the coordinates to pull the new folder icon from the larger image.

<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>
Replacing Existing Controls

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

Figure 4-4

Figure 4.4. Figure 4-4

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 will 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:

<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>
Using URL Actions

Why Doesn't My 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.

First, fire up your JavaScript debugger and set a breakpoint. Since 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.

The second thing to check is 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.

Make sure to check that the name for your function is the same for 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 will not 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? This ties in with the next tip, which 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 in 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, it is set to summary toolbar and you want it to be set to full toolbar, since the summary toolbar will not 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 permission from SPBasePermissions, such as ViewListItems or ManageLists.

Beyond permissions, you can also specify whether a person has to 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

There may be times when 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, 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, as you will see. Figure 4-5 shows a custom tab and group with three controls: two buttons and a combobox.

Figure 4-5

Figure 4.5. Figure 4-5

The code below shows the beginning of the new tab and group. As you can see, the XML looks very similar to earlier XML in creating a button. There is a tab defined that you will learn more about.

<!--Create new Tab and Group-->
  <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>
            ...

First, 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 will be used if the page is resized. A couple of things about the Scaling node. It has a GroupID attribute, which should point to the group that the scaling will affect. Second, 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, as you can see below. 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 will physically lay out the controls in your group with your description at the bottom of the group user interface. Your Group node will contain the definition for 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>
...

Once you have your tab and group, you create your controls just as you would if the control were an extension of an existing group. The code earlier showed how to create a button, so the code below shows you how to create a combobox as a control in your group.

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

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

<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>
Figure 4-5

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

ToolTips and Help

With your user interface, you should help guide the user on the usage of your controls. To aid in this, the Ribbon supports ToolTips and also linking out 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, and 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 seen writing code inline in your XML in order to handle your control commands. However, SharePoint does allow you to write more complex handlers for your user interface if you need to. You should default to trying to keep your code in the XML definition if you are creating simple buttons with simple code. However, if you are creating Ribbon extensions that are dynamically populated via code; your Ribbon requires variables beyond the default ones you can get with {SiteUrl}, {ItemId}, or other similar placeholders; or your code is so long that it may make sense from a manageability standpoint to break it out separately, then you will want to look at creating a page component.

A page component is a set of JavaScript code that can handle commands from your user interface customizations. Your JavaScript has to 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 will handle in your page component. Additionally, you can define whether your global commands should be enabled or not through the canHandleCommand function. This is the function you want to use to enable or disable your control. For example, you may want to only enable your control if the context is correct for your control to work, such as an item being selected in the user interface or the right variables are set. If you return false to this function, your user interface will be grayed out.

Lastly, the page component allows you to handle the command so if someone clicks on your button, you can run code to handle that click.

Once you have defined 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.

A couple of points about the following code. First, notice how to get the selected items by using the SP.ListOperation.Selection.getSelectedItems() method. This is a good way to determine if 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.

Second, you could write more functions to do things like populate your drop-downs dynamically or change the buttons on your user interface. In fact, 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 will want the command action 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 different ways. First, you can look at the __EVENTTARGET variable in the page request variables to see if your custom command caused the postback. The other way is to spin up a Ribbon object — make sure to reference Microsoft.SharePoint.WebControls — and implement the IPostBackHandler interface. Then, you can check to see 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 to 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 4-6 shows the custom button on the Ribbon. Also, notice that there are a custom color picker and other buttons in the figure. You can see the code to implement these other buttons in the sample code for this chapter.

Figure 4-6

Figure 4.6. Figure 4-6

<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>
Figure 4-6

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 4-7 shows the form used to tell SPD how to customize the Ribbon for your list.

Figure 4-7

Figure 4.7. Figure 4-7

Contextual Tabs and Groups with Web Parts

There may be times when you 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 where 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.

In order 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.

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;
            }
        }
Contextual Tabs and Groups with Web Parts

Then, you need to implement a custom page component using JavaScript. This is very similar to the code from earlier in the chapter where you add and register the custom page component. You will notice that the code uses the executeOrDelayUntilScriptLoaded command, which is part of the SharePoint infrastructure to only load and run script on demand.

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>";
            }
        }
Contextual Tabs and Groups with Web Parts

Contextual tabs actually always exist in 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 here.

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;

        }
Contextual Tabs and Groups with Web Parts

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 that does this, and 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, and also when to focus on the contextual tab and when to yield focus depending on whether the web part is selected or not.

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");
Contextual Tabs and Groups with Web Parts

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 appears below the Ribbon tab and to the right and is transient in nature. Both should be used to give the user contextual information without being distracting. For example, if you are editing a page and have not checked it in or published it, the status bar will tell you that only you can see the page. The status bar is for more permanent information, while the notification area is similar to instant message popups or Windows system tray notifications, in that 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 both client- or 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 different preset colors, depending on the importance of the message. To work with the status bar, you want to use the SP.UI.Status class. It is a pretty simple client-side API, since it contains only five methods, and you can find their definitions in SP.debug.js. On the server side, you will want to use the SPPageStatusSetter class, which is part of the Microsoft.SharePoint.WebControls namespace. That API is even simpler in that you call one method — AddStatus. You can also use the SPPageStateControl class to work with the status bar on the server side. Table 4-3 outlines the five methods for the client side.

Table 4.3. SP.UI.Status

NAME

DESCRIPTION

addStatus

Method that allows 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

Method that appends status to an existing status. You need to pass the status ID, title, and HTML you want the new status appended to.

updateStatus

Updates an existing status message. You need to pass the status ID and the HTML payload for the new status message.

setStatusPriColor

Allows you to set the priority color to give a user a visual indication of the status messages' meaning, 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 or not. Most times, you will 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. Once you do this, you will see what appears in Figure 4-8.

Figure 4-8

Figure 4.8. Figure 4-8

<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/>
Figure 4-8

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 was a need to give users feedback that their pages and actions were completed. The notification area does this by telling the user that the page is loading or that a save was successful, which used to be indicated by a postback and page refresh.

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

Table 4.4. SP.UI.Notify

NAME

DESCRIPTION

addNotification

Method that allows you to pass your HTML payload, whether the notification is sticky or not (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.

<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"/>
SP.UI.Notify

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 4-9 shows a custom dialog in SharePoint 2010.

Figure 4-9

Figure 4.9. Figure 4-9

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

Programming the Dialog Framework

If you look in SP.UI.Dialog.debug.js, you will see the implementation for the dialog framework. The framework has a JavaScript API that you can program against to have SharePoint launch and load your own dialogs. The way you do this is 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 4-5.

Table 4.5. 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 will autosize the dialog.

Height

The height of the dialog box as an integer. If you don't specify a height, SharePoint will autosize the dialog.

autoSize

Boolean that specifies whether to have SharePoint autosize the dialog.

X

x coordinate for your dialog.

Y

y coordinate for your dialog

allowMaximize

Boolean that specifies whether to allow the Maximize button in your dialog.

showMaximized

Boolean that specifies whether to show your dialog maximized by default.

showClose

Boolean to specify whether to show the Close button in the toolbar for the dialog.

url

URL for SharePoint to load as the contents for your dialog.

Html

A DOMElement, which contains the HTML you want SharePoint to load as the contents for your dialog. Please note that 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

Title of your dialog.

dialogReturnValueCallback

The function SharePoint will call 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. First, if you are going to 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. One utility you will see used 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.

Another tip is that 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 will respect that.

Looking at the following code, you will see the function OpenDialog. 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 use 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 will be 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.

If you look at the CloseCallback function, you will see that it gets the result and any return value. The result will be 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.

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'),
  }
}
Parameters for the SP.UI.showModalDialog method

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 the code for the dialog loaded by SharePoint. A couple of things to note in the code: First, there are two buttons for OK and Cancel, respectively. If you look at the onclick event handlers for the button, you will notice that they use methods from the window.frameElement object. By using this object, you can get methods from the dialog framework. As you can see, commitPopup will return OK, and cancelPopUp will return Cancel as the result of your dialog. Table 4-6 shows the methods you want to use from the frameElement.

<p>
<img src="/_layouts/1033/images/DefaultPageLayout.gif" 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" />
Parameters for the SP.UI.showModalDialog method

Table 4.6. 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

Will navigate to the parent of the dialog

Theming Infrastructure

One of the advancements in 2010 is a new theming infrastructure. With 2007, if you wanted to change the user interface, you had to do a mixture of changes from Master Pages to CSS to trying to hack inline styles contained in the product. With 2010, this is all simplified, since all styles are moved out into CSS files and certain styles are replaceable using the new theming infrastructure. Plus, rather than creating themes by hand, you can create themes using Office applications, such as PowerPoint, which makes it easy for end users to create new themes to apply to their sites.

Much of the SharePoint user interface supports theming. Supported elements include:

  • Ribbon

  • Site title, icon, and description

  • Secondary title, description, and view name

  • List item selection and bulk editing highlighting

  • ECB menu

  • Quick Launch

  • Tree control

  • Top navigation bar

  • Site Actions menu

  • Welcome menu

  • Breadcrumb control

  • Layout pages (Site Settings Menu)

  • Popup dialogs

  • Error messages/pages

  • Web part chrome/Tool pane

  • RTE Editor

  • Search control

  • Forms

In order to support theming, SharePoint processes the CSS and supporting images that you create. For example, SharePoint can add effects to your images, such as a gradient and rounded corners, if you provide the special theming markup to your elements. This special markup to support theming needs to be placed in your CSS file, and your CSS file has to be placed in a themable location, which is the content database or, more frequently for custom solutions, in the %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATELAYOUTS1033STYLESThemable folder.

The CSS processor works by looking for particular markups using CSS comments and then performing the actions specified by that markup to replace the CSS style with whatever theme is applied to the site. For example, suppose that you had a CSS declaration such as:

.major-font
{
    /* [ReplaceFont(themeFont: "MajorFont")] */
    font-family: Verdana, MS Sans Serif, Sans-Serif;
}

.minor-font
{
    /* [ReplaceFont(themeFont: "MinorFont")] */
    font-family: cursive;
}

.bg-image1-lt1dk1
{
    /* [RecolorImage(lightThemeColor: "Light1", darkThemeColor: "Dark1")] */
    background-image: url("../images/bl_Navbar_Gd_Default.jpg");
}

.class
{
        /*[ReplaceColor(BackgroundColor1)]*/
        Color:#FFFFFF;
}

Notice the markup before each of the CSS declarations. Because of these markups, SharePoint would replace the fonts, recolor the image, and change background color if a new theme was selected that used different elements than the ones specified.

To support theming, SharePoint has enhanced the site theme user interface so that you can preview your changes before you actually make them. Figure 4-10 shows the new site theme administration interface.

Figure 4-10

Figure 4.10. Figure 4-10

The three commands that you can perform are ReplaceColor, ReplaceFont, and RecolorImage. Each of these commands has parameters you can specify to customize the command. Table 4-7 describes these commands.

Table 4.7. Theme Commands

NAME

DESCRIPTION

ReplaceColor(string themeColor)

Replaces the color of the CSS rule with the specified color. You can specify advanced parameters, such as making colors lighter or darker by a certain percentage. For example, /* [ReplaceColor(themeColor:"Light2" {lighter: 0.2})] */ background-color:#f5f6f7;

ReplaceFont(string themeFont)

Replaces the font-family with the specified font-family. For example, /* [ReplaceFont(themeFont: "MajorFont")] */ font-family: Verdana, MS Sans Serif, Sans-Serif;

RecolorImage(string startThemeColor, string endThemeColor, optional string grayscaleImageUrl, optional method: string method)

Recolors the image. This only works for background-image. The grayscaleImageURL specifies a grayscale image that needs to be colorized. For example, /* [RecolorImage(lightThemeColor:

"Light1", darkThemeColor: "Dark1")] */   background-
image: url("../images/bl_Navbar_Gd_Default.jpg");

If you want to recolor the image by blending, filling, or tinting it, you can use the optional method parameter, such as:

/* [RecolorImage(themeColor:"Light2",method:"Filling"
)] */ background:url("/_layouts/images/qlbgfade.png")
repeat-x left top;

Or

/* [RecolorImage(themeColor:"Light2",method:"Tinting")]
*/ background-image:url("/_layouts/images/bgximg.png");

Because a lot of elements support themes, including web parts, you will want to make sure that when you design SharePoint applications, you keep theming in mind. This means that you should move away from using CSS inline styles, since these will not be themable by the engine. Instead, if you use CSS files and appropriately mark up those CSS styles using the theme attributes, then your custom applications will be themable using the built-in SharePoint infrastructure. The extra work of marking up your CSS is worth it for the time savings of not having to write your own theming interface for your applications, plus you will get better user interface integration by not having your application ignore the theme when it changes in the SharePoint product.

Programming Using the Theme API

To work with themes, there is a new class in the Microsoft.SharePoint.Utilities namespace, called ThmxTheme, that provides methods and properties that make programming with themes easier. This class allows you to create new themes and query existing themes in the system. Table 4-8 outlines the important methods and properties of the ThmxTheme class with supporting sample code below the table to show you how to use this class.

Table 4.8. ThmxTheme Class

NAME

DESCRIPTION

EnforceThemedStylesForWeb(SPWeb web)

Forces the styles for the theme to be applied to the SPWeb specified.

GetManagedThemes(SPSite site)

Returns a collection of themes as Theme objects for the specified site.

GetThemeUrlForWeb(SPWeb web)

Gets the Theme URL for the specified SPWeb object.

SetThemeUrlForWeb(SPWeb web, string themeUrl)

Sets the theme URL for the SPWeb specified to the string in the second parameter.

Open(Multiple Overloads)

Opens the theme either using a stream or using an SPFile object, which are the most common overloaded versions of this method.

Save

Saves the theme back to the stream.

AccentColor1

Property that specifies the accent color for the theme. You set it by setting the DefaultColor property and then saving the theme.

Please note that there are many other similar properties such as AccentColor2, DarkColor1, HyperlinkColor, and LightColor1 that behave the same way and just change different styles.

using (SPSite site = new SPSite("http://intranet.contoso.com"))
            {
                //Get all the themes for the site

                foreach (ThmxTheme theme in ThmxTheme.GetManagedThemes(site))
                {
                    //Get Azure hyperlink color
                    if (theme.Name == "Azure")
                    {
                        MessageBox.Show(theme.HyperlinkColor.DefaultColor.Name +
" " + theme.HyperlinkColor.DefaultColor.ToString());
                    }
                }
//Upload a new theme from the file system
                FileStream fs = File.Open(
"c:\users\administrator\desktop\test.thmx",FileMode.Open);


                ThmxTheme newtheme = ThmxTheme.Open(
fs,FileMode.Open, FileAccess.ReadWrite);
                newtheme.Name = "My Test Theme";
                newtheme.HyperlinkColor.DefaultColor = Color.Black;

                newtheme.Save();
}

LIST, VIEW, AND EVENT ENHANCEMENTS

There are a number of new list, view, and event enhancements in SharePoint 2010. 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, there are new events that you can take advantage of with SharePoint 2010 — for example, when new sites and lists are added. Let's 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, since SharePoint uses lists itself for its own functionality and ability to run. With 2010, there are new list enhancements and even new tools that you can take advantage of to work with your custom lists.

Note

One enhancement, support for large lists and list throttling, already has been discussed in Chapter 3, so refer to that chapter to understand that enhancement.

SharePoint Designer and Visual Studio Support

Before diving into the new enhancements in lists, you need to first look at the tools used to create your lists. The tools of choice are SharePoint Designer (SPD) and Visual Studio (VS). Both are good choices, depending on what you are trying to do. If you want barebones, down to the metal, XML-style creation of lists, then Visual Studio will be your choice. 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 would be a better choice if you are interested in a GUI editor.

Diving into SPD, SPD makes it easy to work with your lists, whether it's creating new lists or modifying your existing lists. SPD can make quick work of your columns, views, forms, content types, workflows, and even custom actions for your list. 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 will have to give up some control, since SPD does not allow 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 4-11 shows the List Settings user interface for SPD.

Figure 4-11

Figure 4.11. Figure 4-11

With Visual Studio, you can create list definitions and list instances. List definitions are a built-in project type for Visual Studio.

Warning

One word of warning, don't expect nice designers when you create a list definition. Instead, get ready to work with some XML. The nice thing about the list definition project in Visual Studio is that it allows 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 different sites. If you need the ultimate in 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 how it does not 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 now can 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 very complex data model, you will want to use SQL Server and surface SQL Server through SharePoint, using Business Connectivity Services (BCS) and external lists.

The way that list relationships work is you create a lookup between your lists. One new thing about lookups in a list is that you can retrieve more than just the identifier and can retrieve 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 that are related to the parent item, or cascade the delete from the parent list to the child list. Figure 4-12 shows the user interface for setting the properties of the lookup column to enforce relationship behaviors.

Figure 4-12

Figure 4.12. Figure 4-12

If you restrict the delete, SharePoint will throw an error telling the user that there is an item in the related list that exists and will cancel deleting the error, as shown in Figure 4-13.

Figure 4-13

Figure 4.13. Figure 4-13

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

Please note that 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 will not support the referential integrity features such as cascading delete. Also, referential integrity will not be enforced for a lookup that you allow to have 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.

If you look at the SPWebApplication class, you will see two properties that affect relationships. The first property is CascadeDeleteMaximumItemLimit, which allows you to specify as an integer the maximum number of cascaded items that SharePoint will delete. By default, this value is 1000 items. The other property is CascadeDeleteTimeoutMultiplier, which allows 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 a 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 is the ability to do list validation using formulas. This is more of an end user or power user feature, but for simple validation scenarios, developers will find this feature easy to use, and quick to write formulas rather than writing 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 4-14 shows setting the formula, and Figure 4-15 shows the custom error message that appears when the formula does not validate.

Figure 4-14

Figure 4.14. Figure 4-14

Figure 4-15

Figure 4.15. Figure 4-15

One of the easiest ways to understand what 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 will use the SPList.ValidationFormula and SPField.ValidationFormula properties to get and set your formulas.

Ensuring Uniqueness

Another new feature of lists is the ability to ensure uniqueness for the values in your columns. SharePoint would previously allow you to not 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, since 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 datasources 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 exist in the other list, will be returned in the result set.

The code below 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 are joining 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. Once you get back your results, you will have to 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 for lists is the ability to customize the default forms for your list items. SharePoint 2010 moves 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 2010, you can modify the New, Display, and Edit forms. For more on forms, take a read through Chapter 9.

When you use the Ribbon option to edit the form in InfoPath, InfoPath will automatically be launched, connect to your list, and display your form as shown in Figure 4-16.

Figure 4-16

Figure 4.16. Figure 4-16

View Enhancements

The biggest change with views in 2010 is the change of the technology used to display views. 2010 uses the SharePoint Designer XsltListViewWebPart as the default view web part for viewing lists. There are a number of reasons why this is much better than 2007. First, XSLT views allow you to replace your use of CAML to create views, and can move to using standards-based XSLT to define your view. Second, performance is better than 2007 with the new XSLT view. Third, editing with SPD is easier, since the XSLT view technology is an SPD technology. Lastly, 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 4-17 shows the editing of the view in SPD.

Figure 4-17

Figure 4.17. Figure 4-17

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, you will use the SPView object and SPViewCollection. You can add new views, modify existing views, or delete views. There are a few properties that you will be interested in. One is the DefaultView off the SPList object; this property returns an SPView object, which is the default view for your list. From there, you can use the RenderAsHTML method, which will return the HTML that your view will render. You can also use PropertiesXml, Query, SchemaXml, and Xsl, which return the properties, query, schema, and XSL used in your list, respectively.

EVENTS ENHANCEMENTS

With 2010, there are six new events that you can take advantage of, including WebAdding, WebProvisioned, ListAdding, ListAdded, ListDeleting, and ListDeleted. This is in addition to the existing events that were introduced in SharePoint 2007, such as the ItemAdding, ItemUpdating, and ItemUpdated events. 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, custom error pages and redirection, and finally, impersonation enhancements.

New Events

As part of SharePoint 2010, there are six new events that you can take advantage of. These events allow you to capture creation and provisioning of new webs and the creation and deletion of lists. Table 4-9 goes through each of the events and what you can use them for.

Table 4.9. 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, since 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 being 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.

Using these events is the same as writing event receivers for any other types 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 4-18 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. Once 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.

Figure 4-18

Figure 4.18. Figure 4-18

The code that follows shows you how to use the new web events in SharePoint. The code writes to the event log the properties for the event. 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 using the Cancel property and setting it to false. These events will fire even in the Recycle Bin, so if you restore a list or delete a list, you will get an event for those actions.

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);

            }


            //Log out to the event log
            string source = "WebEventCustomLog";
            string logName = "Application";

            if (!EventLog.SourceExists(source))
            {
                EventLog.CreateEventSource(source, logName);
            }

            try
            {
                EventLog.WriteEntry(source, sb.ToString());
            }
            catch (Exception e)
            { }

        }

    }
}
Figure 4-18

New Event Registration Feature

To support the new events, SharePoint has added a new registration capability for registering your event receivers, using the <Receivers> XML block. The new capability includes registering your event receiver at the site collection level by using the new Scope attribute and setting it 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 will work 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 just have the receiver work on the root site by using the RootWebOnly attribute on the <Receivers> node. The last new enhancement is the ListUrl attribute, which allows you to scope your receiver to a particular list by passing in the relative URL.

Post-Synchronous Events

With 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 be not ideal. With 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.

Custom Error Pages

With 2007, you can cancel events and return an error message to the user, but that provides limited interactivity and not much help to the user beyond what your error message says. 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 allows you to have more control of what the users see, and you can try to help them figure out why their action is failing. The custom error pages and redirection will only work for pre-synchronous events, so you cannot do this for post-synchronous events such as ListAdded. Plus, this will only work with browser clients. Office will just put up 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.

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

Impersonation Enhancements

The last area of enhancement for events is in the impersonation that events support. SharePoint runs your events in the context of the user who triggered the event. Generally, this is okay, but there may be certain times when you want to let a user perform actions on lists, libraries, or the system that the current user does not have permissions to do. In most cases, you would use SPSecurity's RunwithElevatedPrivileges method. However, you may want to revert to the originating user on some operations. With 2010, the event property bag contains the OriginatingUserToken, UserDisplayName, and UserLoginName, which you can use to revert to the original user. The following code shows how, in a ListAdding event, you can elevate some code to the system account and then revert some code to the original user by using these properties.

public override void ListAdding(SPListEventProperties properties)
       {
           LogListEventProperties(properties);
           StringBuilder sb = new StringBuilder();

           SPSecurity.RunWithElevatedPrivileges(delegate()
           {

               using (SPSite site = new SPSite(properties.SiteId))
               {
                   // Running under the "system account" now
                   // Perform operations
                   sb.AppendLine("SiteURL: " + site.Url);
                   sb.AppendLine("Impersonating: " + site.Impersonating.ToString());
                   //Get the web to get the current user
                   using (SPWeb web = site.OpenWeb())
                   {
sb.AppendLine("Name and LoginName: " +
 web.CurrentUser.Name.ToString() + " " + web.CurrentUser.LoginName.ToString());
                   }
                   LogImpersonation(sb.ToString());


               }
           });

           //Clear our stringbuilder
           sb.Length = 0;
           sb.Capacity = 0;


           //Now access the site with the original user token
           using (SPSite originalsite = new SPSite(properties.SiteId,
 properties.OriginatingUserToken))
           {
               //Running under the original user account that generated the event
               //Perform operations
               sb.AppendLine("SiteURL: " + originalsite.Url);
               sb.AppendLine("Impersonating: " +
 originalsite.Impersonating.ToString());
               //Get the web to get the current user
               using (SPWeb web = originalsite.OpenWeb())
               {
                   sb.AppendLine("Name and LoginName: " +
 web.CurrentUser.Name.ToString() + " " + web.CurrentUser.LoginName.ToString());
               }

               LogImpersonation(sb.ToString());

           }




           base.ListAdding(properties);
       }

    private void LogImpersonation(string valueToLog)
       {

           string source = "ListEventCustomLog";
           string logName = "Application";

           if (!EventLog.SourceExists(source))
           {
               EventLog.CreateEventSource(source, logName);
           }

           try
           {
               EventLog.WriteEntry(source, valueToLog);
           }
           catch (Exception e)
           { }
       }
Impersonation Enhancements

OVERVIEW OF DATA TECHNOLOGIES

When it comes to SharePoint, working with, manipulating, and displaying data is one of the most 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. Since SharePoint can surface its data in so many different ways, whether that is through the browser, inside of Office, in applications running on the SharePoint server, or in applications running off the SharePoint server, there are a number of different 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 are writing your application to run on or off the SharePoint server. Your choices in 2010 are: LINQ, Server OM, Client OM, or REST. Of course, you can continue to use the web services APIs of SharePoint, but you will want to look at moving to the client OM rather than using that technology. Table 4-10 goes through the pros and cons of each data access technology.

Table 4.10. Data Access Technologies

NAME

PROS

CONS

LINQ

Entity-based programming

Strongly typed

Supports joins and projections

Good tools support and IntelliSense

Server-side only

New API, so new skills required

Pre-processing of list structure required, so changing list could break application

Server OM

Familiar API

Works with more than just list data

Server-side only

Strongly typed

Client OM

Works off the server

Easier than web services API

Works in Silverlight, JavaScript and .NET

More than just list data

New API

Weakly Typed

REST

Standards-based

URL-based commands

Strongly typed

Only works with lists and Excel

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 allows 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 being passed back to the server.

Getting Started with SharePoint LINQ: SPMetal

The first step in getting 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 will parse your lists and generate the necessary classes for you that you can import into your Visual Studio projects. You can find SPMetal at %ProgramFiles%Common FilesMicrosoft Sharedweb server extensions14 BIN. 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 pre-build for your project so that the latest version of your entity classes are always included.

Using SPMetal is straightforward for the common scenarios that you will want to do. It does support XML customization, but most of the time you will find that you do not need to customize the default code generation. Table 4-11 shows the SPMetal command-line parameters that you can pass.

Table 4.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 of the location where you want the outputted code to be placed.

Language

The programming language you want generated. 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.

Namespace

The namespace you want used for the generated code. If you do not specify this property, SPMetal will use the default namespace of your VS project.

Useremoteapi

SPMetal will use the client object model if you specify this parameter.

User

Allows you to specify DOMAINusername such as /user:DOMAINusername that SPMetal will run as.

Password

The password that SPMetal will use to log on as the user specified in the /user parameter.

Serialization

Specifies whether you want your objects to be serializable or not. By default, this parameter is none, so they are not. If you specify unidirectional, then SPMetal will put 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 over 3000 lines long! You will definitely want to use SPMetal to generate this code and tweak SPMetal as you need to in order 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

Once you have your generated SPMetal code imported into VS, it's time to make sure you have the right references set up to use that code. You will want to add two references at a minimum. The first is a reference to Microsoft.SharePoint, which is the general SharePoint namespace. The second is a reference to the specific SharePoint LINQ assembly using Microsoft.SharePoint.Linq. This will add all the necessary dependent LINQ assemblies to your project.

Working with DataContext Object

The DataContext object is the object that provides the heart of your LINQ programming. Your DataContext object will be named whatever you named the beginning of your generated file from SPMetal. For example, if you had SPMetal create a LINQDemo.cs file for your outputted code, your DataContext object will be LINQDemoDataContext. To create your DataContext object, you can pass along the URL of the SharePoint site to which you want to connect.

Once you have your DataContext object, you can start working with the methods and properties of that object. The DataContext will contain all your lists and libraries as EntityList properties. You can retrieve these lists and libraries and then work with them. Table 4-12 lists the other methods and properties you will use from the DataContext object.

Table 4.12. Common Methods and Properties on DataContext Object

NAME

DESCRIPTION

GetList<T>

Returns the list of the specified type, for example, GetList<AnnouncementsItems>("Announcements")

Refresh

Refreshes the datasource.

RegisterList

Allows 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

Boolean that gets or sets whether LINQ should defer loading your objects until they are needed.

Log

Gets or sets 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 will get autogenerated typed data classes and relationships using the Association attribute. This allows you to use strongly typed objects for your lists and also to do queries across multiple lists that are related by lookup fields. As you will see in the examples, this makes programming much cleaner and also allows you catch compile-time errors when working with your objects, rather than runtime errors.

Querying Data, Enumerating, 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 will throw 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 SharePoint will error on:

  • 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);
}

As you can see in the code, you first need to get your DataContext object. From there, you define your LINQ query as you do against any other datasource. Once you have the results, you can enumerate them using a foreach loop. If you want, you can also use the ToList method to return a generic list that you can perform LINQ to Object operations on.

You can add where clauses to your queries to perform selection. For example, if in the query above you wanted to select only orders that were more than $1000 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 will perform a simple INNER join between two lists that share a lookup field. Since CAML now supports joins, this is supported in LINQ as well. A couple of things to note. First, you'll notice that you get the EntityList objects for the two lists that you will join, so you can use them in the query. Then, in the query, you just use the join operator to join the two lists together on a lookup field. From there, the code 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 allows you to add, delete, and update your data in SharePoint. Since LINQ is strongly typed, you can just create new objects that map to the type of the new objects you want to add. Once 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. Since LINQ works asynchronously from the server with a local cache, you need to call SubmitChanges after all modifications to data through LINQ.

To update items, it's a matter of 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.

Since 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 general exceptions. The code that follows shows how to code for all of 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. Once 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, KeepCurrentValues, or OverwriteCurrentValues. 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. Another choice is KeepCurrentValues, which 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. 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 for you, 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 would execute 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 that is 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;

OUTPUT:

<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"><!--List
Name: Customers--><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 are just querying the list and are not planning to add, delete, or update items in the list. This will make your queries perform better, since LINQ will not 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

There are still definite times when you should revert to using CAML directly. One scenario is where performance is paramount. LINQ makes CAML programming much easier, but no matter how LINQ is optimized, it will add some overhead to your code. Another example is if you have large amounts of adds, deletes, or updates that you need to perform. CAML will provide better performance in this scenario.

Managed Client OM

If you wanted to program on the client side in SharePoint 2007, you had in reality one choice of API, which was the web services API. While functional, the web services API was not the easiest API to program against, and while it was easy to program the web services API from Windows Forms, programming it from JavaScript or Silverlight was difficult at best. With the growth of client-side technologies, such as .NET CLR–based clients (e.g., 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.

The client OM is really two object models. One works with .NET-based clients, such as Windows Forms, WPF, or Silverlight, since these clients can handle the results in .NET objects, while ECMAScript/JavaScript will get back the JSON response. Figure 4-19 shows the way the client object model works.

Figure 4-19

Figure 4.19. Figure 4-19

One principal of the client object model is to minimize network chatter. When working with the client OM, Fiddler will be a key tool to help you troubleshoot any issues, since the client OM batches together 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 will make your application perform better. In addition, you will want to write asynchronous code with callbacks when working with the client OM so that your user interface doesn't block when users perform actions, which is what they are used to when working with web-based applications.

In terms of API support, the client OM supports a subset of the server object model, so you will find access to lists, libraries, views, content types, web parts, and users/groups as part of the object model, but it does not have coverage of all features, such as the taxonomy store or BI. Figure 4-20 shows the major objects in the client OM.

Figure 4-20

Figure 4.20. Figure 4-20

There is also a difference in the namespaces provided by the .NET and ECMAScript object models. Since you will 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 4-13 shows the different namespaces for the two client OMs.

Table 4.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 show you how to map your understanding of server objects to the client, Table 4-14 shows how server objects would be named in the client OMs.

Table 4.14. Equivalent Objects in Server and Client OMs

SERVER OM

.NET MANAGED

ECMASCRIPT

Microsoft.SharePoint.SPContext

Microsoft.SharePoint.Client.ClientContext

SP.ClientContext

Microsoft.SharePoint.SPSite

Microsoft.SharePoint.Client.Site

SP.Site

Microsoft.SharePoint.SPWeb

Microsoft.SharePoint.Client.Web

SP.Web

Microsoft.SharePoint.SPList

Microsoft.SharePoint.Client.List

SP.List

Microsoft.SharePoint.SPListItem

Microsoft.SharePoint.Client.ListItem

SP.ListItem

Microsoft.SharePoint.SPField

Microsoft.SharePoint.Client.Field

SP.Field

Which DLLs Implement the Client OM

Before diving into writing code with the client OM and adding references in VS, you first need to understand where these DLLs are located and some of the advantages of the DLLs, especially size. As with other SharePoint .NET DLLs, you will 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 over a hefty 15MB.

Since 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 will find three relevant JS files, SP.js, SP.Core.js, SP.Ribbon.js, and SP.Runtime.js. Of course, when you are debugging your code, you will want to use the debug versions of these files, such as SP.debug.js, since 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 1MB.

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

Microsoft is finalizing the distribution of these files, since on client machines you may need to be able to distribute the files, depending on whether the machine has the required DLLs already installed from other applications, such as Microsoft Office 2010. Until this is finalized, for your development machines, you can either develop on your server or copy the correct files to your development machine.

Adding References Inside VS

Depending on the type of application you are writing, the way you reference the different client OMs will vary. 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 4-21 shows adding a reference inside of Visual Studio.

Figure 4-21

Figure 4.21. Figure 4-21

When it comes to the ECMAScript object model, the way you will reference this is by using the ScriptLink control, which is part of the Microsoft.SharePoint.WebControls namespace, to add a reference to the ECMAScript files. 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 your code will run in. With the client OM, by default, your code will run in the context of the Windows authenticated user. Since many web applications support forms-based authentication, the client OM supports this as well. You will have to 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;

ClientContext Object

At the heart of all your code is the ClientContext object. This is the object that you will 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 in order to open your site, but with the ECMAScript API, you can pass a relative or blank URL in your constructor and SharePoint will either find the relative site or use the current site as the site you want to open.

One quick note on ClientContext is that, if you look at the implementation, you will notice that it inherits from IDisposable. This means that you will want to properly dispose of your ClientContext objects either 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.

Looking at 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 4-15 shows the important methods and properties for the ClientContext class.

Table 4.15. Methods and Properties for the ClientContext Class

NAME

DESCRIPTION

Dispose

Call this method to dispose of your object after you are done 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 allows 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

Allows you to load your query using the method syntax of LINQ and will fill the object you pass. You can also just 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, FormsAutentication, or Anonymous.

FormsAuthenticationLoginInfo

Use this property to set the username and password for your forms authentication to authenticate against your site.

RequestTimeout

Get or set 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 you will see in the sample code throughout this section, you will 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 will want to perform with the client OM, to show you how to use it.

Retrieving Items from SharePoint

To retrieve items from SharePoint, the easiest way to get back your list is to just use the Load method to load the object into the client OM. For example, if you wanted 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();

One thing to note is that if you try to use any of the other objects below the requested site, you will get an error saying that the collection is not initialized. For example, if you try to retrieve the lists in the site, you will get an error. 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 will return a large set of properties and hydrate your objects with these properties. For performance reasons, you may not want to have it do that if you are only using 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 letting 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 our lists only the Title property, since that is all we use 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 vs. LoadQuery

You may be wondering 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 will fill in that object with the properties of your SharePoint web. LoadQuery does not fill in the objects in-context, so it returns an entirely new collection. The LoadQuery method is more complex, but it also is more flexible. In certain cases, it allows 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, while another collection returns lists with a certain number of items. You can destroy these objects also out of context. With the Load method, the objects are tied to the client context, so they are only destroyed and are eligible for garbage collection when the client context is destroyed.

The LoadQuery method is very 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 into the ViewXml property the CAML query that you want to perform. 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 also use LINQ to query your lists. To do this, you create your query and put it in a variable. Next, using the LoadQuery method you pass your LINQ query. Then, you can 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 for your list, such as the title and the type. 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, use the Fields collection for your list and define the XML in the AddFieldAsXml property. This property takes your XML, a Boolean that specifies whether to add the field to the default view, and AddFieldOptions, such as adding the field to the default content type.

Once you have added your field, you can create list items by first creating a ListItemCreationInformation object, and pass that to the AddItem method, which will return 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 are done modifying your properties.

When you are done with all your changes, 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 is that when you are deleting items from a collection, you will want to 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 working with lists, libraries, and items, is 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 off 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, you use the UserCreationInformation object and set the properties on that object, such as Title, LoginName, and other properties. Then, you call the Add method on your UserCollection object to add the user, and ExecuteQuery to submit the changes. Since this is very similar to the steps 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 of the code so far that we have looked at 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 will see how to 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 will call back on 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. Once it's complete, you 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 very similar to 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 will use string expressions to define your basic queries. Also, ECMAScript is always asynchronous, so you have to use delegates and create callback functions for the success and failure of your call into the client OM. The final piece, as you see 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, and finally put a SharePoint:FormDigest on your page for security reasons, if you want to be able to write or update to the SharePoint database.

<%@ 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>
Working with ECMAScript

Working in Silverlight

Working with Silverlight is also similar to working with the .NET client, except for two major differences. First is that you will want to perform all your operations asynchronously, so this requires you to create delegates and program success and failure methods. Second, since your code runs on a background thread, you will need to get on the user interface thread before you attempt to write to the UI. The easiest way to do this is to wrap your code with the BeginInvoke method of the Dispatcher object. This method guarantees that your code will run on the Silverlight UI thread. If you do not do this, you will receive a threading error. One other thing to remember is to 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 runtime.

The last bit you need to know about Silverlight is how to get your applications deployed. They need to run in a trusted area of SharePoint, which could be in a SharePoint library or in the ClientBin directory. For the ClientBin, the easiest way to get your code there 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 will learn about later, to create a feature that copies the file up to your SharePoint site using a Module with a File reference in your Elements manifest.

One thing to watch out for is caching of your Silverlight application while you are developing it. Make sure that an old version is not loaded by updating the AssemblyVersion and FileVersion in your AssemblyInfo file in VS. You may also have to clear your browser cache. Another recommendation is to change something in the UI to make sure that your application is the latest; this allows you to tell visually.

Also, if you are working across domains, you will 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 are working in the same domain, you do not have 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 will have to use the ClientAccessPolicy.XML file to allow those calls. Figure 4-22 shows the Silverlight application in action.

Figure 4-22

Figure 4.22. Figure 4-22

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);

            }

        }
    }
Figure 4-22

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 ADO.NET Data Services, formerly known as Astoria. If you don't know what REST is, 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. First, you will want to make sure you install the ADO.NET data services on the SharePoint 2010 Server where you are developing using REST. 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 ADO.NET Data Services technologies installed. Second, if you are connecting to your REST services for SharePoint from Internet Explorer (IE), you will want to turn off Feed Reading View in IE so that you get the raw XML returned from SharePoint. You can find this under Tools

Programming Using REST

The easiest way to get started 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 4-23. You will see the XML returned for all your lists in your site.

Figure 4-23

Figure 4.23. Figure 4-23

If you have never worked with REST before, there are two ways in SharePoint that you can return your data. First, there is ATOM, which returns XML and is a standard. Then, there is JavaScript Object Notation (JSON), which returns your data using JSON markup so that you can parse that data using JavaScript objects. JSON is good if you want to turn the returned data into Javascript objects. 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.

Since REST uses a standard URL-addressable format and uses 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 4-16 lists some examples of URL addresses.

Table 4.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

Since Visual Studio has built-in support for using ADO.NET Data Services, programming with REST starts with adding a service reference in your code. In this reference, point to your ListData.svc URL in _vti_bin. Please note that in the beta, you will have to change the reference VS adds from System.Data.Service.Client to Microsoft.Data.Services.Client by removing the reference and adding a new reference to %Program Files (x86)ADO.NET Data Services V1.5 CTP2in. Then, you will have to create new proxy classes by running in a command prompt DataSvcUtil.exe /uri:"http://URL/_vti_bin/ListData.svc" /out:Reference.cs. This will create a C# file that you will use to replace the existing Reference.cs in your project, which you will find in the file directory for your project, not in the user interface, unless you turn on Show All Files in Solution Explorer.

From there, you should see your lists in the Data Source window inside of Visual Studio, as shown in Figure 4-24. If you do not see your datasource in the window, right-click your service reference and select Update Service Reference. You will have to go back and change the System.Data.Services.Client reference again to Microsoft.Data.Services.Client.

Figure 4-24

Figure 4.24. Figure 4-24

From there, you can 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 datagrid 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 4-25.

Figure 4-25

Figure 4.25. Figure 4-25

You can drag and drop your datasource onto your form, and Visual Studio will create and bind a grid to your datasource. You can also use LINQ to program against your REST datasource. Figure 4-26 shows a databound grid against a SharePoint REST datasource.

Figure 4-26

Figure 4.26. Figure 4-26

Since the code for programming using REST is very similar to programming using the rest of the client OM, there is a quick example below of adding an item to your SharePoint list using REST. You will notice 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 ADO.NET Data Services and REST. If you look at your lists using REST, you will find that your external lists will not appear in your list results. This is a deficiency that you will have to 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, since REST supports putting out JSON objects that you can load with JQuery, as do other parts of SharePoint. While SharePoint itself does not include a JQuery library, you can easily link to JQuery in your SharePoint solutions. One thing to note is that 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.3.2.js"
type="text/javascript"></script>

SANDBOX SOLUTIONS

Often developers who want to build solutions on SharePoint can't, because they require administrator access to SharePoint and their solutions must be deployed as a full-trust solution, which could affect the stability of the server if they write bad code. For these reasons, IT administrators do not allow developers to write code against SharePoint 2007. With Sandbox Solutions in SharePoint 2010, the server administrator can allow site administrators to deploy code and developers to write code and still protect the integrity of the server. Sandbox Solutions are self-regulating, since there are quotas for resource usage and the server will shut down any solutions that exceed their quota.

Types of Solutions You Can Build

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

  • Content Types

  • Site Columns

  • Custom Actions

  • Declarative Workflows

  • Event Receivers

  • Feature Receivers

  • InfoPath Forms Services (not admin-approved, that is, without codebehind)

  • 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)

Executing Code in the Sandbox

Before a Sandbox Solution can be 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 Sandbox 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.

If you look at the architecture for Sandbox Solutions, there are three main components when executing your solution. First, there is the User Code Service (SPUCHostService.exe). This service decides whether the server where this service is running will participate in Sandbox Solutions. SharePoint has a modular architecture for Sandbox Solutions where you can run them on your WFEs or you can dedicate separate servers for executing your Sandbox code. If the User Code Service is running on a machine, Sandbox Solutions can run on that machine. When you troubleshoot your Sandbox Solutions, the first thing to check is to make sure that this service is running on a SharePoint server in your farm.

From an architecture standpoint, SharePoint allows 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 Web Front Ends (WFEs) in your farm. While this provides easy administration, since you do not have to create separate servers for Sandbox Solutions or remember which servers the service runs on, it does limit your scalability because the WFEs have to process other web requests while running the Sandbox 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 will route Sandbox Solutions to these servers rather than have the solution run on your WFE.

The second component and next process is the Sandbox Worker Process (SPUCWorkerProcess.exe). This is the process where your code executes. As you can tell, it is not part of w3wp.exe, which is one reason why you don't have 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 that is set on the sandbox.

The last component and process is the Sandbox Worker Proxy (SPUCWorkerProcessProxy.exe). Given that SharePoint has the service application architecture, this proxy allows Sandbox Solutions to tie into that infrastructure.

Subset Object Model

Sandbox does implement a subset of the Microsoft.SharePoint namespace. Sandbox Solutions do allow you to use full trust proxies to access other APIs or capabilities, for example, accessing network resources, but 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 common question you may be asking yourself is "If I can't access local resources such the hard drive on the server or network resources except for SharePoint, how do I get at external data like a database or Twitter or some other external datasource?" Well, you can use external lists and BCS in SharePoint to access external data, since Sandbox 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 quickly use the external lists in your Sandbox Solutions to read and write to that external data.

What about Iframes?

Sandbox 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 allows 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 Sandbox Solutions for this, rather than content editor web parts, makes the control reusable and easier to distribute.

Code Access Security (CAS)

You can find a lot of information about Sandbox Solutions under the UserCode folder in your SharePoint root. If you look at the web.config file located there, you will see that Sandbox Solutions are restricted by an OOB CAS policy. By default, you cannot access anything outside of the SharePoint object model. You should not modify these permission levels and instead should use full trust proxies to allow your sandbox code to perform allowed operations that it does not have by default. The exact permission levels are:

  • SharePointPermission.ObjectModel

  • SecurityPermission.Execution

  • AspNetHostingPermission.Level = Minimal

API Block List

Beyond the default CAS policy, SharePoint also implements an API block list. Imagine the scenario where you find some code exploiting your sandbox using a particular API from SharePoint. You may want to block API across your environment. This is where the API block list comes in. A new object was added to SPWebService called RestrictedObjectModel. This object contains a collection of restricted types that implements an add method, so you can add new API methods to block. For the add method, you need to pass in the type and method you want to block. For example, if you wanted to block the Update method of the SPWeb object you would call SPWebService.RestrictedObjectModel.RestrictedTypes.Add(typeof(SPWeb), "Update"). By default, nothing is blocked.

Visual Studio Support

One of the nice features of VS 2010 is that it supports Sandbox Solutions. When you create a new SharePoint project, for projects that support Sandbox Solutions, VS will give you the option of deploying your solution as a Sandbox Solution, as shown in Figure 4-27. In addition, VS will limit the API set in IntelliSense to just the APIs that work for Sandbox Solutions. VS does not do a compile-time check if you are using restricted APIs, since you program against the full SharePoint namespace and at runtime, your code is limited. So, if you ignore IntelliSense and write to APIs that are not supported in the sandbox, you will not get a compile-time error but instead will get a runtime error. One trick around this is to reference the Microsoft.SharePoint.dll under the Assemblies folder under the UserCode folder in the SharePoint hive. That will limit the APIs you can use. You MUST change back the reference to the full Microsoft.SharePoint.dll before deployment. This may be fixed in the released version of Visual Studio 2010.

Figure 4-27

Figure 4.27. Figure 4-27

Solution Monitoring

One of the powerful features of the sandbox is the monitoring. There are site collection quotas that administrators can set up for all Sandbox Solutions running in that site collection. These quotas stop the Sandbox Solutions from overloading the server, such as maxing out the CPU or pegging the database. Resource calculations are not instantaneous so that you may have to wait a bit during development to see the quota usage change. In addition, there is a daily timer job that aggregates server resource usage, resets any solutions that have exceeded their quota so that they can run again, and deletes old resource usage records.

Farm administrators decide how many resource points a site collection receives in Central Administration. If you look at the Configure Quota and Locks under Application Management in the section called User Solutions Resource Quota, you will see where a farm administrator can set the resource quota for Sandbox Solutions. This is shown in Figure 4-28.

Figure 4-28

Figure 4.28. Figure 4-28

Since the resource quota is for the site collection's Sandbox Solutions, one bad apple ruins the bunch. If Sandbox Solutions eat up all the resources, no Sandbox Solutions will run on that site collection for the rest of the day. So, it's a good idea to make sure that you're writing good code; otherwise, you could use all the resources, even those for other developers running in the same site collection.

What Is Monitored?

In terms of monitoring, SharePoint tracks 14 different counters and tries to normalize across them. For example, how many points should be a millisecond of CPU execution time as compared to the number of SharePoint database queries you make? How do you normalize across different counters to make an aggregate that makes sense? Well, it's easy to see how SharePoint attempts to do it. You can look at using PowerShell by using the SPUserCodeService object. The following PowerShell code returns all the counters measured by Sandbox Solutions:

$snapin = Get-PSSnapin | where-object { $_.Name -eq
 'Microsoft.SharePoint.PowerShell' }
if ($snapin -eq $null){
 write-host "Loading SharePoint PowerShell Snapin..." -foregroundcolor Blue
 add-pssnapin "Microsoft.SharePoint.PowerShell"
 write-host "SharePoint PowerShell Snapin loaded." -foregroundcolor Green
}

[Microsoft.SharePoint.Administration.SPUserCodeService]::Local.ResourceMeasures

# The ReadKey functionality is only supported at the console (not is the ISE)

if (!$psISE)

{

   Write-Host -NoNewLine "Press any key to continue. . . "

   $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

   Write-Host ""

}

write-host "Completed Run" -foregroundcolor Blue
What Is Monitored?

If you bubble up this list, you get the counters and metrics in the bulleted list below. The way to read the list is as the type of counter and then how many counts must occur or the time to count as a single resource point. For example, if you make 20 SharePoint database calls through your calls to different SharePoint APIs, that counts as one resource point.

  • AbnormalProcessTerminationCount: 1

  • CPUExecutionTime: 3600

  • CriticalExceptionCount: 3600

  • InvocationCount: 100

  • PercentProcessorTime: 85

  • ProcessCPUCycles: 100000000000

  • ProcessHandleCount:10000

  • ProcessIOBytes: 10000000

  • ProcessThreadCount: 10000

  • ProcessVirtualBytes: 100000000

  • SharePointDatabaseQueryCount: 20

  • SharePointDatabaseQueryTime: 120

  • UnhandledExceptionCount: 50

  • UnresponsiveprocessCount: 2

The counters are customizable in that you could bump them up or down using the object model any of the counters. For example, if you wanted to allow 40 database calls, you could change that by using the SharePoint object model. However, it's a little like changing search relevancy algorithms yourself; it may cause unintended consequences, so try the default restrictions to see if they meet your needs before you modify them.

Also, there are absolute limits. Absolute limits will terminate a solution even if the resource limits have not been hit. For example, UnresponsiveprocessCount has an absolute limit of 1. This means that, if your Sandbox Solution is not responding, it will be terminated immediately. Then, two points will be added to the aggregate. The solution could try to run again, but it will be terminated if it becomes unresponsive and again two resource points will be added to the aggregate.

If there are solutions that just keep breaking, administrators can block them from running either by using the object model or, more easily, by selecting the solution by browsing for it and putting in a message that tells the user why the solution can't run.

Managing Solutions

When it comes to managing your solutions, there are two key areas to take a look at beyond monitoring. One is solution validation, which allows you to be proactive in validating solutions. You decide what to check on the solution and, if it fails validation, it is not allowed to be activated in the site collection. The second is full-trust proxies. These proxies allow Sandbox Solutions to call more functionality than what the sandbox provides, but in a managed way.

Solution Validation

To work with solution validation, you just need to inherit from the SPSolutionValidator class. Once you write your code for this class, you add it to the SPUserCodeService SolutionValidators collection using either the API or PowerShell. Following is sample code for the solution validator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.UserCode;
using System.IO;


namespace SolutionValidation
{
    [Guid("7158c574-9881-42b3-9116-2575485af534")]
    public class SolutionValidator : SPSolutionValidator
    {

        //Create constant to pass to constructor
        private const string solutionValidatorName = "Solution Validator";
        //Create constant for Validator Signature
        private const int sigValue = 1;


        public SolutionValidator()
        {
            //Blank Constructor
        }


         public SolutionValidator(SPUserCodeService userCodeService)
            :base(solutionValidatorName, userCodeService)
       {
           this.Signature = sigValue;

       }

         public override void ValidateSolution(
SPSolutionValidationProperties properties)
         {
             //Validate your solution such as checking the code files in
the solution
             foreach (SPSolutionFile solutionFile in properties.Files.Where(
                f => String.Equals(
                        Path.GetExtension(f.Location), ".dll",
                            StringComparison.InvariantCultureIgnoreCase)))
             {
                 //Perform Validation of files
             }

             base.ValidateSolution(properties);

             properties.Valid = false;
properties.ValidationErrorMessage = "Illegal Solution";
             properties.ValidationErrorUrl =
"/_layouts/SolutionValidation/SolutionValidationError.aspx";

         }

         public override void ValidateAssembly(
SPSolutionValidationProperties properties, SPSolutionFile assembly)
         {
             //You can open the assembly using OpenBinary
             base.ValidateAssembly(properties, assembly);
             properties.Valid = true;
         }
    }
}
Solution Validation

A couple of things in the code: First, you can see where the code inherits from the SPSolutionValidator class. In addition, you need to create your own GUID to assign to your class, since you will use that GUID when you remove the solution validator from SharePoint in your feature deployment.

From there, you can see the blank constructor and another constructor that overloads the base constructor to assign a signature value to your validator. If you update the validator, you will want to update the signature number.

For the implementation, the code has ValidateSolution and ValidateAssembly methods. The ValidateSolution method gets passed a SPSolutionValidationProperties collection, which contains a number of properties for your SharePoint solutions, such as the files in the solution. You can perform validation in this method and, if the solution is valid, set the Boolean Valid property to True. If the solution is not valid, set the Valid property to False. You can also specify the ValidationErrorMessage and ValidationErrorUrl, which is the message to display and, if you want, the URL of the error message page. You can use the mapped folder feature in Visual Studio to add a custom ASP.NET page to your Layouts folder to display your error message.

For the ValidateAssembly method, you can validate individual assemblies in the solution. You get the assembly, and you can open it using the OpenBinary method, write all the bytes, and look at the bits contained in it. If the assembly is valid, set the Valid property to True.

Once you have your implemented class, you need to create a feature that deploys your solution validator. It must be a farm-level solution, since you want to validate all solutions in your farm. As part of the feature, you will want to write code for your feature event receiver to turn on your solution validator and turn it off when the feature is activated. The following code does this. Note how the same GUID is used in the FeatureDeactivating event that you used to mark up your class. Figure 4-29 shows the solution validator in action, denying a solution the right to activate.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            //Add solution Validator using our class name
             SPUserCodeService.Local.SolutionValidators.Add(
new SolutionValidator(SPUserCodeService.Local));

        }



        public override void FeatureDeactivating(
SPFeatureReceiverProperties properties)
        {
            //Remove our solution validator using our GUID
            SPUserCodeService.Local.SolutionValidators.Remove(new Guid(
"7158c574-9881-42b3-9116-2575485af534"));

        }
Figure 4-29

Figure 4.29. Figure 4-29

Full-Trust Proxy

The last area we will look at is creating full-trust proxies. Full-trust proxies allow you to extend Sandbox Solutions without throwing everything out of the sandbox. For example, you may need to access a network resource to get at data but not want to make the full solution a fully trusted solution; instead you want to provide a limited proxy to just that network resource. With full-trust proxies, you can provide an API just to the network resource and allow your Sandbox Solutions to call through that API to your resource.

To create a full-trust proxy, you need to implement a class that inherits from the SPProxyOperation class. Then, you need to figure out the arguments that you want passed to your proxy by creating a serializable class that inherits from the SPProxyOperationsArgs class. Once you have done this, generate the DLL and put this DLL into the global assembly cache (GAC). Then, you can register the DLL with SharePoint and call it using the SPUtility.ExecuteRegisteredProxyOperation method.

The following code creates a class that mimics accessing a network resource. It passes a fake username and password, and the full-trust proxy returns an array of data. You could imagine that you make a call to ADO.NET or other data access technologies to perform a real database call. Notice in the code that you need to create a serializable class that implements the proxy arguments. The calling Sandbox Solution then displays the data from the datasource that is accessed by using the full-trust proxy.

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

namespace FullTrustProxyDLL
{
    public class AccessDatabase : SPProxyOperation
    {
        public override object Execute(SPProxyOperationArgs args)
        {
            if (args != null)
            {


                ProxyArgs proxyArgs = args as ProxyArgs;

                //Get the user name
                string userName = proxyArgs.userName;

                //Get the password
                string password = proxyArgs.password;

                //Access the datasource here
                string[] results = { "A", "B", "C" };


                return results;
            }
            else return null;

        }

    }

    [Serializable]
    public class ProxyArgs : SPProxyOperationArgs
{
        public string userName { get; set; }

        public string password { get; set; }

    }
}

In order to make your proxy callable from partially trusted code applications, you need to add a line to your AssemblyInfo.cs, as shown here:

//Allow partially trusted callers
[assembly: System.Security.AllowPartiallyTrustedCallers]

Once you have compiled your proxy DLL, you need to register it in the GAC. Once that is done, you can register it with SharePoint. The following PowerShell script performs this step:

$snapin = Get-PSSnapin | where-object { $_.Name -eq
 'Microsoft.SharePoint.PowerShell' }
if ($snapin -eq $null){
 write-host "Loading SharePoint PowerShell Snapin..." -foregroundcolor Blue
 add-pssnapin "Microsoft.SharePoint.PowerShell"
 write-host "SharePoint PowerShell Snapin loaded." -foregroundcolor Green
}


$userCodeService = [Microsoft.SharePoint.Administration.SPUserCodeService]::Local
$assemblyName = "FullTrustProxyDLL, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=3098f04d94800e33"
$typeName = "FullTrustProxyDLL.AccessDatabase"
$proxyOperationType = new-object -typename
 Microsoft.SharePoint.UserCode.SPProxyOperationType -argumentlist
$assemblyName, $typeName
userCodeService.ProxyOperationTypes.Add($proxyOperationType)
$userCodeService.Update()

Now you can use the proxy in your Sandbox Solution. The following code and Figure 4-30 below shows the full trust proxy being used and how to call the proxy class from your sandbox code:

ProxyArgs proxyA = new ProxyArgs();
            proxyA.userName = "TestUser";
            proxyA.password = "Password";

            string assemblyName = "FullTrustProxyDLL, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=3098f04d94800e33";
            string typeName = "FullTrustProxyDLL.AccessDatabase";

            accessDatabaseButton.Click += (object sender, EventArgs e) =>
            {
                string[] results;
                results = (string
[])SPUtility.ExecuteRegisteredProxyOperation(assemblyName, typeName, proxyA);
lbl.Text = "First result: " + results[0];
            };

            Controls.Add(accessDatabaseButton);
            Controls.Add(lbl);
Figure 4-30

Figure 4.30. Figure 4-30

SUMMARY

In this chapter, you have seen how you can use the base services in SharePoint to integrate your applications into the new Ribbon user interface, write event receivers, and even work with the new client OM in your various applications. The SharePoint 2010 platform is a large one with many APIs, so it will take some time for you to absorb all the new APIs. Experimenting with the new APIs is the key to understanding how they work and what their limitations and trade-offs are.

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

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