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
  • Using the new object models, such as LINQ and the client object model
  • Programming using the new Sandbox Solutions architecture

The SharePoint 2010 platform is large. 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. 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.

images

FIGURE 4-1

The SharePoint surface area is huge, because 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, because 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 is downloaded and parsed only when the CSS class is needed for the particular HTML rendering.

The same thing goes for the JavaScript (JS) files in SharePoint. Before, you had 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 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. You need to add <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.Utilitylnternal.CreateButton, it 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 reduces 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 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.

images

FIGURE 4-2

The _layouts pages uses 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 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 necessary to support your Master Page by using the AlternateCSSUrl 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 were 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 provides you 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 box using the new dialog framework without chrome and without a Ribbon. You don't have to do anything to get this functionality if you use V4.Master, because 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 makes 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 stripped down 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 discussed earlier, and it cannot be customized.

The Ribbon

One of the major changes you 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 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 to avoid confusing 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 drop-down 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 placed 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 uses 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, so any custom actions you created for 2007 toolbars appear in a custom commands tab in the Ribbon.

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

The architecture of the Ribbon 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, consider using a JavaScript page component. The next section looks at both options.

If you want to understand how SharePoint implements its Ribbon XML elements, go to %Program Files%Common FilesMicrosoft SharedWeb Server Extensions14TEMPLATEGLOBALXML on your 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

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 learn about later in this chapter, so they can run in a restricted environment.

After you create 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 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:

images

<?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>

images

First, all XML for the Ribbon will be wrapped in a CustomAction node. This tells SharePoint that you want to perform customization of the user interface. For the node, there are attributes that you can set to specify the specifics for your customization. One key one is the Location attribute, which 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 RegistrationlD. When combined, the registration ID and the registration type define the set of content you want your custom UI to appear for. The registration type can be a list, content type, file type, or a proglD. 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 — 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.

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

The CommandUIDefinition element has an attribute that 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 not to replace a control, but instead add this as a child control on that user interface element.

The CommandUIDefinition element is where you create your user interface elements, whether they are tabs, groups, or individual controls. In this simple example, you create a button that has the label Click me!, 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 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 wrap your code in a CommandUIHandler, where you can put in the CommandAction attribute, which is inline JavaScript that handles the action for your button. If you do not want to place your JavaScript inline, you can use the ScriptSrc attribute and pass a URL to your JavaScript file. Figure 4-3 shows the new custom button on the top-left side of the Documents Ribbon.

images

FIGURE 4-3

images

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

images

Replacing Existing Controls

At times you may want to replace existing, built-in controls from your SharePoint deployment and put your own controls in place. 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 point is you have to get the Location attribute set to the same ID as the control ID you want to replace.

The following code replaces the new folder button for a document library. There are a couple of things to highlight in this code. First, notice the Location attribute in the CommandUIDefinition element. It maps exactly to an ID in the cmdUl.XML file. SharePoint parses both files and if the same ID is found, the one with the lower sequence is placed 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. The format map on your server contains lots of icons and the XML code contains the coordinates to pull the new folder icon from the larger image.

images

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

images

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

images

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

images

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

images

Why Doesn't Your Button Show Up?

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

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 of your function is the same as your command attribute on your control definition and your CommandUIHandler. If you get the name wrong, even with the same spelling but different cases, your commands don't fire.

Check the registration for your CustomAction. Did you register your UI on a document library? When you test your code, are you in a document library? Or did you register on an edit form for announcements? 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 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 doesn't load any of the customizations for the toolbar.

Rights and Site Administrators

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

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

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

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

Writing Code to Control Menu Commands

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

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

Creating New Tabs and Groups

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

images

FIGURE 4-5

The following code shows the beginning of the new tab and group. As you can see, the XML looks very similar to the earlier XML in creating a button. This code also defines a tab that you learn more about shortly.

<!--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. You need to know a couple of things about the Scaling node. First, it has a GrouplD attribute, which should point to the group that the scaling affects. 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. A group can have commands, descriptions, and all the standard attributes that other controls have. A group is a logical container for your controls and physically lays out the controls in your group with your description at the bottom of the group user interface. Your Group node contains the definition of your controls that live within that group.

<Groups Id=“Ribbon.CustomTabExample.Groups”>
              <Group
                Id=“Ribbon.CustomTabExample.CustomGroupExample”
                Description=“This is a custom group!”
                Title=“Custom Group”
                Sequence=“52”
                Template=“Ribbon.Templates.CustomTemplateExample”>
          <Controls>
…

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

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

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

images

<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=453 6”
                        Image16by16Top=“−48”
                        Image16by16Left=“−112”
                        Image32by32=“/_layouts/$Resources:core,Language;
                        /images/formatmap32x32.png?vk=453 6”
                        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=453 6”
                         Image32by32Top=“−3 84”
                         Image32by32Left=“−3 52”
                         LabelText=“StaticComboButton2”
                         MenuItemId=“StaticComboButton2”/>
                      </Controls>
                    </MenuSection>
                 </Menu>
               </ComboBox>

images

After your controls, you can handle the commands that your controls need to respond to in the 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 on wrox.com.

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 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=“Ctr1-V, P”

ToolTipImage32by32=“/_layouts/images/PasteHH.png”

ToolTipHelpKeyWord=“WSSEndUser”

Writing a Page Component

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

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

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

After 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.

Notice 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 whether any items are selected in the user interface so that you can enable or disable your control. You can go a step further and look for particular properties or item types by writing some more code.

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 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 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 is 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.

images

FIGURE 4-6

images

<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=453 6”
          Image16by16Top=“−80”
          Image16by16Left=“0”
           Image32by32=“/_layouts/$Resources:core,Language;
          /images/formatmap32x32.png?vk=453 6”
          Image32by32Top=“−9 6”
          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>

images

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.

images

FIGURE 4-7

Contextual Tabs and Groups with Web Parts

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

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

images

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

images

Then, you need to implement a custom page component using JavaScript. This is very similar to the code you saw earlier in the chapter where you add and register the custom page component. The code uses the executeOrDelayUntilScriptLoaded command, which is part of the SharePoint infrastructure to load and run the 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>”;
          }
      }

images

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.

images

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

images

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

protected override void OnPreRender(EventArgs e)
        {
           base.OnPreRender(e);
    
          //RegisterDataExtensions; add Ribbon XML for buttons
          this.AddCustomTab();

          ClientScriptManager csm = this.Page.ClientScript;
          csm.RegisterClientScriptBlock(this.GetType(),
             “custompagecomponent”, this.DelayScript);
        }

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

images

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

images

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. You should use both to give your users 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 tells you that only you can see the page. The status bar is for more permanent information, whereas the notification area is similar to instant message 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 client- and server-side code. The message you can deliver in the bar is HTML, so it can be styled and contain links and images. In addition, the bar can have four preset colors, depending on the importance of the message. To work with the status bar, you want to use the SP.UI.Status class. It is a pretty simple client-side API, since it contains only six methods, and you can find their definitions in SP.debug.js. On the server side, you should use the SPPageStatusSetter class, which is part of the Microsoft.SharePoint.WebControls namespace. That API is even simpler 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 methods for the client side.

TABLE 4-3: SP.UI.Status Methods

NAME DESCRIPTION
addStatus 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 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' meanings, such as green for good or red for bad. You need to pass the status ID of the status you want to change color and one of four color choices: red, blue, green, or yellow.
removeStatus Removes the status specified by the status ID you pass to this method from the status bar.
removeAllStatus Removes all status messages from the status bar. You can pass a Boolean that specifies whether to hide the bar. Most times, you 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.

images

FIGURE 4-8

images

<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/>

images

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 only create and remove notifications. Table 4-4 describes the methods for SP.UI.Notify that work with notifications, with sample code following the table.

TABLE 4-4: SP.UI.Notify

NAME DESCRIPTION
addNotification Allows you to pass your HTML payload, whether the notification is sticky or not (which when set to false means the notification disappears after five 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.

images

<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”/>

images

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.

images

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 it. Plus, the dialog can be dragged to other parts of the browser window and can be maximized to the size of the browser window.

If you look in SP.UI.Dialog.debug.js, you 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 calls back to when the dialog is closed. You create a delegate to this function for this option with the createDelegate function in JavaScript.

Now that you know the options you can pass to the showModalDialog function, programming a dialog is straightforward. A couple of tips before you look at the code. 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.getLayoutsPage Url(‘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 respects that.

The following code contains the OpenDialog function. As part of this function, a variable called options is created, which uses the SP.UI.$create_DialogOptions method. This method returns a DialogOptions object that you can 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.

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

images

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

images

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. Note that the code contains two buttons for OK and Cancel. The onclick event handlers for the buttons use methods from the window.frameElement object. By using this object, you can get methods from the dialog framework. commitPopup returns OK, and cancelPopUp returns Cancel as the result of your dialog. Table 4-6 shows the methods you want to use from the frameElement.

images

<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” />

images

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

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 Extensions14TEMPLATELAY0UTS1033STYLESThemable 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: “Lightl”, darkThemeColor: “Darkl”)] */
   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 that used different elements than the ones specified is selected.

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.

