다음을 통해 공유


Chapter 4: Sandboxed Solutions (Inside SharePoint 2010)

Summary: Learn what's new with the SharePoint developer platform from experts on Microsoft SharePoint Foundation 2010 and Microsoft SharePoint Server 2010. Read a preview of Chapter 4, Sandboxed Solutions, from Inside Microsoft SharePoint 2010.

Applies to: Business Connectivity Services | SharePoint Foundation 2010 | SharePoint Server 2010 | Visual Studio

This article is a preview excerpt from Inside Microsoft SharePoint 2010 by Ted Pattison, Andrew Connell, and Scot Hillier (ISBN 9780735627468, published with the authorization of Microsoft Corporation by O'Reilly Media, Inc. Copyright © 2010 by Critical Path Training, LLC). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Warning

The Sandbox Solution framework provides a mechanism for executing user-provided code outside of the IIS worker process. The Sandbox Solution framework should not be used as a mechanism for enforcing security boundaries when executing user code. Sandbox Solutions are not supported as a security boundary with user code, especially code of unknown origin. We advise against executing Sandbox Solutions of unknown origins.

Sandboxed Solutions

Sandboxed solutions are Microsoft SharePoint Foundation features that are deployed into a partially trusted environment referred to as the sandbox. The sandbox is designed to bring greater stability to a SharePoint farm by restricting actions that could cause problems with performance, security, or other areas. This stability is achieved by limiting the functionality accessible to custom code solutions through the use of code access security (CAS) policies and by restricting access to portions of the object model.

The sandbox is a new capability for SharePoint Foundation and represents one of the most significant changes to the SharePoint developer platform. Because of the benefits the sandbox offers, developers should always look to deploy custom code to the sandbox before considering any other option. Therefore, a strong understanding of the sandbox is critical for writing robust SharePoint solutions.

Prior to SharePoint 2010, the vast majority of custom code had to be deployed to the global assembly cache (GAC). Because of this, developers often operated with full trust in the SharePoint server not only to call any part of the object model, but also to access databases, Web services, directories, and more. The one major exception to this rule in SharePoint 2007 development was deployment with Web Parts. The assembly containing Web Parts could be deployed to the GAC or to the bin folder under the Web application. However, a developer could easily grant full trust to Web Parts deployed in the bin folder anyway, simply by changing the trust level setting in the web.config file to Full.

The result of this situation was that nearly all code in the farm ran with full trust and few SharePoint developers had the need to learn much about CAS restrictions. The drawback of this approach is that the SharePoint farm was occasionally destabilized by custom code. To make matters worse, the offending assembly was often hard to identify.

As an example, consider the case in which an intermediate-level SharePoint developer is writing a Web Part designed to aggregate tasks from many lists to display on a site collection home page. Imagine this developer is unaware of the Microsoft.SharePoint.SPSiteDataQuery object and instead intends to loop through all the sites in the collection, loop through all the lists in the site, and finally loop through all the tasks in a list to create a single view of all tasks in the site collection. Furthermore, the developer intends to elevate privileges with the Web Part code to ensure programmatic access to the entire site collection. This simple Web Part could easily destabilize the entire farm.

Opting to loop through all the lists and items is a potential disaster because the process of looping causes entire lists to be loaded into memory. If these lists are large, significant resources could be consumed, resulting in performance problems for all users, as these resources would then be unavailable for servicing other requests. Elevating privileges is potentially problematic because the code gains access to all lists, and the intermediate developer may be unaware that creating Microsoft.SharePoint.SPSite and Microsoft.SharePoint.SPWeb objects without properly disposing of them leads to memory leaks. If this simple Web Part is deployed to the GAC, it will have no limitations on the resources it can consume. If the Web Part is then put on the home page of the portal, it could be hit by potentially thousands of users. It wouldn't be long before the farm was brought to a standstill because of low memory availability.

Interestingly, when poorly written code does destabilize a farm, the developer rarely suffers. Farms have limited mechanisms for identifying the code that is causing the problem. In some cases, information might be available in the Unified Logging Service (ULS), but often the destabilization is just considered a mysterious performance problem. As a result, fixing the problem generally falls to the IT professionals in charge of running the SharePoint farm. They are the ones who receive a call on Friday night saying that the farm is misbehaving. They are also the least qualified people to identify and fix the real problem—simply because they are not the developer who wrote the custom code that is causing the problem. In fact, IT professionals may actually cause further problems by desperately changing the farm configuration in an attempt to regain stability. Sometimes such situations become chronic, which results innegative experiences for IT professionals and users, who can begin to question the viability of SharePoint as a platform.

On the other end of the spectrum, experienced SharePoint developers often need access to trusted resources like external databases to solve business problems. These developers may be well aware of CAS limitations, understand the SharePoint object model, and generally create professional solutions. Unnecessarily restricting their ability to customize code significantly reduces the flexibility of SharePoint as a platform for them. Therefore, the challenge is one of balancing the agility required to solve business problems against the farm security and stability required of enterprise applications. This is where sandboxed solutions come into play.

Understanding the Sandbox

The sandbox is a separate process in which a SharePoint solution runs in isolation. This separate process exposes a subset of the Microsoft.SharePoint namespace that an assembly can call. Additionally, the process runs under a CAS policy that restricts programmatic access to any resource outside the sandbox (although as you'll learn shortly, you can create a full-trust proxy). Sandboxed solutions are managed through a new Solution Gallery at the site collection level and farm-level tools found in Central Administration. Additionally, Microsoft Visual Studio 2010 can deploy solutions directly to the sandbox during development.

Enabling sandboxed solutions in a SharePoint farm is simple because it requires enabling only a single Windows service, the User Code Service. The User Code Service is responsible for managing the execution of sandboxed solutions across the farm. Each server in the farm that will participate in hosting sandboxed solutions must have the User Code Service enabled. You can enable the User Code Service through Central Administration\System Settings\Manage Services on Server. Generally, this service simply runs under the Network Service account. Once this service is enabled, you can begin running sandboxed solutions.

Building a Basic Sandboxed Solution

When you create a new SharePoint solution in Visual Studio 2010, the SharePoint Customization Wizard offers you the option to deploy your solution as a farm solution or a sandboxed solution. Farm solutions are deployed in the same manner as they were in SharePoint 2007, and you can manage them by selecting System Settings\Manage Farm Solutions in Central Administration. Sandboxed solutions, on the other hand, are deployed directly to a site collection.

Now it's time to walk through an example of creating a sandboxed solution. We'll revisit the idea of developing a SharePoint solution for the Wingtip Lead Tracker feature that we introduced in Chapter 2. This time, however, we'll use the SharePoint Developer Tools in Visual Studio 2010. We'll start by creating an Empty SharePoint project named LeadTracker. When you click OK to create the new project, the Visual Studio SharePoint Customization Wizard allows you to select the sandboxed solution deployment option, as shown in Figure 4-1.

Figure 4-1 The SharePoint Customization Wizard allows you to choose between a sandboxed solution and a farm solution.

Choose a sandboxed solution or a farm solution.

After you create the project in Visual Studio, you can alter the deployment target by editing the Sandboxed Solution property of the project in the project property sheet. Along with changing the deployment target, the Sandboxed Solution property also determines whether the System.Security.AllowPartiallyTrustedCallers attribute appears in the AssemblyInfo file. By default, assemblies targeting the sandbox have this attribute and assemblies targeting the farm do not. Figure 4-2 shows the relationship between the Sandboxed Solution property and the AllowPartiallyTrustedCallers attribute.

Figure 4-2 The Sandboxed Solution property affects the AllowPartiallyTrustedCallers attribute.

Property affects AllowPartiallyTrustedCallers.

From the perspective of Visual Studio, there is no difference between a sandboxed solution and a farm solution. Both solutions are packaged and built in exactly the same manner. The differences are strictly related to the deployment target and the functionality available to the solution at run time.

The LeadTracker project has been created with two ListInstance items, which are used to create two Contacts lists: Sales Leads and Customers. There is also a site collection scoped feature with a Feature Receiver. The FeatureActivated event handler uses the server-side object model to change the default view of the Sales Leads list and to add custom nodes to the Quick Launch bar, as shown in Figure 4-3. The FeatureDeactivating event handler deletes the two lists and removes the Lead Tracker nodes from the Quick Launch bar.

Figure 4-3 The Lead Tracker solution creates lists and modifies the Quick Launch bar.

The Lead Tracker solution creates lists.

Everything that has been added to the Lead Tracker project is compatible with the sandbox, as shown in Figure 4-4. Keep in mind, however, that any SharePoint solution that can be deployed as a sandboxed solution can also be deployed as a farm solution. Therefore, when working on a SharePoint project like Lead Tracker, you can switch back and forth between sandbox deployment and farm deployment.

Figure 4-4 The Lead Tracker project can be deployed as either a sandboxed solution or a farm solution.

Project deployed as sandboxed or farm solution.

To illustrate this point, examine Listing 4-1, which shows an element manifest file for the Sales Leads SharePoint project item. The ListInstance element creates a new Contacts list named Sales Leads. The CAML element for this solution is identical whether it is deployed as a sandboxed solution or as a farm solution.

Listing 4-1 The behavior of a ListInstance element is the same when deployed as either a sandboxed solution or a farm solution.

<Elements xmlns="https://schemas.microsoft.com/sharepoint/">

  <ListInstance 
    TemplateType="105"
    FeatureId="00bfea71-7e6d-4186-9ba8-c047ac750105"
    Title="Sales Leads"
    Description="a list to track sales leads" 
    Url="Lists/SalesLeads"
    OnQuickLaunch="TRUE" /> 

</Elements>

When the Sandboxed Solution property is set to True, selecting Build\Deploy Solution deploys the solution to the site collection Solution Gallery. This new gallery is the repository for all sandboxed solutions deployed within the site collection. You can access the gallery from the Site Settings page, under the Galleries section, using the link entitled Solutions. The Solutions Gallery is part of the Global Site Definition, so every site collection has one. Figure 4-5 shows the Solution Gallery with the feature deployed.

Figure 4-5 The Solution Gallery allows a site collection owner to upload and activate a sandboxed solution.

Upload and activate a sandboxed solution.

Keep in mind that there is a terminology difference between the two deployment methods. Farm solutions are installed and deployed. Sandboxed solutions are uploaded and activated. The install step in a farm deployment is similar to the upload step in a sandboxed deployment. Likewise, the deploy step in a farm deployment is similar to the activate step in a sandboxed deployment. The one notable difference is that the activation of a sandboxed solution automatically activates any feature scoped to the level of the site collection.

When the Sandboxed Solution property is set to False, selecting Build\Deploy will deploy the solution to the Farm Solution Gallery. From the Farm Solution Gallery, the solution can be deployed to various Web applications in the traditional manner. Figure 4-6 shows the Farm Solution Gallery with the feature deployed.

Figure 4-6 The Farm Solution Gallery allows a farm administrator to manage and deploy farm solutions.

Manage and deploy farm solutions.

For the simple ListInstance element defined in the CAML earlier, there is no difference in behavior when deployed as a sandboxed solution or a farm solution. Even at this point, however, you can see a significant difference in the deployment process. When a solution is deployed to the farm-level gallery, the farm administrator must become involved. This requirement gives the farm administrator significant control over the solution, but it is also a significant burden.

A SharePoint solution designed for the sandbox, like the Wingtip Lead Tracker, doesn't require farm-level deployment. Instead, the sandbox approach allows the site collection administrator to upload the solution and deploy it without involving the farm administrator. The farm administrator is thus relieved from the burden of dealing with solution deployment, and the site collection administrator is empowered to make decisions regarding the functionality available in the site collection. Furthermore, the sandbox is protecting the farm from instability by isolating the solution.

Sandboxed solutions also support feature upgrading. Upgrading is accomplished by creating a solution (.wsp) file with a different name but the same solution Id. When you subsequently deploy the new solution version, SharePoint will see that the Id matches an existing solution and prompt you to upgrade the solution. Once upgraded, the old solution is automatically deactivated.

Understanding the Architecture

Although the User Code Service is responsible for managing the execution of sandboxed solutions throughout the farm, several other components and processes are involved in the system. These components and processes include the Execution Manager, the Worker Service, and the Worker Service Proxy. Figure 4-7 shows an architectural diagram of the sandboxing system.

Figure 4-7 The sandboxing system executes code from a sandboxed solution in an isolated, partially trusted worker process.

Sandboxing system executes code from a solution.

The sandboxing system uses a component named the Execution Manager to handle the loading and execution of sandboxed solution code. The Execution Manager runs within the IIS application pool and is responsible for making a call out to the User Code Service (SPUCHostService.exe) requesting that a sandboxed solution be loaded.

As stated earlier, the User Code Service can be running on many different servers in the farm. You specify load balancing execution across the servers in the farm through administrative settings in Central Administration\System Settings\Manage User Solutions. Using these options, you can choose to execute the sandboxed solution on the same server where the user request was made or on a dedicated set of servers. In either case, the User Code Service makes a request of the Worker Service (SPUCWorkerProcess.exe) to load the sandboxed solution.

The Worker Service utilizes the same service framework as other services in the SharePoint farm. This framework includes a proxy that allows for remote communication with the service. For this reason, you'll also see a Worker Service Proxy (SPUCWorkerProcessProxy.exe) running on the server. Although the sandboxing architecture makes use of the SharePoint service framework, it isn't exposed through Central Administration in the same way as other services are. Therefore, you can't make a direct connection to the Worker Service Proxy. Instead, you simply configure the User Code Service for a server. The services framework is covered in more detail in Chapter 13.

Once the assembly of a sandboxed solution is loaded into the Worker Service, its code can be executed. A pool of AppDomains is maintained within SPUCWorkerProcess.exe, and an available AppDomain is used to execute the request. Only one request at a time is executed in any AppDomain, so there won't be conflicts between the solutions.

As mentioned previously, execution of the code is limited to a subset of the Microsoft.SharePoint namespace and subject to CAS policy restrictions. Any calls to the SharePoint object model are first filtered against the subset object model to prevent any disallowed calls and then executed against the full object model, which runs in the Worker Service Proxy. When the code execution completes, the results are bubbled back up to the client request, which has been waiting synchronously for the request to complete. The final page is then drawn and delivered to the waiting user.

Knowing which processes are supporting the sandbox allows you to debug your solutions. In a full-trust solution, you can debug code by attaching to the w3wp.exe process. However, sandboxed solutions are running in a separate process, so you must attach the Visual Studio 2010 debugger to the SPUCWorkerProcess.exe process instead.

The components that make up the sandboxing system can be found in the SharePoint System Directory at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\UserCode. In this directory, you'll find SPUCHostService.exe, SPUCWorkerProcess.exe, and SPUCWorkerProcessProxy.exe. Along with the executables, you'll also find a web.config file that references the CAS policy restrictions in the file C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\wss_usercode.config. Finally, this folder also contains a subfolder named \Assemblies. The Assemblies folder contains the assemblies Microsoft.SharePoint.dll, Microsoft.SharePoint.SubsetProxy.dll, and Microsoft.SharePoint.UserCode.dll, which support the object model subset and creation of full-trust proxies.

Understanding Solution Restrictions

Restricting the access of sandboxed solutions to a subset of the Microsoft.SharePoint namespace is intended to prevent solutions from accessing functionality that could destabilize the farm. When sandboxed solutions make calls to the SharePoint object model, the calls are routed through the subset proxy, which in turn makes calls to the full object model. The subset proxy exposes only the following subset of the object model:

  • All of the Microsoft.SharePoint namespace, except

    • SPSite constructor

    • SPSecurity object

    • SPWorkItem and SPWorkItemCollection objects

    • SPAlertCollection.Add method

    • SPAlertTemplateCollection.Add method

    • SPUserSolution and SPUserSolutionCollection objects

    • SPTransformUtilities object

    • Microsoft.SharePoint.Navigation namespace

  • All of the Microsoft.SharePoint.Utilities namespace, except

    • SPUtility.SendEmail method

    • SPUtility.GetNTFullNameandEmailFromLogin method

  • Microsoft.SharePoint.Workflow namespace

  • All of the Microsoft.SharePoint.WebPartPages namespace, except

    • SPWebPartManager object

    • SPWebPartConnection object

    • WebPartZone object

    • WebPartPage object

    • ToolPane object

    • ToolPart object

If you examine the subset object model carefully, you'll notice that the available portions are all part of the core foundational object model. There are no objects available from more advanced capabilities such as Business Connectivity Services (BCS), SharePoint Search, Excel Services, and so on. With this subset of functionality in mind, Table 4-1 lists common SharePoint project item types and whether or not they are available in the sandbox.

Table 4-1. SharePoint Project Item Availability for Sandboxed Solutions

Available in Sandbox

Not Available in Sandbox

List definitions

Visual Web Parts

List instances

Application pages

Site definitions

Custom action group

WebTemplate feature elements (instead of Webtemp.xml)

HideCustomAction element

Content types/Fields

Content type binding

Module/files

Web application-scoped features

Feature callouts

Farm-scoped features

Web Parts

Workflows with code

Support for all Web Parts that derive from System.Web.UI.WebControls.WebParts.WebPart

SPItemEventReceiver

SPListEventReceiver

SPWebEventReceiver

Custom actions

Declarative workflows

When designing sandboxed solutions, you have the option of using a declarative, programmatic, or hybrid approach. The purely declarative approach utilizes CAML elements alone to define the solution; a programmatic approach uses only code; and a hybrid approach uses both CAML and code. While many SharePoint solutions are hybrids, a declarative approach is particularly good for sandboxed solutions because no code needs to be deployed to the sandbox. This approach simplifies your solution development considerably. The following is a list of supported CAML elements you can use in your declarative solutions.

  • ContentType

  • CustomAction

  • Field

  • ListInstance

  • ListTemplate

  • Module

  • PropertyBag

  • Receivers

  • WebTemplate

  • WorkflowActions

  • WorkflowAssociation

If a sandboxed solution should make a call to a portion of the SharePoint object model that isn't exposed through the subset proxy, the call will fail. In addition to the default elements not available through the subset proxy, Microsoft provides a mechanism for farm administrators to further restrict access to the API. The API block list is a collection associated with the Microsoft.SharePoint.Administration.SPWebService class that allows farm administrators to specify additional types that are blocked from use in the sandbox.

The SPWebService class has a RestrictedObjectModel collection property that contains zero or more Microsoft.SharePoint.Administration.SPObjectModelMember objects. Each SPObjectModelMember instance represents a blocked type. Farm administrators can use this collection to customize the available set of types or in response to a sandbox vulnerability that might be identified by Microsoft in the future. The following code shows how to list all the blocked APIs in the farm.

SPWebServiceCollection webServices = new SPWebServiceCollection(SPFarm.Local);

foreach (SPWebService service in webServices){
  foreach (SPObjectModelType type in service.RestrictedObjectModel.RestrictedTypes) {
    Console.WriteLine(type.FullName);
  }
}

Interestingly, there is little in Visual Studio 2010 to prevent you from writing code against forbidden portions of the object model. Therefore, you have to be careful with your code and test to ensure that it is properly supported by the targeted deployment model. When creating solutions, you can set references to any assembly you want and write code just as you can with any SharePoint solution. The only scenario that really doesn't work is to reference one sandboxed solution from another. However, the assemblies that you reference will be subject to CAS policy restrictions when executed. So even though you can write code against an assembly and compile successfully, it doesn't mean that the code will execute at run time. Sandboxed solutions are severely limited by CAS policies and can't generally access resources outside the sandbox. Table 4-2 details the specific CAS restrictions applied through the wss_usercode.config policy file.

Table 4-2. CAS Restrictions for Sandboxed Solutions

Policy

Description

SecurityPermission.Execution

Allows the sandboxed solution to load and execute.

AspNetHostingPermission.Level =Minimal

Allows the sandboxed solution to load and execute within anASP.NET context, but prevents the solution from accessing anyresourceson the server.

SharePointPermission.ObjectModel

Allows the sandboxed solution to access the SharePoint objectmodel; however, the subset proxy limits the calls that will actuallysucceed.

While CAS policies restrict the code that can run on the server, no restrictions are placed on code that runs on the client. This means that you can create Web Parts that inject JavaScript directly into the page, build Silverlight solutions, create AJAX Web Parts, use the client object model, and utilize script libraries such as JQuery. In many cases, a client-side solutionis an attractive option because resources can be made available to the client through the SharePoint client object model or using Windows Communication Foundation (WCF) services that aren't available through the sandbox. In addition, although we haven't yet talked about resource throttling, it's important to know that resources utilized via the client object model don't count against a sandboxed solution's resource limits. More details on resources are coming later in this chapter, in the "Administrating Sandboxed Solutions" section.

Designing a Sandboxed Solution

As an example of the type of thinking required when designing sandboxed solutions, consider a simple sandboxed solution named ListPrintingUtility designed to add support for printing lists. The idea behind this solution is to add a command button to the Ribbon above a list that will allow the user to print the current view of the list. Figure 4-8 shows what the Print List button looks like on the Ribbon and also shows the utility printing window that opens when the button is clicked.

Figure 4-8 A sandboxed solution can add a custom Ribbon command for printing lists.

Add a custom Ribbon command for printing lists.

The Print List button shown on the Ribbon is added using a CustomAction element. When adding to the Ribbon, the CAML is more complex than what is required for simply adding to the Site Actions menu. Nonetheless, the CAML is supported by the sandbox, so it's a valid approach for creating the required interface. Listing 4-2 shows the CAML used to create the button. (More detail on Ribbon customization is available in Chapter 5.)

Listing 4-2 This CustomAction element extends the Ribbon with a custom Print List command.

<CustomAction
  Id="Ribbon.List.Items.Print" Location="CommandUI.Ribbon"
  RegistrationType="ContentType" RegistrationId="0x01"
  Sequence="11"  Title="Print" >

  <CommandUIExtension>
    <CommandUIDefinitions>
      <CommandUIDefinition Location="Ribbon.ListItem.Actions.Controls._children">
        <Button
          Id="Ribbon.List.Items.Action.PrintListButton"
          LabelText="Print List" Alt="Print List" Sequence="01"
          Image32by32="/_layouts/images/mewa_frozenb.gif"
          Command="PrintList"
          TemplateAlias="o1" />
      </CommandUIDefinition>
    </CommandUIDefinitions>
    <CommandUIHandlers>
      <CommandUIHandler
        Command="PrintList"
        CommandAction="javascript:
          var queryString = '?ListId={ListId}&amp;ViewId=' + ctx.view;
          var targetUrl = '{SiteUrl}/PrintingPages/List.aspx' + queryString; 
          var windowOptions = 'scollbars=1,height=600,width=800';
          window.open(targetUrl, 'printwindow', windowOptions);"/>
    </CommandUIHandlers>
  </CommandUIExtension>
</CustomAction>

The CommandAction attribute specifies the code to run when the button is clicked. In this case, some JavaScript runs that opens a new window containing the page List.aspx. QueryString parameters are passed to the page detailing the list and view to display. The idea is simply to display the current view in a new page without any chrome. This design effectively creates a print view for the list.

The challenge with the List.aspx page is that it must be designed to run in the sandbox. In the past, most developers would simply deploy the page as an application page to the LAYOUTS directory directly in the SharePoint root directory. But the sandbox doesn't support these types of pages. Therefore, we need a different approach.

For this solution, the List.aspx page is deployed as a site page. Site pages are pages that are deployed to the content database instead of the system directory. These types of pages are supported in the sandbox and can be deployed to the content database by using a SharePoint project item created from the Module SharePoint project item type.

Figure 4-9 shows the ListPrintingUtility project opened in Visual Studio 2010. You can see that a SharePoint project item named RibbonExtensions was created from the Empty Element SharePoint project item type. This project item contains the element manifest with the CustomAction that adds the Print List button to the Ribbon. A second SharePoint project item, PrintingPages, was created from the Module SharePoint project item type. The PrintingPages item is used to deploy the site page List.aspx along with its associated files, including List.js, ListStyles.css, and ListStylesPrinting.css.

Figure 4-9 The ListPrintingUtility project uses client-side code instead of server-side code.

ListPrintingUtility project uses client-side code.

The site page List.aspx renders a print view of a list. When a user clicks the Print List button, the List.aspx page is requested with a QueryString containing the ListId and the ViewId. The List.aspx page must provide code to get the list items for rendering. However, there is a problem: site pages don't support server-side code. Therefore, you can't write any code in the page that uses the server-side SharePoint object model. This is where the client object model comes into play.

The client object model is a JavaScript library that allows you to write code on the client that is very similar to server-side code. The client object model is covered in detail in Chapter 10. For this solution, the necessary print view is created using client-side code, which is supported in site pages. Listing 4-3 shows the complete code in List.js for rendering the print view using the client object model.

Listing 4-3 A sandboxed solution can make use of JavaScript code written against the new client-side objectmodel.

var list;
var printView;

function initPage() {
  ExecuteOrDelayUntilScriptLoaded(getListData, "sp.js");  
}

function getListData() {
  // get QueryString parameters
  var queryString = window.location.search.substring(1);
  var args = queryString.split('&');
  var listId = args[0].split('=')[1];
  var viewId = args[1].split('=')[1];
  // use client object model to get list items
  var context = new SP.ClientContext.get_current();
  var site = context.get_web();
  context.load(site);
  var lists = site.get_lists();
  context.load(lists);
  list = lists.getById(listId);
  context.load(list);
  var views = list.get_views();
  context.load(views);
  var view = views.getById(viewId);
  context.load(view);
  printView = view.renderAsHtml();
  context.executeQueryAsync(success, failure);
}

function success() {
  $get("listTitle").innerHTML = list.get_title();
  $get("listTable").innerHTML = printView.get_value();
}

function failure() {
  $get("listTitle").innerHTML = "error running Client OM query";
  $get("listTable").innerHTML = "";
}

The print list solution is a good example of the thinking involved in designing sandboxed solutions. Supported CAML elements were used to create the button. A site page was used to create the user interface experience, and supported client-side code was used to implement the functionality. A key point is that this project contains no server-side code. If you look back at the project property sheet in Figure 4-9, you can see that this project has the Include Assembly in Package property set to false. This is a new approach that is very different from what most developers have been doing in previous versions of SharePoint.

Understanding Full-Trust Proxies

After examining the restrictions imposed by the subset object model and CAS policies, you might conclude that many SharePoint solutions simply can't be deployed to the sandbox. Many SharePoint solutions require access to external resources such as databases or Web services or to advanced object model features. Fortunately, there is a mechanism designed to provide access to these resources and objects through a full-trust proxy.

A full-trust proxy allows a sandboxed solution to make a call to a trusted assembly outside the sandbox. That trusted assembly can in turn make calls to resources and objects that the sandboxed solution can't reach. The results of the operation can then be returned to the sandboxed solution.

Initially, you may find yourself wondering if the full-trust proxy object is simply a complex workaround for deploying full-trust solutions in the first place. After all, if you can deploy a full-trust proxy, can't you destabilize the farm? Wasn't the point of a sandbox to avoid code that can destabilize the farm? Well, the value of the full-trust proxy is that the farm administrator can choose what functionality will be deployed with full trust.

Consider the scenario in which a developer wants to create a Web Part that will read records from a database and display the information in a grid. If the Web Part is written assuming it has full trust, the developer can easily create database entities and read them. The entities can then be bound to controls and displayed. However, the developer would also have the ability to write back to the database. Furthermore, nothing stops the developer from performing other operations within the Web Part that require full trust. This is the situation that potentially leads to instability.

The full-trust proxy, on the other hand, can be written so that it only reads records from the database and returns those records to a sandboxed solution. The sandboxed solution can call the full-trust proxy to get the records it needs, bind them to the grid, and display them. However, the Web Part has no mechanism to write back to the database because the full-trust proxy doesn't expose that functionality. Additionally, the full-trust proxy can provide the same capability to other sandboxed solutions so that it needs to be written only once and can then be used by many solutions. Finally, the full-trust proxy allows only this read operation—no other higher-trust functionality is available to the developer.

The full-trust proxy is what makes the sandbox architecture more than simply an interesting new capability. The full-trust proxy completely changes the game for SharePoint developers. Under SharePoint 2010, developers should always look to deploy sandboxed solutions first. Only after proving that a sandboxed solution is inadequate should a developer move to another deployment model.

Even though sandboxed solutions are a boon for developers, they do have some limitations, even with full-trust proxies. For example, sandboxed solutions can't be used to deploy application pages with code to the LAYOUTS directory. Those assemblies must still be placed in the GAC.

To create a full-trust proxy that can be used by sandboxed solutions, you can create a new SharePoint project and add a custom class for each trusted operation. The class must then be registered within the SharePoint farm so that its operation can be made available to deployed sandboxed solutions. Finally, you must create a sandboxed solution that calls the trusted operation.

The first step in developing a full-trust proxy operation is to create a class that inherits from the SPProxyOperation class in the Microsoft.SharePoint.UserCode namespace. The SPProxyOperation class defines the operations that will be exposed to the sandboxed solution. This class must override the Execute method, which takes a SPProxyOperationsArgs object as an argument. The Execute method returns an object, which means that the sandboxed code must know what object type is returning and cast it appropriately.

Let's look at an example class that defines a full-trust proxy operation. Listing 4-4 shows the GetWingtipWebApps class that uses the server-side object model to enumerate through the Web applications in the current farm and to pass back a collection with the URL for each one. The point here is that this type of server-side object model code can't be called from within the sandbox. However, it can be called from a full-trust proxy operation on behalf of code running in the sandbox.

Listing 4-4 A full-trust proxy operation can be created using a class that inherits from SPProxyOperation.

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.UserCode;

namespace WingtipTrustedBase {
  public class GetWingtipWebApps : SPProxyOperation {

    public override object Execute(SPProxyOperationArgs args) {
      List<string> webApps = new List<string>();
      SPWebService webService = SPFarm.Local.Servers.GetValue<SPWebService>();
      foreach (SPWebApplication webApp in webService.WebApplications) {
        webApps.Add(webApp.DisplayName);
      }
      return webApps;
    }
  }
}

Notice that the SPProxyOperationsArgs class is passed as an argument to the Execute method. If you need to create a full-trust proxy operation that requires parameters, you must create a second class that inherits from SPProxyOperationsArgs and is marked as Serializable. The caller is then responsible for creating an instance of this class and passing it when calling the full-trust proxy operation.

Once the SPProxyOperation classes and optionally the SPProxyOperationsArgs class are created, they must be added to the GAC. The operations must then be registered with the SharePoint farm. The registration process can easily be accomplished using a SharePoint project with a farm-level feature with a Feature Receiver. Listing 4-5 shows a Feature Receiver that handles registration.

Listing 4-5 A full-trust proxy operation must be registered with the User Code Service.

using System;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.UserCode;
using Microsoft.SharePoint.Administration;

namespace WingtipTrustedBase.Features.MainFarm {

  [Guid("ac52a0b6-7d95-400a-a911-25a103fa3ba7")]
  public class MainFarmEventReceiver : SPFeatureReceiver {

    public override void FeatureActivated(
                                SPFeatureReceiverProperties properties) {
      SPUserCodeService userCodeService = SPUserCodeService.Local;
      if (userCodeService != null) {
        string assemblyName = this.GetType().Assembly.FullName;
        SPProxyOperationType GetWingtipWebAppsOperation =
            new SPProxyOperationType(assemblyName, 
                                     typeof(GetWingtipWebApps).FullName);
        userCodeService.ProxyOperationTypes.Add(GetWingtipWebAppsOperation);
        userCodeService.Update();
      }
      else { throw new ApplicationException("User Code Service not running."); }
    }

    public override void FeatureDeactivating(
                                  SPFeatureReceiverProperties properties) {
      SPUserCodeService userCodeService = SPUserCodeService.Local;
      if (userCodeService != null) {
        string assemblyName = this.GetType().Assembly.FullName;

        SPProxyOperationType GetWingtipWebAppsOperation =
            new SPProxyOperationType(assemblyName,
                                     typeof(GetWingtipWebApps).FullName);
        userCodeService.ProxyOperationTypes.Remove(GetWingtipWebAppsOperation);
        userCodeService.Update();
      }
      else { throw new ApplicationException("User Code Service isn't running."); }
    }
  }
}

Once the full-trust proxy is created and registered, you can call it from a sandboxed solution using the SPUtility.ExecuteRegisteredProxyOperation method. If the full-trust proxy operation you're calling takes parameters, you must create an instance of the SPProxyOperationsArgs class and pass it along with the assembly information for the SPProxyOperation class when calling the ExecuteRegisteredProxyOperation method. Listing 4-6 shows a Web Part making a call to the full-trust proxy operation named.

Listing 4-6 You call a trusted proxy operation using the ExecuteRegisteredProxyOperation method.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.UserCode;

namespace SandboxedParts.WebApps {

  public class WebApps : WebPart {
    protected override void RenderContents(HtmlTextWriter writer) {
      string operationAssemblyName = "WingtipTrustedBase, [four=part assembly name]";
      string operationTypeName = "WingtipTrustedBase.GetWingtipWebApps";
      List<string> WebApplications = 
        SPUtility.ExecuteRegisteredProxyOperation(operationAssemblyName,
                                                  operationTypeName,
                                                  null) as List<string>;
      
      // use the data returned from full trsut proxy operation
      foreach (var WebApplication in WebApplications) {
        writer.Write(WebApplication + "<br />");
      }
    }
  }
}

While full-trust proxies represent a good option for overcoming the limitations of the sandbox, they are not the only option. Keep in mind that you can always use client-side code to perform operations outside the sandbox. This means that you can utilize the client object model and WCF services from JavaScript code to perform operations that might otherwise be restricted on the server.

Administrating Sandboxed Solutions

Like all administration in SharePoint Foundation, sandboxed solutions support a layered-administration model. Configuration can be done at the farm level as well as the site collection level. Farm-level administration controls quotas and resources as well as providing monitoring capabilities. At the site collection level, administrators can upload, activate, and deactivate solutions within the site collection.

Using Central Administration Tools

To begin farm-level administration, the administrator opens the Central Administration site and clicks System Settings\Manage Services on Server. From this page, the administrator can turn the User Code Service on or off for any given server in the farm. Remember that the User Code Service must be running on a server in order for it to execute a sandboxed solution.

The administrator can control blocked solutions and load balancing by selecting System Settings\Manage User Solutions. On this page, the farm administrator can browse to solution packages that should be blocked and add them to the blocked list. Blocked solutions are not allowed to execute on the farm. The farm administrator can also select the type of load balancing to use with sandboxed solutions. The administrator can choose to have the solution execute on the same server where the request was received or to have it routed to a dedicated set of servers. Figure 4-10 shows the Manage User Solutions page.

Figure 4-10 Farm administrators can manage user solutions within Central Administration.

Manage user solutions in Central Administration.

Along with basic management, the farm administrator can also specify a quota for sandboxed solutions. Quotas are set up in Central Administration by selecting Application Management\Configure Quotas and Locks. On this page, the farm administrator can set a limit on the resources that a sandboxed solution can use in a given Web application. This capability is critical to maintaining the stability of the farm. Figure 4-11 shows the Site Quota Information page where the limits are specified.

Figure 4-11 Farm administrators can adjust the settings site quotas for sandboxed solutions.

Adjust settings site quotas for solutions.

Quotas for sandboxed solutions are measured in units of resource "points." Resource points are a calculated value that takes 14 different factors into account. The farm administrator assigns a number of points to a site collection that represents the resource usage allowed per day. The farm administrator can also set a threshold at which the site collection administrator will receive a warning e-mail. The following is a list of the 14 factors used to determine points.

  • AbnormalProcessTerminationCount

  • CPUExecutionTime

  • CriticalExceptionCount

  • InvocationCount

  • PercentProcessorTime

  • ProcessCPUCycles

  • ProcessHandleCount

  • ProcessIOBytes

  • ProcessThreadCount

  • ProcessVirtualBytes

  • SharePointDatabaseQueryCount

  • SharePointDatabaseQueryTime

  • UnhandledExceptionCount

  • UnresponsiveProcessCount

A conversion factor is applied to each of the 14 metrics used to monitor sandboxed solutions to convert the measure to points. These factors normalize the different units used in the measurements so that the farm administrator can set and monitor them. Each conversion factor can be changed using Windows PowerShell.

When you're initially introduced to the concept of resource points, you'll find yourself trying to understand exactly what they are, how they are calculated, and how you can plan their usage. Such an approach is inappropriate for sandboxed solutions. Instead, the goal for developers is to use as few resources as possible. You should test your resource usage in a QA environment where you can run the solution and see the resulting resource point usage. This will give you some idea of how your solution performs and whether it will fit into the current quota. Once a daily resource quota is exceeded, no more solutions will execute and an error message will appear. Figure 4-12 shows error messages for Web Parts running in a site collection that has exceeded its daily quota.

Figure 4-12 Code within a sandboxed solution will no longer execute once it has exceeded the daily quota.

Code does not execute once it exceeds daily quota.

Validating Sandboxed Solutions

For a farm administrator to block solutions from running in the sandbox, the administrator must have some way of identifying the solutions to block. The farm administrator could block a solution because it is behaving poorly, but this approach isn't proactive. A better approach is to identify rogue solutions before they ever run. To solve this problem, you can create a custom solution validator that runs whenever a solution is activated within a site collection. Your custom solution validator can decide which sandboxed solution to allow and which to prohibit. For example, you can decide to allow for only sandboxed solutions with assemblies that have been compiled using your company's private signing key.

To develop a validator, you create a class that inherits from Microsoft.SharePoint.UserCode. SPSolutionValidator. The SPSolutionValidator class in turn inherits from the Microsoft. SharePoint.Administration.SPPersistedObject class, which means that SPSolutionValidator can serialize its state into the SharePoint database. The impact of this capability is that you can use a generic List<string> collection to store any information you want to support validation. For example, you could use this approach to store information about known bad publishers.

When you inherit from the SPSolutionValidator class, you must provide a System.Runtime.InteropServices.Guid for your validator that is surfaced as the ProviderID property of the validator. Your validator must provide a default constructor that takes no arguments as well as a constructor that takes a Microsoft.SharePoint.UserCode.SPUserCodeService object. In the second constructor, you must set the Signature property to a unique value. Listing 4-7 shows a basic validator class with constructors.

Listing 4-7 A solution validator class must inherit from SPSolutionValidator.

[Guid("D1735DCC-141F-4F1A-8DFE-8F3F48DACD1F")]
public class SimpleSolutionValidator : SPSolutionValidator {
    [Persisted]
    List<string> allowedPublishers;

    private const string validatorName = "Simple Solution Validator";

    public SimpleSolutionValidator() { }
    public SimpleSolutionValidator(SPUserCodeService userCodeService)
        : base(validatorName, userCodeService) {
        this.Signature = 5555;
    }
}

After coding the basic class and constructors, you must override the ValidateSolution and ValidateAssembly methods. ValidateSolution is called once for each solution, and ValidateAssembly is called once for each assembly within each solution. The ValidateSolution method receives a SPSolutionValidationProperties object, which contains information about the solution. The ValidateAssembly method receives a SPSolutionValidationProperties object as well, but it also receives a SPSolutionFile object with additional information about the assembly being validated. Listing 4-8 shows the ValidateSolution and ValidateAssembly methods, which for the sake of this demonstration are simply checking to see whether the name of any file in the solution or assembly begins with the string "Bad_". If any file begins with this string, the file fails validation.

Listing 4-8 A solution validator prevents a sandboxed solution from activating when the isValid property is set to true.

public override void ValidateSolution(SPSolutionValidationProperties properties) {
    base.ValidateSolution(properties);
    bool isValid = true;
    
    //Check the name of the package
    if (properties.PackageFile.Location.StartsWith("Bad_",
                               StringComparison.CurrentCultureIgnoreCase)) {
        isValid = false;
    }

    //Look at the files in the package
    foreach (SPSolutionFile file in properties.Files)  {
        if (file.Location.StartsWith("Bad_", 
                                     StringComparison.CurrentCultureIgnoreCase))
            isValid = false;
    }

    //set error handler
    properties.ValidationErrorMessage = "Failed simple validation.";
    properties.ValidationErrorUrl = 
       "/_layouts/Simple_Validator/ValidationError.aspx?SolutionName=" 
       + properties.Name;
    properties.Valid = isValid;
}

public override void ValidateAssembly(
    SPSolutionValidationProperties properties, SPSolutionFile assembly) {
    base.ValidateAssembly(properties, assembly);
    bool isValid = true;

    //Check the name of the assembly
    if (assembly.Location.StartsWith("Bad_", 
                 StringComparison.CurrentCultureIgnoreCase))
        isValid = false;

    //set error handler
    properties.ValidationErrorMessage = "Failed simple validation.";
    properties.ValidationErrorUrl = 
           "/_layouts/Simple_Validator/ValidationError.aspx?SolutionName="
           + properties.Name;
    properties.Valid = isValid;
}

When a solution fails validation, you can elect to display an error page. The ValidationErrorMessage and ValidationErrorUrl properties are used to set the values for handling validation errors. Typically, you simply create an application page in the LAYOUTS directory that is called when validation fails. Figure 4-13 shows a message being displayed from a custom application page when a solution fails validation.

Figure 4-13 You can redirect the user to a custom error page when solution validation fails.

Redirect the user to a custom error page.

Before a custom solution validator can be used, it must be registered with the farm. To register the validator with the farm, you use a Feature Receiver in a farm-level feature. In fact, it's best to package the custom validator, application page, and Feature Receiver into a single feature. This way, the farm administrator can simply activate a single farm-level feature and the validator will be active. Listing 4-9 shows a Feature Receiver for registering and unregistering a custom validator.

Listing 4-9 A Feature Receiver can be used to register a solution validator.

public class FeatureEventReceiver : SPFeatureReceiver {
  public override void FeatureActivated(SPFeatureReceiverProperties properties) {
    SPUserCodeService userCodeService = SPUserCodeService.Local;
    SPSolutionValidator validator = new SimpleSolutionValidator(userCodeService);
    userCodeService.SolutionValidators.Add(validator);
  }

  public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
    SPUserCodeService userCodeService = SPUserCodeService.Local;
    SPSolutionValidator validator = new SimpleSolutionValidator(userCodeService);
    userCodeService.SolutionValidators.Remove(validator.Id);
  }
}

Using Windows PowerShell for Administration

In addition to using Central Administration, farm administrators can also use Windows PowerShell to perform administrative tasks for sandboxed solutions. The tasks could include reporting as well as changing key settings. For example, the following Windows PowerShell code shows the command and displays the quota settings for Wingtip.com in the SharePoint 2010 Management Console. The properties UserCodeMaximumLevel and UserCodeWarningLevel map to the Maximum Usage per Day and Warning E-mail values in Central Administration, respectively.

(Get-SPSite -Identity "http://wingtip.com").Quota
QuotaID                     : 0
StorageMaximumLevel         : 0
InvitedUserMaximumLevel     : 0
StorageWarningLevel         : 0
UserCodeWarningLevel        : 100
UserCodeMaximumLevel        : 300
UpgradedPersistedProperties:

Administrators can also change the quota settings for a site collection by setting the UserCodeMaximumLevel and UserCodeWarningLevel properties through Windows PowerShell. They would simply set the properties to the number of points desired. The following code shows how to set the Wingtip.com quota values back to the default.

(Get-SPSite -Identity "http://wingtip.com").Quota.UserCodeMaximumLevel = 300
(Get-SPSite -Identity "http://wingtip.com").Quota.UserCodeWarningLevel = 100

Along with viewing the quotas, you can also list the currently active solution validators. This is a great way for administrators to verify the validations that are in effect. The following command lists all the active solution validators.

[System.Reflection.Assembly]::Load(
"Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
$s = [Microsoft.SharePoint.Administration.SPUserCodeService]::Local;
$s.solutionvalidators;

If you're interested in understanding more about the 14 measures that make up resource points, you can list them and set their values with Windows PowerShell. The following command lists all the measures.

[System.Reflection.Assembly]::Load(
"Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
$s = [Microsoft.SharePoint.Administration.SPUserCodeService]::Local;
$s.ResourceMeasures;

If you want to see all the blocked APIs in the farm, you can list them as well as add new blocked types. The following command lists all the blocked APIs.

[System.Reflection.Assembly]::Load(
"Microsoft.SharePoint,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c")
$f = Get-SPFarm
$c = New-Object Microsoft.SharePoint.Administration.SPWebServiceCollection($f)
$c | foreach-object{$_.RestrictedObjectModel}

Using Site Collection Tools

Site Collection Administration is accomplished primarily through the Solution Gallery. From the Solution Gallery, a site collection administrator can upload, activate, and deactivate solution files. Site collection administrators can also monitor the current resource usage for the day as well as a rolling 14-day average. Figure 4-14 shows the site collection Solution Gallery with several installed solutions and their resource usage.

Figure 4-14 The site collection administrator can monitor resource usage for sandboxed solutions.

Monitor resource usage for solutions.

Conclusion

Sandboxed solutions represent a new form of solution deployment and management in SharePoint 2010. Because these solutions balance agility with farm stability, you should consider sandboxed solutions in your initial development efforts. Only if a sandboxed solution doesn't work should you move to another deployment model. You can't use the sandbox to deploy every conceivable SharePoint solution, but you can run many common solutions in the sandbox. Therefore, you should consider sandboxed solutions a best practice for SharePoint development.

The authors of this book have created CodePlex projects that have additional solutions and resources for sandboxed solutions. You can find these solutions at http://sandbox.codeplex.com and http://ckssandboxsolutions.codeplex.com.

Additional Resources

For more information, see the following resources:

About the Authors

  • MVP ContributorTed Pattison is an author, instructor, and co-founder of Critical Path Training, a company dedicated to education on SharePoint technologies. As a Microsoft SharePoint Most Valuable Professional (MVP), Ted frequently works with the Microsoft Developer Platform Evangelism group to research and author SharePoint training material for developers early in the product life cycle while in its alpha and beta stages. Ted also writes a developer-focused column for MSDN Magazine titled Office Space.

  • MVP ContributorMVP Andrew Connell is an author, instructor, and co-founder of Critical Path Training, a SharePoint education-focused company. Andrew is a six-time recipient of Microsoft’s Most Valuable Professional (MVP) award (2005-2010) for Microsoft Content Management Server (MCMS) and Microsoft SharePoint Server. He authored and contributed to numerous MCMS and SharePoint books over the years including his book Professional SharePoint 2007 Web Content Management Development by WROX. Andrew speaks about SharePoint development and WCM at conferences such as Tech-Ed, SharePoint Connections, VSLive, SharePoint Best Practice Conference, SharePoint Evolutions Conference, Office Developer Conference, and Microsoft SharePoint Conference in the United States, Australia, England, and Spain. Andrew blogs at Andrew Connell Blog and on Twitter @andrewconnell.

  • MVP ContributorScot Hillier is an independent consultant and Microsoft SharePoint Most Valuable Professional (MVP) focused on creating solutions for Information Workers with SharePoint, Office, and related .NET Framework technologies. He is the author/coauthor of 15 books and DVDs on Microsoft technologies, including Inside Microsoft SharePoint 2010 and Professional Business Connectivity Services. Scot splits his time between consulting on SharePoint projects, speaking at SharePoint events like Tech-Ed, and delivering training for SharePoint developers. Scot is a former U.S. Navy submarine officer and graduate of the Virginia Military Institute. Scot can be reached at scot@shillier.com.