images

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, /* [Re placeColor(themeColor:“Light2” {lighter: 0.2})] */ background-color: #f5f6f7;
ReplaceFont(string For 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 works only for background-image. The grayscaleImageURL specifies a grayscale image that needs to be colorized. For example, /* [RecolorImage(lightThemeColor: “Lightl”, darkThemeColor: “Darkl”)] */ background-image: url(“../images/bl_Navbar_Gd_Default.jpg”); If you want to recolor the image by blending, filling, or tinting it, you <sup> </sup> 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, including web parts, support themes, 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 aren't themable by the engine. Instead, if you use CSS files and appropriately mark up those CSS styles using the theme attributes, 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 get better user interface integration by not having your application ignore the theme when it changes in the SharePoint product.

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 following 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 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.
AccentColorl

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, DarkColorl, HyperlinkColor, and LightColorl, 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.0pen(
                 “C;/sup>c:\users\administrator\desktop\test.thmx”,FileMode.0pen);


               ThmxTheme newtheme = ThmxTheme.0pen(
                 fs,FileMode.0pen, 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.

images 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 options, depending on what you are trying to do. If you want bare-bones, down to the metal, XML-style creation of lists, Visual Studio is your best bet. If you would rather work with a GUI, SPD provides a nice interface to work with your lists, whether it is creating columns or views, or customizing your list settings. Of course, you can use the built-in list settings in SharePoint to work with your lists, but SPD is a better choice if you are interested in a GUI editor.

SPD makes it easy to work with your lists, whether it's creating new lists or modifying existing ones. SPD can make working with your columns, views, forms, content types, workflows, and even custom actions for your list easier. If you need to rapidly create a list or list definition, SPD is going to be the fastest and easiest way to work with your SharePoint lists. You 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.

images

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.

images 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 sites. If you need ultimate flexibility, VS is your tool of choice for creating list definitions and customizing lists.

List Relationships with Cascade or Block

One common complaint about SharePoint is that it doesn't behave like a relational database. For example, if you have a lookup between two lists and you want to have some referential integrity, SharePoint previously would not block or cascade your deletes between your lists. With 2010, SharePoint 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, including additional properties from the list such as built-in or custom fields. On the list where you create your lookup, you can enforce the relationship behavior to either restrict deleting parent list items if items exist in the list 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.

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

images

FIGURE 4-12

images

FIGURE 4-13

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

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

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

The two properties affect relationships when you use them with the SPWebApplication class. 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 1,000 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 an SPRelatedFieldCollection collection. From this collection, you can iterate through each related field. From there, you can retrieve properties, such as the LookupList that the field is related to, the ListID, the FieldID, or the relationship behavior when something is deleted from the list.

     using (SPSite site = new SPSite(“http://intranet.contoso.com”))
     {


         SPList list = site.AllWebs[“”].Lists[“Orders”];
         SPRelatedFieldCollection relatedFields = list.GetRelatedFields();
         foreach (SPRelatedField relatedField in relatedFields)
         {
             //Lookup the list for each

             SPList relatedList = relatedField.LookupList;
             MessageBox.Show(relatedField.ListId + “ “ +
               relatedField.FieldId);
             //MessageBox.Show(“List Name: “ +
               relatedList.Title + “ Relationship Behavior: “ +
               relatedField.RelationshipDeleteBehavior.ToString());


         }
 }

Validation with Excel-Like Formulas

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

images

FIGURE 4-14

images

FIGURE 4-15

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

Ensuring Uniqueness

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

List Joins

Just like a database, SharePoint supports list joins. Again, SharePoint won't provide as much functionality as a relational database, 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, is returned in the result set.

The following code performs a join across two lists on a lookup field. You need to set the Joins property on your SPQuery object with the join you want to perform. In the code, you 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. When you get your results, you have to use the SPFieldLookupValue object to display the values for your projected fields.

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

                        SPFieldLookupValue(Result[“CustomerAddress”].ToString());

                   MessageBox.Show(Result.Title + “ ” + CustomerTitle.LookupValue + “
                      ” + CustomerAddress.LookupValue);


                   }

Customize Default Forms Using Web Parts or InfoPath

One of the new features of lists is that you can customize the default forms for your list items. SharePoint 2010 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 is automatically launched. Connect to your list, and display your form, as shown in Figure 4-16.

images

FIGURE 4-16

View Enhancements

The biggest change with views in 2010 is the technology used to display views. 2010 uses the SharePoint Designer XsltListViewWebPart as the default 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.

images

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 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 returns the HTML that your view renders. 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 events that were introduced in SharePoint 2007, such as ItemAdding, ItemUpdating, and ItemUpdated. There are also other enhancements beyond new events, including new registration scopes to support the new events, new tools support in Visual Studio, support for post-synchronous events, 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 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. After you finish the wizard inside of Visual Studio, you can modify your feature definition or your code using the standard Visual Studio SharePoint tools. Plus, with on-click deployment and debugging, it's a lot easier to get your receiver deployed and start debugging it.

images

FIGURE 4-18

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

images

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)
            { }
        }
   }
}

images

New Event Registration Feature

To support the new events, SharePoint now provides a new mechanism for registering your event receivers, using the <Receivers> XML block. This new feature enables you to register your event receiver at the site collection level by using the new Scope attribute and set 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 works across all sites in your site collection, as long as your feature is registered across all these sites as well. You can tell SharePoint to have the receiver work on only the root site by using the RootWebOnly attribute on the <Receivers> node. The last new enhancement is the ListUrl attribute, which 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 over what the users see, and you can try to help them figure out why their action is failing. The custom error pages and redirection work only for pre-synchronous events, so you cannot do this for post-synchronous events such as ListAdded. Plus, this only works only with browser clients. Office just puts 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.

images

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.0penWeb())
                 {
                    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)
            { }
        }

images

OVERVIEW OF DATA TECHNOLOGIES

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

images

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 parses your lists and generates the necessary classes 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 performing common tasks that you will want to do. It does support XML customization, but most of the time you don't 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 for the generated code. The value for this can be either csharp or vb. SPMetal can look at your code parameter and infer the language you want by the extension.
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 uses the client object model if you specify this parameter.
User Allows you to specify the DOMAINusername, such as /user:DOMAINusername, that SPMetal runs as.
Password The password that SPMetal uses to log on as the user specified in the /user parameter.
Serialization Specifies whether you want your objects to be serializable. By default, this parameter is none, so they are not. If you specify unidirectional, SPMetal 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 3,000 lines long! You will definitely want to use SPMetal to generate this code and tweak SPMetal to meet your requirements.

      /// <summary>
      /// Use the Announcements list to post messages on the home page of your
      /// site.
      /// </summary>
      [Microsoft.SharePoint.Linq.ListAttribute(Name=“Announcements”)]
      public Microsoft.SharePoint.Linq.
       EntityList<AnnouncementsAnnouncement> Announcements {
             get {
                return this.
                 GetList<AnnouncementsAnnouncement>(“Announcements”);
            }
        }

/// <summary>
/// Create a new news item, status or other short piece of information.
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name=“Announcement”, Id=“0x0104”)]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute
(Type=typeof(AnnouncementsAnnouncement))]
public partial class Announcement : Item {

       private string _body;

        private System.Nullable<System.DateTime> _expires;

       #region Extensibility Method Definitions
       partial void OnLoaded();
       partial void OnValidate();
       partial void OnCreated();
       #endregion
    
       public Announcement() {
           this.OnCreated();
       }

       [Microsoft.SharePoint.Linq.ColumnAttribute(Name=“Body”,
         Storage=“_body”, FieldType=“Note”)]
       public string Body {
          get {
                  return this._body;
          }
          set {
                  if ((value != this._body)) {
                        this.OnPropertyChanging(“Body”,
                        this._body); this._body = value;
                        this.OnPropertyChanged(“Body”);
             }
          }
         }
    [Microsoft.SharePoint.Linq.ColumnAttribute(Name=“Expires”,
       Storage=“_expires”,FieldType=“DateTime”)]
    public System.Nullable<System.DateTime> Expires {
            get {
                   return this._expires;
    
            }
            set {
                    if ((value != this._expires)) {
                           this.0nPropertyChanging(“Expires”,
                           this._expires); this._expires = value;
                           this.0nPropertyChanged(“Expires”);
               }
           }
      }
}

What about Default Fields?

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

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

Adding References in VS

After you generate SPMetal code imported into VS, it's time to make sure you have the right references set up to use that code. You 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 adds all the necessary dependent LINQ assemblies to your project.

Working with the DataContext Object

The DataContext object provides the heart of your LINQ programming. Your DataContext object 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 generated 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.

When you have your DataContext object, you can start working with that object's methods and properties. DataContext contains 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 of the DataContext Object

NAME DESCRIPTION
GetList<T> Returns the list of the specified type, for example, GetList<AnnouncementsItems>(“Announcements”).
Refresh Refreshes the datasource.
RegisterList 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 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. This makes programming much cleaner and also allows you catch compile-time errors when working with your objects, rather than runtime errors.

Querying and Enumerating Data, and Inefficient Queries

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

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

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

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

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 previous query you wanted to select only orders that were more than $1,000 dollars, you would change the query to the following one:

var orderresults = from orders in context.Orders
                               where orders.Total > 1000
                               select orders;

For the next example, the query 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. After the new object is created, you can call the InsertOnSubmit method and pass the new object or the InsertAllOnSubmit method and pass a collection of new objects. Since LINQ works asynchronously from the server with a local cache, you need to call SubmitChanges after all modifications to data through LINQ.

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

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 if general exceptions occur. 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. After you decide what to do about the discrepancies, you can resolve the changes with the ResolveAll method. The ResolveAll method takes a RefreshMode enumeration, which can contain one of three values: KeepChanges, 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<0rdersItem> Orders =
             context.GetList<0rdersItem>(“0rders”);
           OrdersItem order = new 0rdersItem();
           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(“0riginal Value: “ + Fields.OriginalValue);
                sb.AppendLine(“Database Value: “ + Fields.DatabaseValue);
                sb.AppendLine(“Current Value: “ + Fields.CurrentValue);
                MessageBox.Show(sb.ToString());
            }
        }

        //Force all changes
        context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
        context.SubmitChanges();

    }   
    catch (SPDuplicateValuesFoundException duplicateException)
    {
        MessageBox.Show(“Duplicate value found: “ +
         duplicateException.Message);
    }
    catch (SPException SharePointException)
    {
        MessageBox.Show(“SharePoint Exception: “ +              SharePointException.Message);
    }
    catch (Exception ex)
    {
        MessageBox.Show(“Exception: “ + ex.Message);
    }
}

Inspecting the CAML Query

If you want to inspect the CAML query that LINQ is generating, you can use the Log property and set that to a TextWriter or an object that derives from the TextWriter object, such as a StreamWriter object. If you are writing a console application, the easiest way to set the Log property is to set it to the Console.Out property. From there, you can retrieve the CAML query that SharePoint LINQ 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><0rderBy
0verride=“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”>2147483 647</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 makes your queries perform better, since LINQ doesn't have the overhead of trying to track changes to the SharePoint objects. The way to turn off object tracking is to set the ObjectTrackingEnabled property to false on your DataContext object.

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

When to Use CAML and LINQ

There are still times when you should revert to using CAML directly. One scenario is when performance is paramount. LINQ makes CAML programming much easier, but no matter how LINQ is optimized, it 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 provides better performance in this scenario.

Tools: LINQPad

If you do not want to write your LINQ queries by hand, give the tool LINQPad a try. It makes writing your queries easier by giving you a graphical designer. You can find it at http://www.linqpad.net.

Managed Client OM

If you wanted to program on the client side in SharePoint 2007, you had in reality one API choice — web services API. Although 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 (for example, Windows Presentation Framework (WPF) or Silverlight); new technologies for programming in JavaScript, such as JSON; and the introduction of REST, moving from the web services API to a richer API was sorely needed in SharePoint. Welcome the managed client object model, which this chapter refers to as the client object model (OM).

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 get back the JSON response. Figure 4-19 shows the way the client object model works.

images

FIGURE 4-19

One principle of the client object model is to minimize network chatter. When working with the client OM, Fiddler is a key tool for helping you troubleshoot any issues, since the client OM batches its commands and sends them all at once to the server at your request. This minimizes the round trips and network bandwidth used by the object model and makes your application perform better.

In addition, you should write asynchronous code with callbacks when working with the client OM so that your user interface doesn't block when users perform actions.

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

images

FIGURE 4-20

There is also a difference in the namespaces provided by the .NET and ECMAScript object models. Since you extend the Ribbon using script, the ECMAScript OM has a Ribbon namespace, while the managed client OM does not. Plus, there is a difference in naming conventions for the foundational part of the namespaces. For example, if you wanted to access a site, in the .NET API you would use the Microsoft.SharePoint.Client.Site object, but in ECMAScript you would use SP.Site. Table 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

images

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 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 has redistributable versions of the .NET and Silverlight object models to install on your client machines. If you have Office 2010 installed on your machine, you don't need the redistributable, but if you don't, you can retrieve the 32-bit and 64-bit versions of the redist at www.microsoft.com/downloads/en/details.aspx?FamilyID=b4579045-b183-4ed4-bf61-dc2f0deabe47. Given the long URL, it is probably easier to search for “SharePoint client object model redist.”

Adding References Inside VS

Depending on the type of application you are writing, the way you reference the different client OMs varies. With WPF or WinForms, you use the VS Add Reference user interface to add a reference to the DLLs discussed earlier. From there, you can use the proper statements to leverage the namespaces in your code. The same process is true for Silverlight. Figure 4-21 shows adding a reference inside of Visual Studio.

images

FIGURE 4-21

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

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

Authentication

Before you write your first line of code, you need to understand the context your code will run in. With the client OM, by default, your code runs in the context of the Windows authenticated user. Since many web applications support forms-based authentication, the client OM supports this as well. You 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 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 finds the relative site or uses the current site as the site you want to open.

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

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

Table 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 demonstrated in the sample code throughout this section, you use the Load or LoadQuery method on the ClientContext object and then call the ExecuteQuery or executeQueryAsync method to execute your query. The rest of this section goes through the different programming tasks you perform with the client OM, to show you how to use it.

Retrieving Items from SharePoint

To retrieve your list items from SharePoint, 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();

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

//Load the List Collection
                ListCollection lists = context.Web.Lists;
                context.Load(lists);
                context.ExecuteQuery();

                MessageBox.Show(lists.Count.ToString());

                foreach (Microsoft.SharePoint.Client.List list in lists)
              {
                    MessageBox.Show(“List: “ + list.Title);
              }

Properties Returned and Requesting Properties

By default, SharePoint returns a large set of properties and hydrates your objects with these properties. For performance reasons, you may not want to have it do that if you are 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 let SharePoint retrieve all properties for you. This is similar to the best practice of not doing a SELECT * in SQL Server.

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

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  “http://intranet.contoso.com”);

                Web site = context.Web;
                context.Load(site, s => s.Title, s => s.ServerRelativeUrl);
              ListCollection lists = site.Lists;
              context.Load(lists, ls => ls.Include(l => l.Title));

              context.ExecuteQuery();



              MessageBox.Show(“Title: “ + site.Title + “ Relative URL: “ +
               site.ServerRelativeUrl);


              MessageBox.Show(lists.Count.ToString());

              foreach (Microsoft.SharePoint.Client.List list in lists)
              {
                MessageBox.Show(“List: “ + list.Title);
              }


              context.Dispose();

Load 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 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, such as the title and the type, for your list. Your ListCollection object has an Add method that you can call and pass your ListCreationInformation object to in order to create your list.

To create a field, use the Fields collection for your list and define the XML in the AddFieldAsXml property. This property takes your XML, a Boolean value that specifies whether to add the field to the default view, and AddFieldOptions, such as adding the field to the default content type.

After you add your field, you can create list items by creating a ListItemCreationInformation object, and passing it to the AddItem method, which returns a ListItem object representing your new item. Using this object, you can set the properties for your item. Make sure to call the Update method when you 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 should materialize your collection into a List<T> object, using the ToList method, so you can iterate through the list and delete without errors.

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  “http://intranet.contoso.com”);


            List list = context.Web.Lists.GetByTitle(“New List”);

            CamlQuery camlQuery = new CamlQuery();

            camlQuery.ViewXml = @”
                <View>

                   <Query>
                     <Where>
                         <IsNotNull>
                             <FieldRef Name=‘Title’ />
                         </IsNotNull>
                     </Where>
                  </Query>
              </View>”;
           ListItemCollection listItems = list.GetItems(camlQuery);

           context.Load(listItems, items => items.Include(item => item[“Title”]));
           context.ExecuteQuery();
           foreach (ListItem listItem in listItems.ToList())
           {
               listItem.Delete0bject();
           }
           context.ExecuteQuery();
           context.Dispose();

Working with Users and Groups

Another feature of the client OM, beyond enabling you to work with lists, libraries, and items, is that it gives you the ability to work with users and groups. The client OM includes the GroupCollection, Group, UserCollection, and User objects to make working with users and groups easier. Just as you iterate on lists and items, you can iterate on users and groups using these collections. The client OM also has access to built-in groups such as the owners, members, and visitors groups. You can access these 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, use the UserCreationInformation object and set the properties, such as, Title, LoginName, and others, on that object. 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 used to create items, the sample code that follows shows you how to query users and groups but not create users.

ClientContext context = new Microsoft.SharePoint.Client.ClientContext(
  “http://intranet.contoso.com”);

           GroupCollection groupCollection = context.Web.SiteGroups;
        
           context.Load(groupCollection,
               groups => groups.Include(
                   group => group.Users));
            
           context.ExecuteQuery();
        
           foreach (Group group in groupCollection)
           {
              UserCollection userCollection = group.Users;

              foreach (User user in userCollection)

            {
            MessageBox.Show(“User Name: “ + user.Title + “ Email: “ +
             user.Email + “ Login: “ + user.LoginName);
    }
}
//Iterate the owners group
Group ownerGroup = context.Web.AssociatedOwnerGroup;

context.Load(ownerGroup);
context.Load(ownerGroup.Users);
context.ExecuteQuery();
foreach (User ownerUser in ownerGroup.Users)
{
   MessageBox.Show(“User Name: “ + ownerUser.Title + “ Email: “ +
     ownerUser.Email + “ Login: “ + ownerUser.LoginName);
}
context.Dispose();

Working Asynchronously

All of the code shown so far is synchronous code running in a .NET client, such as a WPF, console, or Windows Forms application. You may not want to write synchronous code, even in your .NET clients, so your application can be more responsive to your users, rather than having them wait for operations to complete before continuing to use your application. ECMAScript and Silverlight are asynchronous by default, so you 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 calls when your code is done executing asynchronously. Then, you can do other work while you are polling to see if the asynchronous call is complete. When it's complete, 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 using the .NET object model. The main differences are that you use server-relative URLs for your ClientContext constructor and the ECMAScript object model does not accept LINQ syntax for retrieving items from SharePoint. Instead, you use string expressions to define your basic queries. Also, ECMAScript is always asynchronous, so you have to use delegates and create callback functions for the success and failure of your call into the client OM. The final piece, as shown in the code that follows, is that you need to reference the WebControls namespace from the Microsoft.SharePoint assembly, reference the ECMAScript client OM in SP.js or SP.debug.js using a SharePoint:ScriptLink control, 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.

images

<%@ 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>

images

Working in Silverlight

Working with Silverlight is also similar to working with the .NET client, except for two major differences. First is that you 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 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 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 Extensions14TEMPLATELAY0UTSClientBin directory for the core OM and the runtime.

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

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

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

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

Also, if you are working across domains, you need to understand how to create cross-domain policies using a ClientAccessPolicy.XML file that you host at the root of your website. If you 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.

images

FIGURE 4-22

images

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

images

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. 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, 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), turn off Feed Reading View in IE so that you get the raw XML returned from SharePoint. You can find this under Tools images Internet Options images Content images Feeds and Web Slices.

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.

images

FIGURE 4-23

REST offers two ways you can return your data in SharePoint. 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.

Because REST uses a standard URL-addressable format and standard HTTP methods, such as GET, POST, PUT, and DELETE, you get a predictable way to retrieve or write items in your SharePoint deployment. Table 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=3 0
Inline expansion (lookups) listdata.svc/Listname?$expand=Item

Using REST in Visual Studio

Because 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)AD0.NET Data Services V1.5 CTP2in. Then, you 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 creates a C# file that you 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 Sources 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 have to go back and change the System.Data.Services.Client reference again to Microsoft.Data.Services.Client.

images

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.

images

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.

images

FIGURE 4-26

Since the code for programming using REST is very similar to programming using the rest of the client OM, a quick example of adding an item to your SharePoint list using REST follows. Notice it uses a call to generate a context for the rest of your calls to leverage so that you can batch commands and send them to server when you need to. For adding, you call the specific AddTo method for your list, such as AddToAnnouncements. For updating and deleting, you use the UpdateObject and DeleteObject methods and pass in the object you want to delete, which is derived from your item type, such as AnnouncementItem.

RESTReference.HomeDataContext context = new RESTReference.HomeDataContext(
  new Uri(“http://intranet.contoso.com/_vti_bin/listdata.svc”));

       private void button1_Click(object sender, EventArgs e)
       {
           //Populate grid using LINQ
           context.Credentials = CredentialCache.DefaultCredentials;

           var q = from a in context.Announcements
               select a;

           this.announcementsItemBindingSource.DataSource = q;
       }
     
       private void button2_Click(object sender, EventArgs e)
       {
            //Add a new Announcement
            RESTReference.AnnouncementsItem newAnnounce = new
             RESTReference.AnnouncementsItem();

            newAnnounce.Title = “My New Announcement! “ + DateTime.Now.ToString();

            context.AddToAnnouncements(newAnnounce);
            context.SaveChanges();
       }

External List Support and REST

Unfortunately, external lists are not supported with the 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 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. Although 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.5.2.js”
   type=“text/javascript”></script>

Deploying Your jQuery Applications

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

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

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

jQuery Basics

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

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

Some other examples of jQuery functions are listed in Table 4-17. This is not the exhaustive list of jQuery functionality, so pick up a resource on jQuery to learn more.

TABLE 4-17: Basic jQuery Methods

EXAMPLE DESCRIPTION
$(‘#elementID’) Access any element.
$(‘#elementID’).append(‘value’); Append text to the element.
$(‘#elementID’).html(); Get or retrieve the HTML within an element.
$(‘#elementID’).val(); Get the value of inputs.
$(‘#elementID’).hide(); Hide the element.
$(‘#elementID’).show(); Show the element.
var divs = $(‘div’); Return all instances of element specified, in this case all divs.
$(‘#elementID’).addClass(‘className’); Add a CSS class to the element
$(‘#elementID’).removeClass(‘className’); Remove the specified class.
$(‘#elementID’).attr(‘attribute’); Retrieve the attribute.
$(‘table[class = “className”]’).each( function(){ $(‘#’ + this.id).show(); } ); Perform an action on multiple elements of the same class.

jQueryUI

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

Bringing it All Together

Rather than talk about programming with jQuery, you can take a look at a couple of examples here. The first example combines jQuery and the jQuery UI to add an accordion visual element that turns SharePoint announcements into a nicer visual representation. As part of this example, you will also learn that you can leverage the SharePoint client OM with jQuery so all your knowledge of the client OM moves easily into your jQuery solutions. Figure 4-27 shows the final solution hosted in SharePoint.

images

FIGURE 4-27

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

images

<!-- Load JQuery -->
<script src= “/SiteAssets/jquery/jquery-1.5.2.js” type= “text/javascript”></script>

<!-- Load JQuery UI -- >

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

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

           </div>
<script type=“text/javascript”>
    var allDocs;

    $(‘#btnDemo2’).click(function () {
        var ctx = new SP.ClientContext.get_current();
        var targetList = ctx.get_web().get_lists().getByTitle(‘Announcements’);
        var query = SP.CamlQuery.createAllItemsQuery();
        allDocs = targetList.getItems(query);
        ctx.load(allDocs);
        ctx.executeQueryAsync(Function.createDelegate(this, getAllItemsSuccess),
        Function.createDelegate(this, getAllItemsFailure));
     });


     function getAllItemsSuccess(sender, args) {
   
        var listEnumerator = allDocs.getEnumerator();

        while (listEnumerator.moveNext()) {

            $(‘#accordion’).append(‘<div><h3><a href=“#”>’ +
              listEnumerator.get_current().get_item(“Title”)
              + ‘</a></h3><div>’ + listEnumerator.get_current().
              get_item(“Body”) + ‘</div></div>’);
         }
     
        accordionContent();


     }
   
     function accordionContent() {
   
        $(“#accordion”).accordion({ header: “h3” });
     }
   
     function getAllItemsFailure(sender, args) {
        alert(‘Failed to get list items.  
Error: ‘ + args.get_message() +
        ’
StackTrace: ‘ + args.get_stackTrace());
    }
</script>

images

What about CSS3 and HTML5?

With all the excitement around CSS3 and HTML5, you may be wondering how either one plays in the SharePoint environment. Since both are browser-dependent, a lot of the answer depends on your browser, especially when it comes to HTML5. SharePoint itself does not leverage HTML5 but if you create content using HTML5 and place it in a SharePoint site, as long as you browse that content with an HTML5-compatible browser, your content should work seamlessly. To show you some CSS3 working inside of SharePoint, Figure 4-28 shows a Polaroid sample that takes images from a SharePoint picture library and turns them into draggable Polaroid images. The image URLs are retrieved using the client OM and the manipulation of the images leverages CSS3 and jQuery.

images

FIGURE 4-28

TIMER SERVICE APPLICATIONS

At some point, you may need to write timer service applications. For example, you may want certain tasks to occur on a scheduled basis. Writing timer applications in SharePoint 2010 is not much different than writing the same application in SharePoint 2007 except that the Visual Studio experience is much better. The following code shows a timer service that counts the numbers of items in a folder.

images

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


namespace SharePoint_2010_Timer
{
    class SharePointTimerLists : SPJobDefinition
    {

        //URL to the server
        private const string serverURL = “http://demo2010a:9999/sites/team/”;

        //URL to the folder to retrieve item count from
        private const string folderName = “Tasks”;
    
        // Default Constructor
        public SharePointTimerLists()
        {
        }

        // Second constructor that will list the name of the timer
        public SharePointTimerLists(SPWebApplication spWebApp, string jobName)
            : base(jobName, spWebApp, null, SPJobLockType.ContentDatabase)
        {
            this.Title = jobName;
        }
        //Create execute function for when timer fires
        public override void Execute(Guid targetInstanceId)
        {

            //Create
       
            using (SPSite siteCollection = new SPSite(serverURL))
            {

                using (SPWeb site = siteCollection.RootWeb)
                {
                    SPList spList = site.Lists[folderName];
                    //Get the count
                    int itemCount = spList.Items.Count;

                    //Write out the item count as a new item

                    SPItem item = spList.Items.Add();

                    item[“Title”] = “This list (not counting me :)) has “
                    + itemCount + “
                items item.Update();

             }
         }
     }
  }
}

images

To deploy the timer service, you have to write event receivers that remove the timer on subsequent installs. This is essential while you are developing your code; otherwise, you will have multiple versions of your timer service. In addition, the code registers the timer service to fire every five minutes.

images

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Administration;

namespace SharePoint_2010_Timer.Features.Feature1
{
     /// <summary>
     /// This class handles events raised during feature
     /// activation, deactivation, installation, uninstallation, and upgrade.
     /// </summary>
     /// <remarks>
     /// The GUID attached to this class may be used
     /// during packaging and should not be modified.
     /// </remarks>
     
     [Guid(“b793b43c-379d-44b1-bcdc-84a1ca369e1d”)]
     public class Feature1EventReceiver : SPFeatureReceiver
     {

        // Uncomment the method below to handle the
        // event raised after a feature has been activated.

    private const string jobName = “Timer Sample SharePoint Timer”;

    public override void FeatureActivated(
SPFeatureReceiverProperties properties)
       {
        SPWebApplication spWebApp = properties.Feature.Parent as
SPWebApplication;

          //try to remove old instances of our timer
          foreach (SPJobDefinition spJob in spWebApp.JobDefinitions)
          {
            if (spJob.Name == jobName)

            {
                   //Found it. Delete it.
                   spJob.Delete();
                   spJob.Update();
                }

                //Use our constructor to create a new instance
                SharePointTimerLists spTimerService = new
                  SharePointTimerLists(spWebApp, jobName);
                   
                spTimerService.Schedule = SPSchedule.FromString(“every 5
      minutes”);

                spTimerService.Update();
            }
        }

        
        // Uncomment the method below to handle the event raised
        // before a feature is deactivated.
        
        public override void FeatureDeactivating(SPFeatureReceiverProperties
         properties)
        {

           SPWebApplication spWebApp = properties.Feature.Parent as
            SPWebApplication;

           //Delete our timer definition
           foreach (SPJobDefinition spJob in spWebApp.JobDefinitions)
           {
              if (spJob.Name == jobName)
              {
                //Found it. Delete it.
                spJob.Delete();
                spJob.Update();
              }
           }
        }

images

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 full-trust solutions, 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 code-behind)
  • JavaScript, Ajax, jQuery, REST, or Silverlight applications
  • List definitions
  • Site pages (but not application pages with code-behind)
  • Web parts (but not visual web parts without the VS Power Tools installed)

Executing Code in the Sandbox

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

The Solution gallery contains all your 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.

The Sandbox Solutions architecture provides three main components to use 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 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 that this service is running on a SharePoint server in your farm.

From an architectural 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 WFEs in your farm. Although this provides for easy administration, because you don't 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 routes 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 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

Using a sandbox implements a subset of the Microsoft.SharePoint namespace. Sandbox Solutions allow you to use full trust proxies to access other APIs or capabilities, for example, accessing network resources, but out of the box (OOB) the following capabilities from the Microsoft.SharePoint namespace are supported:

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

What about Accessing External Data?

One question people commonly ask is “If I can't access local resources such 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 use the external lists in your Sandbox Solutions to read and write quickly 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

You can find a lot of information about Sandbox Solutions under the UserCode folder in your SharePoint root. The web.config file located there shows that Sandbox Solutions are restricted by an OOB Code Access Security (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 operations that are not approved 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 an 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 adds 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 want to block the Update method of the SPWeb object you call SPWebService.Restricted0bjectModel.RestrictedTypes.Add(typeof(SPWeb), “Update”). By default, nothing is blocked.

Visual Studio Support

VS 2010 supports Sandbox Solutions. When you create a new SharePoint project, for projects that support Sandbox Solutions, VS gives you the option to deploy your solution as a Sandbox Solution. In addition, VS limits the API set in IntelliSense to only those APIs that work for Sandbox Solutions. VS does not do a compile-time check to determine if you are using restricted APIs, because you program against the full SharePoint namespace and at runtime, your code is limited. So, if you ignore IntelliSense and write to APIs that are not supported in the sandbox, you don't get a compile-time error but instead 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 limits the APIs you can use. You must change back the reference to the full Microsoft.SharePoint.dll before deployment.

Visual Studio 2010 SharePoint Power Tools

One of the deficiencies inside of Visual Studio is that it doesn't let you use the visual design capabilities to create web parts in the sandbox. Instead, you have to hand code. The Visual Studio 2010 SharePoint Power Tools add this capability and provide pre-compilation support so that you can view any errors for types or members that are not allowed in a SharePoint sandbox environment. Figure 4-29 shows creating a Sandboxed visual web part in Visual Studio.

images

FIGURE 4-29

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, for example by maxing out the CPU or pegging the database. Resource calculations are not instantaneous, so you may have to wait a bit during development to see the quota usage change. In addition, a daily timer job 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-30.

images

FIGURE 4-30

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.

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:

images

$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

images

If you bubble up this list, you get the counters and metrics shown in the following bulleted list. 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 with 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 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:

images

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

images

A couple of things you should note 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 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 also should update the signature number.

For the implementation, the code has the ValidateSolution and ValidateAssembly methods. The ValidateSolution method gets passed an 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.

After 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 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-31 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”));
        
      }

images

FIGURE 4-31

Full-Trust Proxy

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. After 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; }

     }
  }

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]

After you have compiled your proxy DLL, you need to register it in the GAC. When 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”
   $proxy0perationType = new-object -typename
    Microsoft.SharePoint.UserCode.SPProxyOperationType -argumentlist
   $assemblyName, $typeName
   userCodeService.Proxy0perationTypes.Add($proxy0perationType)
   $userCodeService.Update()

Now you can use the proxy in your Sandbox Solution. The following code and Figure 4-32 show 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.ExecuteRegisteredProxy0peration(assemblyName,
                typeName, proxyA);

              lbl.Text = “First result: “ + results[0];
           };

           Controls.Add(accessDatabaseButton);
           Controls.Add(lbl);

images

FIGURE 4-32

MOBILE SOLUTIONS

The use of mobile devices is one of the fastest growing areas in computer usage. Mobile devices can be smartphones, tablets, or other types of devices that are not PCs. To support this growing device family, SharePoint 2010 provides a number of ways to work them. Choose your method of working with mobile devices based on the type of device you are targeting. For example, you can write native mobile applications for your devices or you can write server-side mobile adapters that will take your code and render it for specific mobile devices.

Writing native applications probably results in the best user experience but it requires you to write different applications for the Windows Phone, Android, Blackberry, iPhone, and all the other mobile devices out there. Mobile adapters, since they run on the server, are device-agnostic from a code standpoint but can render their output based on the mobile device differently. Of course, the output for a mobile adapter will be consumed through a mobile web browser, which may not provide as good an experience as that provided by a native mobile application. This section covers both methods so you can decide which one is best for your application.

Writing a SharePoint Mobile Adapter

When browsing your SharePoint from your mobile device, SharePoint optimizes the experience so that web parts without a mobile equivalent are stripped out of the browsing experience. Figure 4-33 shows a SharePoint site with some built-in and custom web parts in a PC browser. Figure 4-34 shows the same page rendered using the mobile version of the same site by adding the ?mobile=1 token to the end of the site URL. Please note that you can use the older /m token at the end of your URL for backwards compatibility, but ?mobile=1 has more functionality.

images

FIGURE 4-33

images

FIGURE 4-34

As you noticed in the mobile version, the custom web part is stripped out because it does not have an optimized mobile version. To render the custom web part, you need to write a mobile adapter that SharePoint can call to change your web part into an optimized mobile web part.

To create a mobile adapter for the web part, you just need to add a new class to the web part and have that class inherit from the Microsoft.SharePoint.WebPartPages.WebPartMobileAdapter class. This class provides a number of events and methods that you can program to customize your web part. For the most part, you override the CreateControlsForSummaryView and the CreateControlsForDetailsView methods. Summary view is displayed on the default render and details view is displayed when a user clicks the web part to get more details.

The other events you will want to look at are the Init, Load, PreRender, and Unload events. As their names suggest, these events occur when initializing and loading, and before rendering and unloading your web part. You can also override properties such as the SummaryViewTitleIconUrl, DetailViewTitleIconUrl, and TitleIconUrl to change the appearance of the icons for your web part.

The example here shows how to create a mobile adapter that leverages some of this functionality. First, look at the completed web part, as shown in Figure 4-35, which shows the summary view for the web part on the page. As the figure demonstrates, some text is produced when events for the web part are fired. Figure 4-36 shows the detailed view for the web part, which shows a list of all the SharePoint content when a user clicks on the web part to expand it.

images

FIGURE 4-35

images

FIGURE 4-36

This web part inherits from the WebPartMobileAdapter class and the web part overrides a number of properties and methods to implement its functionality. The following code shows some of these methods and properties in addition to the CreateControlsForSummaryView method, which is what SharePoint calls to render the web part's summary view.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.MobileControls;
using Microsoft.SharePoint.MobileControls;

namespace MobileWebPart.SharePoint.WebPartPages
{
   public class VisualWebPart1MobileAdapter :
     Microsoft.SharePoint.WebPartPages.WebPartMobileAdapter
   {
       string eventLog = “”;

       protected override void OnInitForMobile(EventArgs e)
       {

          eventLog += “nOnInitForMobile called at “ +
            DateTime.Now.ToShortTimeString();

            //base.OnInit(e); DON'T CALL THE BASE YOURSELF!
        }

        protected override void OnLoadForMobile(EventArgs e)
        {
            eventLog += “nOnLoadForMobile called at “ +
              DateTime.Now.ToShortTimeString();
            //base.OnLoadForMobile(e);
        }

         protected override void OnPreRenderForMobile(EventArgs e)
         {

            eventLog += “ OnPreRenferForMobile called at “ +
              DateTime.Now.ToShortTimeString();
            //base.OnPreRenderForMobile(e);
         }
         
         protected override void OnUnloadForMobile(EventArgs e)
         {
             eventLog += “ OnUnloadForMobile called at “ +
               DateTime.Now.ToShortTimeString();
             //base.OnUnloadForMobile(e);
         }

         protected override string SummaryViewPageUrl
         {
            get
            {
               return base.SummaryViewPageUrl;
            }
         }
         
         protected override void CreateControlsForSummaryView()
         {
            Image iconImage = this.CreateWebPartIcon(
              WebPartIconLink.LinkToDetailView);
            iconImage.BreakAfter = false;
            this.Controls.Add(iconImage);

            Label titleLabel = this.CreateWebPartLabel();
            titleLabel.BreakAfter = true;
            this.Controls.Add(titleLabel);
            
            Label wpTitle = new Label();
            wpTitle.BreakAfter = true;
            wpTitle.Text = “ Retrieved Title: “ + this.Control.Title.ToString();
            this.Controls.Add(wpTitle);

            Label wpEventLog = new Label();
            wpEventLog.BreakAfter = true;
            wpEventLog.Text = eventLog;
            this.Controls.Add(wpEventLog);
            

        }

In the CreateControlsForSummaryView, the code creates a number of new controls to place on the page. The controls you can create are from the System.Web.UI.MobileControls class such as a Label, List, or SelectionList. In addition, you can set properties on your web part such as an image to link to the detail view and the label for the web part. Because you are overriding this method, you need to create some of these icons and labels yourself.

The other code you see in the method is the code that creates a couple of labels to place into the summary view. Notice how the controls are added to the Controls collection for the web part.

For the details view, the following code has a similar sort of functionality. Since you are running in SharePoint, you also can use the SharePoint object model to interact with SharePoint in the context of the current user. In this case, the code gets the lists from the current site and puts them into a mobile List control.

images

protected override void CreateControlsForDetailView()
        {
            Image iconImage = this.CreateWebPartIcon(
             WebPartIconLink.LinkToSummaryView);
            iconImage.BreakAfter = false;
            this.Controls.Add(iconImage);

           Label titleLabel = this.CreateWebPartLabel();
            this.Controls.Add(titleLabel);
        
            System.Web.UI.MobileControls.List listMobile = new List();

            SPWeb mySite = SPContext.Current.Web;

            //Get the Lists
            SPListCollection lists = mySite.Lists;
            foreach (SPList list in lists)
            {
                //Add to drop down
                listMobile.Items.Add(list.Title);
            }

            System.Web.UI.MobileControls.Label labelURL = new Label();
            labelURL.Text = mySite.Url.ToString();
            this.Controls.Add(labelURL);
            this.Controls.Add(listMobile);
      }

images

Safe Controls and Editing the Compat.Browser File

When you are done writing your mobile adapter, you need to add your mobile adapter as a safe control because Visual Studio does this only for your web part. To add your mobile adapter, you need to edit your web.config file for SharePoint, which is normally located under drive:intetpubwwwrootwssvirtualdirectories80 if your SharePoint deployment is on the standard HTTP port 80. Add the following line to your safecontrols section. Make sure to replace the PublicKeyToken.

   <SafeControl Assembly=“MobileWebPart, Version=1.0.0.0, Culture=neutral,
   PublicKeyToken=4b2679045807bdd3”
  Namespace=“MobileWebPart.SharePoint.WebPartPages”TypeName=“*” Safe=“True”
  SafeAgainstScript=“False” />

Here's a tip on customizing Visual Studio to make retrieving the public key token a single-click operation. First, under the Tools menu, click the External Tools menu item. Add a new item using the Add button and use the setting shown in Figure 4-37. This adds a new menu item under Tools that calls sn.exe to retrieve and output the public key token, as shown in Figure 4-38.

images

FIGURE 4-37

images

FIGURE 4-38

Next, you configure the compat.browser file to use the mobile web part. Compat.browser is the file that SharePoint uses to determine which browsers and web parts should be used. If you do not configure this file, your web part will be stripped out like you saw earlier in this chapter. The compat.browser file is located in your SharePoint IIS directory in a directory called App_Browsers. Locate the controlAdapters at the top of the file and add the following code to the file. (Remember to change the PublicKeyToken to your mobile web part's public key token.)

<adapter controlType=“MobileWebPart.VisualWebPart1.VisualWebPart1,
MobileWebPart, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=4b2679045807bdd3”
 adapterType=“MobileWebPart.
SharePoint.WebPartPages.VisualWebPart1MobileAdapter,
MobileWebPart, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=4b2679045807bdd3” />

Rebuild and deploy your project and then browse your site with the ?mobile=1 token at the end of the URL. Your new mobile web part should appear and, when you click it, the detail view should display with a list of the contents of your SharePoint site.

Creating a Windows Phone 7 Application

In addition to writing a mobile adapter that works across all mobile browsers, you can also write native phone applications. In this section, you look at writing a Windows Phone 7 application but you could also write an Android or iOS application to run on non-Windows platforms. Since Windows Phone 7 supports Silverlight, I cover using the SharePoint Client Object Model and Silverlight on a Windows Phone 7 device.

Prerequisites for Windows Phone and SharePoint

The first things you download are the Windows Phone 7 Development Tools. These tools are supported only on Windows Vista and Windows 7. Also, note that the phone emulator shipped with these tools doesn't work in a virtualized environment so you need a native install of Windows Vista or Windows 7. Some people have hacked the install to work on a Windows Server but since SharePoint now supports Windows 7, you do not have to develop only on a server operating system.

Note that Windows Phone 7 does not currently support Windows authentication. This means that the Windows Phone 7 application will not authenticate against a SharePoint sites that uses Windows authentication (also known as NTLM authentication). You have to enable Basic or Claims authentication against your SharePoint site. Windows Phone 7 doesn't support Basic authentication over standard HTTP but requires HTTPS. If you try to use Basic authentication over HTTP in your applications, Windows Phone 7 doesn't pass the credentials. Do not get bit by this issue. No error is thrown; you will just not be authenticated.

Microsoft Expression Studio is not exactly a prerequisite, but it is very nice to have. Since you are designing Silverlight applications that run on Windows Phone 7, Expression Blend makes it easier to model your application and then you can import the design into Visual Studio to write your application logic.

Details of Writing a Windows Phone 7 Application

Let's dive right in and build a Windows Phone 7 application since many of the concepts you use were covered earlier in this chapter. The application you build is a simple task list application. The application uses the client object model to call into a SharePoint list and display the results on the Windows Phone 7 Silverlight application. The final sample is shown in Figure 4-39.

To get started with the application, since you are using Silverlight and databinding, you can use Expression Blend to model the application. With Expression Blend, you can easily design the application and add the databinding so that you don't have to do this manually inside of Visual Studio.

images

FIGURE 4-39

The first step for using Expression Blend is to create a sample dataset so that Expression Blend can databind to it. If you do not perform this step, you can manually databind your elements to your SharePoint data but it is much easier if you have Expression Blend do this for you.

The following code uses the SharePoint client object model to retrieve all the items in the list and writes it to a text file in an XML format. Remember that the different SharePoint data access methods produce and consume different XML formats. This XML file works with Silverlight applications but if you are using the REST services for SharePoint, you may need to restructure your XML.

images

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SP= Microsoft.SharePoint.Client;

namespace WindowsFormsApplication2
{
   public partial class Form1 : Form
   {
       public Form1()
       {
          InitializeComponent();
       }
       
       private void button1_Click(object sender, EventArgs e)
       {
          SP.ClientContext context = new
           SP.ClientContext(“http://intranet.contoso.com”);
          SP.Site siteCollection = context.Site;
          SP.ListItemCollection sampleListItems;
          SP.List sampleList = context.Web.Lists.GetByTitle(“Tasks”);

          SP.CamlQuery qry = new SP.CamlQuery();

          qry.ViewXml = “<View><RowLimit>5</RowLimit></View>”;
          sampleListItems = sampleList.GetItems(qry);

          context.Load(sampleListItems);

        
          context.ExecuteQuery();

          //Build the Sample XML Data

          
          StringBuilder SampleData = new StringBuilder();
          SampleData.Append(“<SharePointListItems>”);

          foreach (SP.ListItem item in sampleListItems)
          {
             SampleData.Append(“<ListItem>”);
             foreach (KeyValuePair<String, 0bject> entry in item.FieldValues)
             {
                SampleData.Append(“<” + entry.Key + “>” + entry.Value +
                 “</” + entry.Key + “>”);
             }
             SampleData.Append(“</ListItem>”);

              
          }
          SampleData.Append(“</SharePointListItems>”);
          System.I0.File.AppendAllText(“c:\TaskSampleData.xml”,
            SampleData.ToString());

        }
    }
}

images

The returned XML looks something like this:

  <SharePointListItems>
    <ListItem>
      <iD>1</ID>
      <ContentTypeId>0x0108001FD53D7D4366034680B02CC2F9855E32</ContentTypeId>
      <Title>Write Phone App</Title>
      <Modified>5/9/2011 5:13:16 PM</Modified>
      <Created>5/9/2011 5:13:16 PM</Created>
      <Author>Microsoft.SharePoint.Client.FieldUserValue</Author>
      <Editor>Microsoft.SharePoint.Client.FieldUserValue</Editor>
      <_HasCopyDestinations></_HasCopyDestinations>
      <_CopySource></_CopySource>
      <owshiddenversion>1</owshiddenversion>
      <WorkflowVersion>1</WorkflowVersion>
      <_UIVersion>512</_UIVersion>
      <_UIVersionString>1.0</_UIVersionString>
      <Attachments>False</Attachments>
      <_ModerationStatus>0</_ModerationStatus>
      <_ModerationComments></_ModerationComments>
      <instanceID></InstanceID>
      <Order>100</Order>
      <GUID>9df989fd-cd5e-41e7-ac5e-c72208b23280</GUID>
      <WorkflowInstanceID></WorkflowInstanceID>
      <FileRef>/Lists/Tasks/1_.000</FileRef>
      <FileDirRef>/Lists/Tasks</FileDirRef>
      <Last_x0020_Modified>2011-05-09T17:13:16Z</Last_x0020_Modified>
      <Created_x0020_Date>2011-05-09T17:13:16Z</Created_x0020_Date>
      <FSObjType>0</FSObjType>
      <SortBehavior>Microsoft.SharePoint.Client.FieldLookupValue</SortBehavior>
      <FileLeafRef>1_.000</FileLeafRef>
      <UniqueId>210c7187-fb8d-4160-97ec-8fabb2d9510f</UniqueId>
      <SyncClientId>Microsoft.SharePoint.Client.FieldLookupValue</SyncClientId>
      <ProgId></ProgId>
      <ScopeId>{1A981506-E02E-4E26-A5E5-B7FD51291F39}</ScopeId>
      <File_x0020_Type></File_x0020_Type>
      <MetaInfo></MetaInfo>
      <_Level>1</_Level>
      <_IsCurrentVersion>True</_IsCurrentVersion>
      <itemChildCount>0</ItemChildCount>
      <FolderChildCount>0</FolderChildCount>
      <Predecessors>Microsoft.SharePoint.Client.FieldLookupValue[]</Predecessors>
      <Priority>(2) Normal</Priority>
      <Status>In Progress</Status>
      <PercentComplete>0.1</PercentComplete>
      <AssignedTo>Microsoft.SharePoint.Client.FieldUserValue</AssignedTo>
      <TaskGroup></TaskGroup>
      <Body>
        <div>Need to write my phone app!</div>
      </Body>
      <StartDate>5/9/2011 7:00:00 AM</StartDate>
      <DueDate>5/12/2011 7:00:00 AM</DueDate>
    </ListItem>
    <ListItem>
      <iD>2</ID>
      <ContentTypeId>0x0108001FD53D7D43 6 6034680B02CC2F9855E32</ContentTypeId>
      <Title>Create Sample Data</Title>
      <Modified>5/9/2011 5:13:31 PM</Modified>
      <Created>5/9/2011 5:13:31 PM</Created>
      <Author>Microsoft.SharePoint.Client.FieldUserValue</Author>
      <Editor>Microsoft.SharePoint.Client.FieldUserValue</Editor>
      <_HasCopyDestinations></_HasCopyDestinations>
      <_CopySource></_CopySource>
      <owshiddenversion>1</owshiddenversion>
      <WorkflowVersion>1</WorkflowVersion>
      <_UIVersion>512</_UIVersion>
      <_UIVersionString>1.0</_UIVersionString>
      <Attachments>False</Attachments>
      <_ModerationStatus>0</_ModerationStatus>
      <_ModerationComments></_ModerationComments>
      <instanceID></InstanceID>
      <Order>200</Order>
      <GUID>a9470cd0-70d8-4284-b47f-fb70cce76f39</GUID>

      <WorkflowInstanceID></WorkflowInstanceID>
      <FileRef>/Lists/Tasks/2_.000</FileRef>
      <FileDirRef>/Lists/Tasks</FileDirRef>
      <Last_x0020_Modified>2011-05-09T17:13:31Z</Last_x0020_Modified>
      <Created_x0020_Date>2011-05-09T17:13:31Z</Created_x0020_Date>
      <FS0bjType>0</FS0bjType>
      <SortBehavior>Microsoft.SharePoint.Client.FieldLookupValue</SortBehavior>
      <FileLeafRef>2_.000</FileLeafRef>
      <UniqueId>aadfeaef-1aa8-4f29-9e89-0153783fef80</UniqueId>
      <SyncClientId>Microsoft.SharePoint.Client.FieldLookupValue</SyncClientId>
      <ProgId></ProgId>
      <ScopeId>{1A981506-E02E-4E26-A5E5-B7FD51291F3 9}</ScopeId>
      <File_x0020_Type></File_x0020_Type>
      <MetaInfo></MetaInfo>
      <_Level>1</_Level>
      <_IsCurrentVersion>True</_IsCurrentVersion>
      <itemChildCount>0</ItemChildCount>
      <FolderChildCount>0</FolderChildCount>
      <Predecessors>Microsoft.SharePoint.Client.FieldLookupValue[]</Predecessors>
      <Priority>(2) Normal</Priority>
      <Status>Deferred</Status>
      <PercentComplete>0.05</PercentComplete>
      <AssignedTo></AssignedTo>
      <TaskGroup></TaskGroup>
      <Body>
        <div></div>
      </Body>
      <StartDate>5/9/2011 7:00:00 AM</StartDate>
      <DueDate></DueDate>
   </ListItem>
</SharePointListItems>

When you have the sample data, you can start Expression Blend. You create a new Windows Phone Application, as shown in Figure 4-40.

Adding Your Sample Data

Once you are in Expression Blend, you need to connect your sample dataset to your application. To do this, click the Data tab to get to the data panel. In the panel, click the Create Sample Data icon on the right side. The icon looks like a database stack with a plus sign on it. In the menu that drops down, select Import Sample Data from XML and browse to the sample data XML file that the previous program created. You can rename the datasource to whatever name you want. When done, you should see something similar to what is in Figure 4-41.

images

FIGURE 4-40

images

FIGURE 4-41

Next, you create your form. In the included sample, there is a databound list box, but you can use any control you like. The key thing is to make sure that in the Data panel, you select the List Mode icon. This makes Expression Blend create a list rather than the details if you drag and drop controls from the data panel onto your form. To create a simple master-details view, drag and drop some controls from the list in List Mode first and then change the mode to details view and drag and drop some other controls. You should see something similar to Figure 4-42.

images

FIGURE 4-42

When you finish designing your application, you can upgrade it from Expression to Visual Studio by right-clicking your solution name and selecting Edit in Visual Studio. From there, you can write your code to connect your application from the phone to SharePoint. All of your databinding will still be intact.

Authenticating the Phone Against SharePoint

Remember that Windows Phone does not support NTLM so you either have to use Basic authentication over HTTPS or Forms-Based authentication. The sample shows you how to authenticate when using Forms-Based authentication. When working with authentication, you need to retrieve the security token for the user, also referred to as the FEDAUTH token. Then, for every call to the server, you pass that token as part of your call.

SharePoint gives you the token by calling the authentication.asmx service. You pass your credentials to the service and the service returns the token that you can use for your subsequent calls. The one issue is that the authentication service marks the token using the HTTPOnly flag. This means that you cannot access the authentication cookie from code since .NET respects this flag and blocks you. Instead, you can use the CookieContainer class in .NET, which holds the collection of cookies that you receive and send during your web requests. You cannot retrieve the cookie using this class since .NET strips out any HTTPOnly cookies. Instead, the code passes this cookie container with every call because the FEDAUTH cookie is in the container, just not visible from the .NET code.

When you write your Silverlight Phone application, unfortunately you cannot use the SharePoint Silverlight Object Model since it's not supported on Windows Phone. Instead, you have to use the web services or WCF Data Services to get and set data inside of SharePoint.

To demonstrate this, you step through the code to authenticate against SharePoint and the code that retrieves information from the list. The following code listing shows how to call the authentication service. The code creates a cookie container to hold the security token and then calls the authentication service by manually creating a web request with an XML payload that passes the username and password of the user you want to authenticate as. If you use the sample application, make sure to change the username and password and to change the service reference to point to your server. At the end of the service reference there is a ?wsdl token. Sometime when you try to link to the service reference for Lists.asmx, you get an error. Adding ?wsdl to the end fixes the error.

images

namespace Windows_Phone_SharePoint_App
{
    public partial class MainPage : PhoneApplicationPage
    {


       public event EventHandler OnAuthenticated;

       CookieContainer cookieJar = new CookieContainer();
       
       //CHANGE THESE VARIABLES FOR YOUR OWN ENVIRONMENT
       //ALSO CHANGE THE SOAP REFERENCE TO YOUR SERVER UNDER SERVICE REFERENCES
       private string userName = “thomriz”;
       private string userPassword = “password@”;
       private string SharePoint_vti_bin =
“http://thomrizwin7sp:9999/_vti_bin/”;



       public MainPage()
       {
           InitializeComponent();
       }

     
       private void PhoneApplicationPage_Loaded(object sender,
   RoutedEventArgs e)
       {
          Authenticated;
        
          this.OnAuthenticated += new EventHandler(MainPage_0nAuthenticated);
       }
       void MainPage_0nAuthenticated(object sender, EventArgs e)
       {
          GetTasks();
       }
     
private void Authenticated()
           {
               System.Uri authServiceUri = new Uri(SharePoint_vti_bin +
                “authentication.asmx”);
        
               HttpWebRequest spAuthReq = HttpWebRequest.Create(authServiceUri)
                as HttpWebRequest;
               spAuthReq.CookieContainer = cookieJar;
               spAuthReq.Headers[“S0APAction”] =
                “http://schemas.microsoft.com/sharepoint/soap/Login”;
               spAuthReq.ContentType = “text/xml; charset=utf-8”;
               spAuthReq.Method = “POST”;

           
               spAuthReq.BeginGetRequestStream(new
                AsyncCallback(spAuthReqCallBack), spAuthReq);
          }

          private void spAuthReqCallBack(IAsyncResult asyncResult)
          {
             string envelope =
                    @“<?xml version=““1.0”” encoding=““utf-8””?>
                 <soap:Envelope xmlns:xsi=““http://www.w3.org/2001/
                  XMLSchema-instance”” xmlns:xsd=
                  ““http://www.w3.org/2001/XMLSchema””
                   xmlns:soap=““http://schemas.xmlsoap.org/soap/envelope/””>
                  <soap:Body>
                    <Login xmlns=
                      ““http://schemas.microsoft.com/sharepoint/soap/””>
                      <username>{0}</username>
                      <password>{1}</password>

                  </Login>
                 </soap:Body>
               </soap:Envelope>”;
              
    UTF8Encoding encoding = new UTF8Encoding();
    HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
    Stream _body = request.EndGetRequestStream(asyncResult);
    envelope = string.Format(envelope, userName, userPassword);
    byte[] formBytes = encoding.GetBytes(envelope);

    _body.Write(formBytes, 0, formBytes.Length);
    _body.Close();

    request.BeginGetResponse(new AsyncCallback(ResponseCallback),
    request);
 }

images

The next set of code calls the Lists.asmx web service and passes the cookie container. One thing you have to do when you create your service reference to the Lists.asmx web service, or any service in SharePoint where you want to use the FEDAUTH token, is to go into the bindings that are in the ServiceReferences.ClientConfig file in your project. Find the <basicHttpBinding> section and the <binding> element that corresponds to your web service reference. Add enableHttpCookieContainer=“true” to the element so that the element looks similar to this listing.

<bindings>
          <basicHttpBinding>
              <binding name=“ListsSoap”
               enableHttpCookieContainer=“true”                      maxBufferSize=“2147483647”
 maxReceivedMessageSize=“2147483647”>
                   <security mode=“None” />
                </binding>
            </basicHttpBinding>
        </bindings>

This makes WCF include the cookie container so you can retrieve it in your code. After you add the enableHttpCookieContainer attribute to your element, if you try to update your service reference, Visual Studio complains. To update your service reference, remove the enableHttpCookieContainer attribute. Then, after VS is done updating, paste it back into your element.

To retrieve the SharePoint data, all you need to do is write your standard code to call web services in SharePoint. The only twist is that you need to make sure that you set the CookieContainer property of your web services object to the cookie jar you retrieved from your call to the authentication web service. If you do not do this, you get an access denied error from SharePoint for the calls. The following code does this and retrieves the tasks in the site's task list to display on the phone.

images

void MainPage_OnAuthenticated(object sender, EventArgs e)
        {
            GetTasks();
        }
               
        private void GetTasks()
        {
          ListsService.ListsSoapClient lists = new
            ListsService.ListsSoapClient();
          lists.CookieContainer = cookieJar;

          lists.GetListItemsCompleted += new
            EventHandler<ListsService.GetListItemsCompletedEventArgs>
           (lists_GetListItemsCompleted);

           lists.GetListItemsAsync(
              “Tasks”,             //List Name
              String.Empty,   //View Name
              null,                 //query
              null,                 //view fields
              null,                 //row limit
              null,                 //query options
              null);                //webID

        }

        void lists_GetListItemsCompleted(object sender,
            ListsService.GetListItemsCompletedEventArgs e)
        {
            string result = Convert.ToString(e.Result);
 
            XElement listItems = e.Result;

            IEnumerable<XElement> rows =
              e.Result.Descendants(XName.Get(“row”, “#RowsetSchema”));

            IEnumerable<Task> tasks = from element in rows
              select new Task
                {

                   Title =
                   (string)element.Attribute(“ows_LinkTitle”),
                  Status = (string)element.Attribute
                   (“ows_Status”),
                  Priority = (string)element.Attribute(“ows_Priority”),
                  DueDate = (string)element.Attribute(“ows_DueDate”)
                };

            listBox1.ItemsSource = tasks;

         }

         public class Task
         {

               public string Title { get; set; }
               public string Status { get; set; }
               public string Priority { get; set; }
               public string DueDate { get; set; }

            }


               private void ResponseCallback(IAsyncResult asyncResult)
               {
                    HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState;
                    HttpWebResponse response =
                        (HttpWebResponse)request.EndGetResponse(asyncResult);
                      Stream content = response.GetResponseStream();

                    if (request != null && response != null)
                    {
                        if (response.StatusCode == HttpStatusCode.OK)
                        {
                          using (StreamReader reader = new StreamReader(content))
                          {
         
                               string _responseString = reader.ReadToEnd();
                               reader.Close();
                            }
                        }
                     }

                 //Authentication finished
                 OnAuthenticated(null, null);
            }

images

Now you are well on your way to writing Windows Phone applications for SharePoint. You can use the Phone emulator included in Visual Studio or a real Windows Phone 7 to test your application.

One other note; in addition to using SharePoint web services to retrieve data from SharePoint, you can use the REST API for SharePoint. Remember that you still have to authenticate against SharePoint using the techniques described in this section. The following code snippet shows how to retrieve data using the REST API after you have already authenticated.

HttpWebRequest webReq =
    (HttpWebRequest)HttpWebRequest.Create(_vti_bin + “Tasks”);

           webReq.CookieContainer = cookieJar;

           webReq.BeginGetResponse(
                (result) =>
                {
                    HttpWebRequest asyncReq = (HttpWebRequest)result.AsyncState;

                    XDocument xdoc = XDocument.Load(
                    ((HttpWebResponse)asyncReq.EndGetResponse(result)).
                      GetResponseStream());

            XNamespace ns = “http://www.w3.org/2005/Atom”;
            var items = from item in xdoc.Root.Elements(ns + “entry”)
                       select new { Title = item.Element(ns +
                       + “title”).Value };
            
            this.Dispatcher.BeginInvoke(() =>
            {
                foreach (var item in items)
                    listBox1.Items.Add(item.Title);
            });
        }, webReq);

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
13.59.79.176