Chapter 2: SharePoint Foundation Development (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 2, SharePoint Foundation Development, 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.
Contents
SharePoint Foundation Development
The Server-Side Object Model
Creating the Hello World Console Application
Enabling the SharePoint Developer Dashboard
Developing SharePoint Solutions
Developing a SharePoint Solution Using a Class Library Project
The SharePoint Root Directory
Creating a Feature Definition
Creating a Solution Package
Feature Upgrade Enhancements in SharePoint 2010
Conclusion
Additional Resources
About the Authors
SharePoint Foundation Development
Did you notice that Chapter 1 doesn't include a single example using Microsoft Visual Studio? That omission was intentional. We want to make sure you first understand the fundamental concepts and terminology of Microsoft SharePoint Foundation and site customization before diving into a development environment. After all, it doesn't make sense to start writing code before you know what's possible without writing code.
Now we're ready to launch Visual Studio 2010 and start writing code. In this chapter, we're going to create several Visual Studio projects to demonstrate the fundamental development techniques for SharePoint Foundation. We'll do a few quick warm-up exercises by creating console applications that program against the server-side object model. After that, we concentrate on the central topic of this chapter: developing a SharePoint solution in Visual Studio.
Developer productivity takes a giant step forward in SharePoint 2010 because of a powerful new set of tools known as the SharePoint Developer Tools for Visual Studio 2010. These tools provide project templates and project item templates explicitly designed for SharePoint Foundation development. The SharePoint Developer Tools are valuable because they manage and hide so many of the tedious details of developing in SharePoint Foundation.
Although the SharePoint Developer Tools represent one of the most significant enhancements to the SharePoint development platform, we're going to hold off delving into them until Chapter 3. In this chapter, we create Visual Studio projects based on standard console applications and class libraries. This approach will show you the essential aspects of SharePoint Foundation development. It will also give you the background you need to fully appreciate what the Visual Studio 2010 SharePoint Tools are doing for you behind the scenes.
The Server-Side Object Model
The core server-side object model of SharePoint Foundation is served up through an assembly named Microsoft.SharePoint.dll. Once you reference this assembly within a Visual Studio 2010 project, you can start programming against the classes in the server-side object model, such as SPSite, SPWeb, and SPList. In SharePoint 2007, developers generally referred to the public classes in the Microsoft.SharePoint assembly as the "object model." Nobody felt the need to qualify the term by saying "server-side" object model because there was only one object model. SharePoint Foundation introduces a new client-side object model. Therefore, we must now differentiate between the two models using the terms server-side object model and client-side object model. In this chapter, we focus on the server-side object model. In Chapter 10, we concentrate on the client-side object model.
There are two initial requirements for a Visual Studio project that programs against the server-side object model using the Microsoft.SharePoint assembly. First, the project must be configured to use .NET Framework 3.5 as its target framework. You have to be careful because Visual Studio 2010 uses a default value of .NET Framework 4.0 for many of the built-in project templates. The second requirement is that your project must have a platform target setting that is compatible with a 64-bit environment, which is essential for properly loading the Microsoft.SharePoint assembly.
Another critical requirement for any application or component that is programmed against the server-side object model is that the application or component must be deployed and run on a front-end Web server or application server in a SharePoint farm. This issue doesn't usually come up when you're creating standard SharePoint components such as Feature Receivers, event receivers, Web Parts, and workflow templates because these component types are deployed using SharePoint solutions, which make it possible to install and execute them on all front-end Web servers in a SharePoint farm.
You can also create client applications with Visual Studio 2010 that program against the server-side object model. For example, you can create a standard console application that uses the server-side object model to access a site and the elements inside the site, such as lists and items. However, you must keep in mind that any client application that depends on the Microsoft.SharePoint assembly can be run only when launched on a Web server in a SharePoint farm.
The key point here is that you won't likely encounter real-world scenarios that call for creating client applications that use the server-side object model. Even so, creating simple console applications that program against the Microsoft.SharePoint assembly can be useful because it gives you a quick and easy way to write and test code as you begin learning the serverside object model. Furthermore, these types of applications will run without issue on any SharePoint 2010 developer workstation that's been configured as a single-server farm.
Creating the Hello World Console Application
Let's start by creating the traditional Hello World application. Launch Visual Studio 2010, and create a new project named HelloSharePoint based on the Console Application project template. Make sure you create the new project based on .NET Framework 3.5 and not .NET Framework 4.0. After creating the project, you can verify this setting by navigating to the Application tab of the Project Properties dialog and inspecting the Target framework property, as shown in Figure 2-1.
Figure 2-1 Visual Studio 2010 projects that use the server-side object model must target .NET Framework 3.5.
After you've verified that your project has the correct Target framework setting, you must navigate to the Build tab of the Project Properties dialog and change the Platform target setting to a value that is compatible with 64-bit applications. The default Platform target setting for a new console application is x86, which causes the application to load as a 32-bit application instead of a 64-bit application, causing strange and misleading error messages when the application attempts to load and use the Microsoft.SharePoint assembly. Make sure you set the Platform target property for your console application to a value of either x64 or Any CPU, as shown in Figure 2-2.
Figure 2-2 A console application that loads the Microsoft.SharePoint assembly should have a Platform target setting of either x64 or Any CPU.
Now that you've properly configured the console application's project settings, you can add a project reference to the Microsoft.SharePoint assembly, as shown in Figure 2-3. Once you've added this reference, you can begin programming against the SharePoint server-side object model.
Figure 2-3 You must reference the Microsoft.SharePoint assembly to access the server-side object model.
Examine the code for the HelloSharePoint console application shown in Listing 2-1. This code demonstrates how to program against commonly used classes defined inside the Microsoft.SharePoint namespace, such as SPSite, SPWeb, and SPList.
Listing 2-1 A Windows console application accesses a SharePoint site through the server-side object model.
using System;
using Microsoft.SharePoint;
namespace HelloSharePoint {
class Program {
static void Main() {
const string siteUrl = "http://intranet.wingtip.com";|
using (SPSite siteCollection = new SPSite(siteUrl)) {
SPWeb site = siteCollection.RootWeb;
foreach (SPList list in site.Lists) {
if (!list.Hidden) {
Console.WriteLine(list.Title);
}
}
}
}
}
}
The code in Listing 2-1 creates a new SPSite object with a URL pointing to a specific site collection on the local farm. A console application should use this technique to gain an entry point into the server-side object model. Next, the code accesses the SPSite object's RootWeb property to gain access to the top-level site within the current site collection. Finally, the code enumerates through all the lists in this site using a foreach loop and writes the Title property value of each nonhidden list to the console window.
When you create SPSite objects using the new operator, you must dispose of them when you're done using them. The reason for this is that SPSite objects allocate unmanaged resources behind the scenes and consume a significant amount of unmanaged memory. If you create SPSite objects with the new operator in your code and you don't properly dispose of them, your code will leak memory and can cause serious performance problems in a production farm. As a rule of thumb, when you create SPSite objects using the new operator, you must properly dispose of them either by creating them within a using construct or by making an explicit call to the Dispose method exposed by the SPSite class.
Now that you've written your first application that leverages the server-side object model, you can test it to make sure it works. If you press the F5 key to start the application in debug mode, the application will run and complete without pausing, making it difficult to see what is being displayed to the console window because the item disappears almost as soon as it is displayed.
If you start the application using the Ctrl+F5 keyboard combination, the application goes into standard execution mode instead of debug mode. In standard mode, the application pauses with the console window remaining open after the code has run, so you can see whatever the code has written to the console window. If you want to run the application in debug mode and still see output in the console window, you should either set breakpoints in your code or add a call to Console.ReadLine at the end of the Main method to pause the application and keep the console window open after the code completes its execution.
Enabling the SharePoint Developer Dashboard
A new infrastructure component that has been added to SharePoint 2010 is the Developer Dashboard. The Developer Dashboard is a user interface component built into SharePoint Foundation that displays diagnostic information that can be useful to developers and to farm administrators in diagnosing problems with the farm. Figure 2-4 shows the Developer Dashboard and some of the diagnostic information it displays about the current request.
Figure 2-4 SharePoint Foundation provides the Developer Dashboard as a built-in diagnostics component.
By default, the Developer Dashboard is disabled. A quick and easy way to enable and disable the Developer Dashboard is by writing a console application that configures a few farm-wide properties through the server-side object model. Inside the Microsoft.SharePoint.Administration namespace is the class SPWebService, which exposes the ContentService property. The ContentService property exposes another property, DeveloperDashboardSettings, which holds a reference to an object of type SPDeveloperDashboardSettings. By accessing this object, you can enable the Developer Dashboard on a farm-wide basis by updating two properties: DisplayLevel and TraceSettings. Doing so causes the Developer Dashboard to display at the bottom of every page farm-wide.
using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace EnableDeveloperDashboard {
class Program {
static void Main() {
SPDeveloperDashboardSettings settings =
SPWebService.ContentService.DeveloperDashboardSettings;
settings.DisplayLevel = SPDeveloperDashboardLevel.On;
settings.TraceEnabled = true;
settings.Update();
}
}
}
After you've successfully enabled the Developer Dashboard, you probably want to figure out how to disable it so that it doesn't appear at the bottom of every page. You just need to modify the preceding code. Simply update the DisplayLevel property with a value of SPDeveloperDashboardLevel.Off instead of SPDeveloperDashboardLevel.On. Once you've made this change, rerun your console application to disable the Developer Dashboard.
Developing SharePoint Solutions
You've already seen how to leverage the server-side object model in a Visual Studio project by adding a reference to the Microsoft.SharePoint assembly. Up to this point, we've used examples based on console applications so that we could focus on the server-side object model with a minimal amount of complexity. Now it's time to start developing SharePoint solutions.
When developing for SharePoint 2010, you're required to package and deploy your development efforts using a SharePoint solution. At the physical level, a SharePoint solution is a set of files from a development effort that are compressed into a single CAB file known as a solution package. The file name for a solution package must have an extension of .wsp.
The strategy of packaging and distributing each SharePoint solution as a single selfcontained file is important because it leads to an easier and less error-prone approach to deploying and updating a custom development effort in one or more SharePoint farms.
Solution packages were introduced in SharePoint 2007. However, SharePoint 2007 allows you to deploy a solution package only at the farm level. SharePoint 2010 provides greater flexibility. You can deploy a solution package in SharePoint 2010 as a farm solution, which is consistent with how deployment works in SharePoint 2007, or as a sandboxed solution, which reduces the scope of deployment from the level of the farm to that of a single site collection.
When you plan to develop a new SharePoint solution, you should consider whether you'll ever want to deploy the resulting solution package as a sandboxed solution. If you do, you must learn how to create a SharePoint solution that can operate within the restrictions of the sandbox. In Chapter 4, we examine sandboxed solutions in greater depth and answer questions about why, when, and how to use them. Until then, we'll focus on developing SharePoint solutions that will be deployed only as farm solutions.
Developing a Solution for Wingtip Toys
Imagine that you're a developer working at Wingtip Toys. Chris Sells, the Wingtip Sales Director, has asked you to develop a SharePoint solution to assist his salespeople by automating the creation of a new list dedicated to tracking incoming sales leads. Your mission is to build a SharePoint solution that includes a feature to automate the creation of this new list for tracking sales leads.
Developing a SharePoint Solution Using a Class Library Project
You typically begin developing a SharePoint solution by creating a new Visual Studio project. The project will contain the source files for your SharePoint solution and should be configured to build a solution package as its output. The project should also contain integrated commands that allow Visual Studio to interact with the SharePoint Foundation to install and deploy the project's output solution package for testing.
For the remainder of this chapter, we'll develop a SharePoint solution using a standard class library project. The main goal of this exercise is to build your theoretical understanding of things like feature definitions and solution packages. This exercise will also give you an opportunity to improve your ability to write Windows PowerShell scripts and to integrate them with Visual Studio 2010.
Just keep in mind that the approach we're using in this chapter—creating a class library project to develop a SharePoint solution—is something you probably won't do in the real world. Starting in Chapter 3, we'll begin using the approach you'll use in your actual projects targeting SharePoint 2010. This effort will involve creating new projects to develop solutions for SharePoint 2010 using the SharePoint Developer Tools for Visual Studio 2010.
The sample code that accompanies this chapter (which you can find at http://www.CriticalPathTraining.com/books/InsideSharePoint2010) contains a class library project named WingtipDevProject1. You can see the high-level structure of this project by examining Figure 2-5, which shows a view of the project in Solution Explorer.
Figure 2-5 WingtipDevProject1 is a class library project created to develop a SharePoint solution.
The sample project WingtipDevProject1 contains the type of source files you typically add into a Visual Studio project when developing a SharePoint solution. WingtipDevProject1 has been developed for a SharePoint solution containing custom image files, XML definitions, and C# code compiled into a .NET assembly. To understand why the project is structured in this fashion, you must first understand the purpose and structure of the SharePoint root directory.
The SharePoint Root Directory
The fundamental architecture of SharePoint Foundation relies on a set of template files that are stored in a special directory on the local file system of each front-end Web server. In SharePoint 2007, this directory went by several different names, including the SharePoint System directory and the 12 Hive. In SharePoint 2010, the SharePoint product team has formalized the name of this directory to be the SharePoint root directory.
A typical installation of SharePoint Foundation or SharePoint Server 2010 creates the SharePoint root directory at the following path.
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14
If you've installed only SharePoint Foundation and not SharePoint Server 2010, the SharePoint root directory contains a stock set of images, templates, features, and pages. If you install SharePoint Server 2010, the SharePoint root directory contains the same stock files of SharePoint Foundation and also a wealth of template files to provide addition functionality beyond that supplied by SharePoint Foundation.
When creating a SharePoint solution that is to be deployed as a farm solution, many of the types of template files that you add to your solution must be deployed in specific directories within the SharePoint root directory. Table 2-1 lists some of the more commonly used directories along with the types of template files they contain.
Table 2-1. SharePoint Solution File Locations Inside the SharePoint Root Directory
Path Relative to SharePoint Root Directory |
Template File Types |
---|---|
/ISAPI |
Web Services (.svc, .ashx, .asmx) |
/Resources |
Resource files (.resx) |
/TEMPLATE/ADMIN |
Application pages used exclusively in Central Administration |
/TEMPLATE/CONTROLTEMPLATES |
ASP.NET User Controls (.ascx) |
/TEMPLATE/FEATURES |
Feature definition files (.xml) |
/TEMPLATE/IMAGES |
Images (.gif, .jpg, and .png) |
/TEMPLATE/LAYOUTS |
Application pages (.aspx) |
/TEMPLATE/LAYOUTS/1033/STYLES |
CSS Files (.css) |
/TEMPLATE/LAYOUTS/ClientBin |
Silverlight components (.xap) |
/TEMPLATE/SiteTemplates |
Site Definition files (onet.xml) |
/TEMPLATE/XML |
Custom field type definition files (fdltype*.xml) |
If you examine the project structure of WingtipDevProject1 shown in Figure 2-5, you notice that the project contains a top-level directory named SharePointRoot. Inside the SharePointRoot directory is a directory named TEMPLATE, and within TEMPLATE are two child directories, FEATURES and IMAGES. The key point is that this Visual Studio project contains a SharePointRoot directory with child directories that mirror the layout of the real SharePoint root directory.
Warning
The development technique of creating a SharePointRoot directory inside your project is optional, but it became a popular approach used in SharePoint 2007 development. When we get to Chapter 3, we'll abandon this approach in favor of an easier project structure created by the SharePoint Developer Tools. For now, we'll continue with this approach because it provides an effective way to teach you about deploying files from a farm solution into specific locations inside the SharePoint root directory.
Let's examine the two image files in WingtipDevProject1: FeatureIcon.gif and SiteIcon.gif. These files are being deployed inside the SharePoint IMAGES directory. However, note that FeatureIcon.gif and SiteIcon.gif aren't being deployed directly inside the IMAGES directory but instead inside an inner directory that has been given the same name as the project. Deploying images files in this fashion—inside a solution-specific directory within the IMAGES directory—is considered a best practice because it allows you to avoid file name conflicts with any of the stock image files that Microsoft deploys inside the IMAGES directory.
Creating a Feature Definition
Although you can create many different types of components within a SharePoint solution, you really should start by learning about feature definitions. A feature definition provides a mechanism for adding elements to a target site or site collection through a process known as feature activation. The types of elements that can be added to a site include menu commands, link commands, page templates, page instances, list definitions, list instances, event handlers, and workflows.
Many SharePoint developers use the terms feature definition and feature interchangeably. In this chapter, we stick with the term feature definition because it conveys the fact that you're dealing with a type of template that is used to create instances. Once you understand that a feature definition is really a template, you'll find it easier to differentiate between feature definitions and feature instances.
So what is the difference between a feature definition and a feature instance? The feature definition is the set of source files in your Visual Studio 2010 project that gets deployed using a solution package. Once deployed, a feature definition is a set of template files and components that reside on each front-end Web server. A feature instance is what gets created when a user activates the feature definition. SharePoint Foundation creates and maintains feature instances with entries in either a content database or the configuration database, depending on the activation scope of the underlying feature definition.
At a physical level, a feature definition is implemented with a set of files that are deployed within a dedicated directory created inside the FEATURES directory. More specifically, a feature's directory contains one or more XML-based files that contain Collaborative Application Markup Language (CAML). The only file required for every feature definition is the feature manifest file, which must be named feature.xml and must be placed at the root of the feature's directory.
In addition to the feature.xml file, a feature definition often contains one or more XML files known as element manifests that define the elements that make up the feature definition. The directory for a feature definition can also contain several other types of files for elements such as list definitions and page templates as well as other kinds of resources, such as image files, Cascading Style Sheets (CSS) files, and JavaScript files.
One valuable technique for getting up to speed on features is to examine the standard set of feature definitions that ship as part of the basic SharePoint Foundation installation.An example of what the FEATURES directory looks like after you've installed SharePoint Foundation is shown in Figure 2-6. As you can see from this screenshot, the FEATURES directory contains 54 directories, each one representing one of the built-in feature definitions that gets installed along with SharePoint Foundation. If you've installed SharePoint Server 2010, the number of feature definitions included out of the box in the FEATURES directory increases from 54 to 266.
Figure 2-6 The installation of SharePoint Foundation includes a set of 54 built-in features.
Creating the Feature.xml File
When you want to create a new feature definition, you must create a directory inside the FEATURES directory, and directly inside this directory, you must create a file with the name feature.xml. Examine the XML definition in Listing 2-2, which shows the starting point for the feature.xml file used to create the Wingtip Lead Tracker feature definition.
Listing 2-2 Every feature definition requires a feature.xml file, which serves as the feature manifest.
<Feature
Id="86689158-7048-4421-AD21-E0DEF0D67C81"
Title="Wingtip Lead Tracker"
Description="A sample feature deployed using WingtipDevProject1.wsp"
Version="1.0.0.0"
Scope="Web"
Hidden="FALSE"
ImageUrl="WingtipDevProject1/FeatureIcon.gif"
xmlns="https://schemas.microsoft.com/sharepoint/" >
<ElementManifests>
<ElementManifest Location="elements.xml" />
</ElementManifests>
</Feature>
You see that a feature definition is defined in XML using a top-level Feature element containing attributes such as Id, Title, Description, Version, Scope, Hidden, and ImageUrl. You must create a new GUID for the Id attribute so that your feature can be uniquely identified. You should create the feature definition's Title and Description attributes using user-friendly text. These attributes are shown directly to the users on the SharePoint Foundation feature management pages, where users are able to activate features.
The Scope of a feature definition defines the context in which the feature instances can be activated and deactivated. The Wingtip Lead Tracker feature definition has a Scope attribute setting of Web, which means it can be activated and deactivated within the context of a site. If you assign a Scope value of Site, your feature definition is then activated and deactivated within the scope of a site collection. The two other possible activation scopes for a feature definition are WebApplication and Farm.
As you can see, the Hidden attribute has a value of FALSE. This means that once installed within the farm, the feature definition can be seen by administrators who might want to activate it. You can also create a feature definition where the Hidden attribute has a value of TRUE, which has the effect of hiding the feature definition in the list of available features shown to administrators. Users can't directly activate hidden feature definitions. Instead, a farm administrator must activate them by using a Windows PowerShell script, through a development technique such as using a feature activation dependency, or by writing code against the server-side object model.
You can also see that the ImageUrl attribute of this feature definition points to the custom image file named FeatureIcon.gif that is part of the same project. The setting for the ImageUrl property in a feature.xml file should be set relative to the IMAGES directory.
The feature.xml file for the Wingtip Lead Tracker feature definition contains a reference to an element manifest named elements.xml. The reference to elements.xml is added using an ElementManifests element with an inner ElementManifest element whose Location attribute points to elements.xml. In the following section, we explain the purpose of element manifests and how to work with them.
Element Manifests
A feature definition can have one or more element manifests. An element manifest is an XML file that contains a set of declarative elements defined using CAML. SharePoint Foundation inspects the element manifest during feature activation and provisions an object in the target site for each defined element.
Every element manifest must have a top-level element named Elements. Inside Elements, you add declarative elements. The elements.xml file in WingtipDevProject1, which is shown in Listing 2-3, contains a single child element of type ListInstance, which provisions a new list in the target site when the feature definition is activated. This list is created using the Contacts list type built into SharePoint Foundation and has the title Sales Leads.
Listing 2-3 An element manifest file contains an Elements node with one or more nested elements inside.
<Elements xmlns=https://schemas.microsoft.com/sharepoint/ >
<ListInstance
FeatureId="00BFEA71-7E6D-4186-9BA8-C047AC750105"
TemplateType="105"
Id="SalesLeads"
Title="Sales Leads"
Url="SalesLeads"
OnQuickLaunch="TRUE"
/>
</Elements>
The ListInstance element contains several attributes that SharePoint Foundation uses as parameterized values when it creates the new list. The FeatureId attribute contains a GUID that identifies the feature definition in which the built-in Contacts list type is defined. TheTemplateType attribute contains the integer identifier for the Contacts list type, which is 105. The remaining four attributes provide SharePoint Foundation with initialization settings for the list instance being created.
This example contains a single declarative element, but you have the flexibility to add multiple elements inside the Elements section of an element manifest. If you like, you can also extend the feature definition by adding element manifests. If you're developing a feature definition in which you need to add multiple declarative elements, you have a choice. You can add all the elements to a single element manifest, or you can spread them across two or more element manifests.
Throughout this book, we'll continue to explain the various types of declarative elements you can use in element manifests when developing a feature definition. Table 2-2 lists the most common element types used when developing feature definitions, along with a brief description of each element type.
Table 2-2. Common Element Types Used When Developing a Feature Definition
Element Type |
Description |
---|---|
PropertyBag |
Adds name-value properties to a feature |
ListInstance |
Creates a list instance |
CustomActionGroup |
Creates a new section for links |
CustomAction |
Creates a new link or menu command |
HideCustomAction |
Hides a built-in or custom link or menu command |
Module |
Provisions a file from a template file |
Field |
Creates a site column |
ContentType |
Creates a content type |
ContentTypeBinding |
Adds a content type to a list |
ListTemplate |
Creates a custom list type |
Control |
Creates a delegate control |
Workflow |
Creates a workflow template |
WorkflowActions |
Creates declarative workflows |
WorkflowAssociation |
Associates a workflow template with a list |
FeatureSiteTemplateAssociation |
Staples a second feature to a site template to supportauto-activation |
Adding a Feature Receiver
In addition to declarative elements, you can also extend a feature definition with a Feature Receiver. A Feature Receiver allows you to write event handlers in a managed programming language such as C# or Visual Basic. These event handlers are executed during feature-specific events such as feature activation and feature deactivation. The code you write in these event handlers can access the SharePoint Foundation server-side object model, making it possible to modify the target site. For example, you can write an event handler that fires during feature activation and performs routine initialization tasks such as creating new lists and adding list items. You can also extend the site by adding pages, navigation links, and Web Part instances.
To create a Feature Receiver, you start by adding a new class to your project that inherits from the SPFeatureReceiver class defined in the Microsoft.SharePoint namespace. Next, you override methods defined in SPFeatureReceiver, such as FeatureActivated and FeatureDeactivating. Listing 2-4 shows how the Feature Receiver class has been implemented for the Wingtip Sales Leads feature definition.
Listing 2-4 You create a Feature Receiver by inheriting from the SPFeatureReceiver class and overriding event handler methods.
public class FeatureReceiver : SPFeatureReceiver {
public override void FeatureActivated(SPFeatureReceiverProperties props) {
SPWeb site = props.Feature.Parent as SPWeb;
if (site != null) {
site.Title = "Feature Activated";
site.SiteLogoUrl = @"_layouts/images/WingtipDevProject1/SiteIcon.gif";
site.Update();
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties props) {
SPWeb site = props.Feature.Parent as SPWeb;
if (site != null) {
site.Title = "Feature Deactivated";
site.SiteLogoUrl = "";
site.Update();
SPList list = site.Lists.TryGetList("Wingtip News");
if (list != null) {
list.Delete();
}
}
}
}
The FeatureActivated method in this listing has been written to update the current site's Title and SiteLogoUrl properties. The site's Title property is updated to Feature Activated during feature activation in this example to give you a quick way to test the feature definition and get visual feedback that the code actually ran. The update to the SiteLogoUrl property will replace the stock site logo image in the upper left corner of the target site with the custom image file SiteIcon.gif.
Notice the technique used to obtain a reference to the current site. A parameter of type SPFeatureReceiverProperties is passed to the FeatureActivated method. You can use this parameter to acquire a reference to the SPWeb object associated with the target site. The SPFeatureReceiverProperties parameter exposes a Feature property that in turn exposes a Parent property that holds a reference to the current site. After updating the Title and SiteLogoUrl properties of the SPWeb object, the code must also call the Update method to save these changes back to the content database.
The FeatureDeactivating method in this example updates the value of the site's Title property to Feature Deactivated and updates the value of the SiteLogoUrl property back to an empty string. Assigning an empty string value has the effect of returning the site logo to the stock site logo image used by SharePoint Foundation. The code in the FeatureDeactivating method also deletes the Sales Leads list, which was declaratively provisioned during feature activation.
Those of you moving from SharePoint 2007 should take note of the TryGetList method that was added to the server-side object model in SharePoint 2010. In versions prior to SharePoint 2010, this method didn't exist, and developers usually resorted to enumerating through the Lists collection of a site to determine whether a list existed.
The code for a Feature Receiver must be compiled into a .NET assembly and deployed in the global assembly cache (GAC). The fact that the assembly requires deployment in the GAC means that you must add a signing key file to the project to sign the resulting output DLL with a strong name during compilation. If you look back at Figure 2-5, you can see that WingtipDevProject1 has a signing key file named WingtipSigningKey.snk.
The next step to integrating a Feature Receiver is to update the Feature element inside the feature.xml file with two new attributes. You must do this to inform SharePoint Foundation that the feature definition has an associated Feature Receiver. More specifically, you should add the ReceiverAssembly and ReceiverClass attributes as shown in the following example.
<Feature Id="86689158-7048-4421-AD21-E0DEF0D67C81"
Title="Wingtip Lead Tracker"
Description="A sample feature deployed using WingtipDevProject1.wsp"
Version="1.0.0.0"
Scope="Web"
Hidden="FALSE"
ReceiverAssembly="WingtipDevProject1, [four-part assembly name]"
ReceiverClass="WingtipDevProject1.FeatureReceiver"
ImageUrl="WingtipDevProject1/FeatureIcon.gif"
xmlns="https://schemas.microsoft.com/sharepoint/" >
<ElementManifests>
<ElementManifest Location="elements.xml" />
</ElementManifests>
</Feature>
The ReceiverAssembly attribute contains a four-part assembly name, which in most cases is the name of the assembly being built by the current project. Keep in mind that the assembly must be installed in the GAC prior to feature activation to work properly. The ReceiverClass attribute contains the namespace-qualified name of a public class within the receiver assembly.
Any class you reference with the ReceiverClass attribute must inherit from SPFeatureReceiver. When a feature definition with a Feature Receiver is activated, SharePoint Foundation loads the target assembly and dynamically creates an object from the class referenced by the ReceiverClass attribute. Once SharePoint Foundation has created the Feature Receiver object, it performs a type conversion to SPFeatureReceiver. If your Feature Receiver class doesn't inherit from SPFeatureReceiver, the SharePoint Foundation will experience a type conversion error at run time, causing your feature definition to fail during feature activation. At this point, we've walked through the entire implementation of the Wingtip Lead Tracker feature definition. Your next challenge is to learn how to deploy and test this feature definition.
In the next section, we discuss how to prepare this feature definition and the accompanying images files for deployment using a solution package.
Creating a Solution Package
Because SharePoint solutions are deployed in farms that range in size from a single standalone Web server to large enterprise server farms, you need an easy and reliable mechanism to deploy them as a single unit. By deploying a single unit, you can have a supported, testable, and repeatable deployment mechanism. The deployment mechanism that SharePoint uses is the solution package.
As mentioned earlier in the chapter, a solution package is a compressed CAB file with a .wsp extension that contains the set of template files and components to be deployed on one or more front-end Web servers. Every solution package contains metadata that enables SharePoint Foundation to unpack the .wsp file and install its template files and components. In server farm installations, SharePoint Foundation is able to automate pushing the solution package file out to each Web server in the farm.
Solution packages are deployed in two steps. The first step is installation, in which SharePoint Foundation adds a copy of the .wsp file to the configuration database. The second step is the deployment, in which SharePoint Foundation creates a timer job that is processed by all front-end Web servers in the server farm. The key takeaway here is that deploying SharePoint solutions using solution packages greatly simplifies installation across a farm of Web servers and ensures a consistent deployment in a variety of scenarios.
The Manifest.xml File
The metadata for a solution package is maintained in a solution manifest file that must be named manifest.xml. When you deploy a solution package, SharePoint Foundation inspects manifest.xml to determine which template files it needs to copy into the SharePoint root directory. The metadata inside manifest.xml can also instruct SharePoint Foundation to install an assembly in the GAC and to install feature definitions in the local farm. Installing a feature definition requires updates to the configuration database in addition to copying feature definition files into the proper location in the SharePoint root directory.
Let's take a look at a simple solution manifest. Examine the code in Listing 2-5, which shows the manifest.xml file from WingtipDevProject1.
Listing 2-5 The manifest.xml file is the solution manifest, which provides installation instructions to the SharePoint Foundation installer.
<Solution
SolutionId="0cee8f44-4892-4a01-b8f4-b07aa21e1ef2"
Title="Wingtip Dev Project 1"
DeploymentServerType="WebFrontEnd"
ResetWebServer="True"
xmlns="https://schemas.microsoft.com/sharepoint/">
<FeatureManifests>
<FeatureManifest Location="WingtipDevProject1\feature.xml" />
</FeatureManifests>
<TemplateFiles>
<TemplateFile Location="IMAGES\WingtipDevProject1\FeatureIcon.gif" />
<TemplateFile Location="IMAGES\WingtipDevProject1\SiteIcon.gif" />
</TemplateFiles>
<Assemblies>
<Assembly Location="WingtipDevProject1.dll"
DeploymentTarget="GlobalAssemblyCache" />
</Assemblies>
</Solution>
As you can see, a manifest.xml file has a top-level Solution element with attributes such as an identifying GUID for the SolutionId and user-friendly text for the Title. The DeploymentServerType attribute tells SharePoint Foundation whether the contents of the solution package should be installed on front-end Web servers in the farm or on application servers. The ResetWebServer attribute tells SharePoint Foundation whether it needs to run an IISRESET operation on each server at the end of the deployment process.
In this example, three different child elements are nested inside the Solution element. The FeatureManifests element contains a FeatureManifest element with a Location attribute that references a feature.xml file using a path relative to the FEATURES directory. SharePoint Foundation now has the information it needs to properly install the feature.
The TemplateFiles element contains two TemplateFile elements with Location attributes that reference the two .gif files that are being used as custom image files using a path relative to the TEMPLATE directory. These elements give SharePoint Foundation the information it needs to copy these two files to a new subdirectory that it creates inside the IMAGES directory. Finally, the Assemblies element contains an Assembly element with a Location attribute that references the assembly file that's distributed as part of the solution package using a path relative to the root of the solution package. As you can see, the Assembly element contains a DeploymentTarget attribute with a value of GlobalAssemblyCache, which tells SharePoint Foundation to install the assembly in the GAC on each front-end Web server or application server.
Activation Dependencies
If you're familiar with creating solution packages for SharePoint 2007, you'll be happy to discover that SharePoint 2010 introduces the capability to define a dependency between two solution packages. This type of dependency is helpful when one solution package installs components that are used by another solution package.
For example, imagine a scenario in which a solution package named WingtipUtilities.wsp installs an assembly in the GAC that contains a library of reusable utility functions. What if you then develop a second SharePoint solution named WingtipDevProject2 that references the assembly deployed by WingtipUtilities.wsp to call functions inside the utility library?
The key point is that the second SharePoint solution has been created with a dependency on WingtipUtilities.wsp. In other words, code deployed in WingtipDevProject2.wsp can't be expected to work correctly in a farm in which WingtipUtilities.wsp hasn't been deployed. Therefore, you should define an activation dependency on WingtipUtilities.wsp by modifying the manifest.xml file inside WingtipDevProject2.wsp.
To create an activation dependency, you add an ActivationDependencies element inside the top-level Solution element with a nested ActivationDependency element. Therefore, the manifest. xml file in WingtipDevProject2.wsp should be updated to look like this:
<ActivationDependencies>
<ActivationDependency
SolutionId="0cee8f44-4892-4a01-b8f4-b07aa21e1ef1"
SolutionName="WingtipUtilities.wsp"
/>
</ActivationDependencies>
The value of defining an activation dependency is that it allows you to discover problems early—at deployment time instead of run time. If you fail to define an activation dependency, you make it possible for a farm administrator to successfully deploy your solution package in a SharePoint farm that doesn't have the required dependencies. As a result, users could experience errors in the production environment that are difficult to track down and remedy.
If you define an activation dependency, the farm administrator will experience an error when attempting to deploy the solution package in a SharePoint farm that doesn't have the required dependencies. The error will generate the following error message:
"This solution cannot be activated because its functionality depends on another solution that does not exist: WingtipUtilities.wsp, Id: 0cee8f44-4892-4a01-b8f4-b07aa21e1ef1. First add the other solution to the Solution Gallery, activate that solution, and then repeat activation of this solution."
Warning
We must point out that the term activation dependency is a little confusing when used in the context of a farm solution. From a conceptual standpoint, you're really creating a deployment dependency rather than an activation dependency when you use the ActivationDependency element in a solution package that will be deployed as a farm solution. You will see that the term activation dependency makes more sense when we get to Chapter 4 and discuss deploying a solution package as a sandboxed solution, which goes through the process of activation (not deployment) when it is used within the scope of a site collection.
Generating the Solution Package File
There are a few different ways to generate a solution package from the source files inside a Visual Studio project. Starting in Chapter 3, we'll use the SharePoint Developer Tools to create a solution package file. As you'll see, the SharePoint Developer Tools make your life much easier because the details of creating the solution package are handled behind the scenes and become transparent to developers. However, because we're building a SharePoint solution in this chapter without assistance from the SharePoint Developer Tools, we're going to build the solution package file using a command-line utility named makecab.exe.
When you plan to create a solution package file using the makecab.exe utility, you start by creating a diamond definition file (DDF). The DDF file is passed to makecab.exe as an input parameter, and it lists the files that must be copied inside the solution package file. The following code shows the DDF file used in WingtipDevProject1.
.OPTION EXPLICIT
.Set CabinetNameTemplate=WingtipDevProject1.wsp
.Set DiskDirectory1=DeploymentFiles/output
;*** Add files to root folder of Solution Package
DeploymentFiles\input\manifest.xml
bin/debug/WingtipDevProject1.dll
;*** add feature files into a folder having the same name as feature
.Set DestinationDir=WingtipDevProject1
SharePointRoot\TEMPLATE\FEATURES\WingtipDevProject1\elements.xml
SharePointRoot\TEMPLATE\FEATURES\WingtipDevProject1\feature.xml
;*** add TEMPLATE files such as IMAGE files into a folder relative to /TEMPLATE
.Set DestinationDir=IMAGES\WingtipDevProject1
SharePointRoot\TEMPLATE\IMAGES\WingtipDevProject1\FeatureIcon.gif
SharePointRoot\TEMPLATE\IMAGES\WingtipDevProject1\SiteIcon.gif
As you can see, information at the top of this DDF file tells makecab.exe to generate an output file named WingtipDevProject1.wsp. There's also a line in the DDF file for each source file that must be copied into the output solution package file. Some of the files in a solution package, such as manifest.xml and WingtipDevProject1.dll, must be copied to the root of the solution package, while other files need to be copied to child folders nested inside the root.
You can call the makecab.exe utility from the command line using a Windows PowerShell script. Here is a simple example of a Windows PowerShell script that automates building a solution package using makecab.exe.
$ProjectName = "WingtipDevProject1"
$ProjectDirectory = "C:\InsideSP2010\WingtipDevProject1\WingtipDevProject1"
$DeploymentFilesDirectory = $ProjectDirectory + 'DeploymentFiles\'
$DiamondDefinitionFilePath = $DeploymentFilesDirectory +
'input\BuildSolution.ddf'
Set-Location $ProjectDirectory
$MAKECAB = "C:\Windows\SysWOW64\MAKECAB.EXE"
& $MAKECAB /V1 /F $DiamondDefinitionFilePath
Notice that the last line of this script begins with an & character, which serves as the Windows PowerShell call operator. The call operator is commonly used when your Windows PowerShell script needs to call out to an external utility such as makecab.exe.
Using Windows PowerShell Scripts to Automate Tasks in Visual Studio
When you're developing SharePoint solutions, it's helpful if you can integrate Windows PowerShell scripts to automate the tasks involved with testing and deploying your code. As you've seen in the previous section, you can call the makecab.exe utility from a Windows PowerShell script to build your project files into a solution package. You can also call Windows PowerShell cmdlets from Microsoft.SharePoint.PowerShell to automate other important tasks during testing, such as installing and deploying a solution package in the local farm.
The Visual Studio 2010 project named WingtipDevProject1 has been configured with an integrated set of Windows PowerShell scripts, as shown in Figure 2-7. These scripts demonstrate how to automate many of the tasks you need to perform while testing and debugging a SharePoint solution.
Figure 2-7 WingtipDevProject1 contains Windows PowerShell scripts used to test and debug a SharePoint solution.
WingtipDevProject1 has been configured so that you can run any of these Windows PowerShell scripts by executing the project's Build command. Now we'll walk through the process of integrating Windows PowerShell scripts with a Visual Studio 2010 project so that you can accomplish the same goal in your projects.
The easiest way to execute a Windows PowerShell script from within a Visual Studio 2010 project is to navigate to the Build Events tab of the Project Properties dialog and call powershell. exe from the post-build event command line. You should call out to powershell.exe using the following path.
%WINDIR%\SysNative\WindowsPowerShell\v1.0\powershell.exe
It's important that you use the virtual path of %WINDIR%\SysNative and not the actual path of C:\Windows\System32. The reason for this is that Visual Studio 2010 is a 32-bit application that needs to call the 64-bit version of powershell.exe to successfully load the Microsoft.SharePoint.Powershell snap-in.
If you launch to powershell.exe using the path C:\Windows\System32, you'll encounter problems in SharePoint Foundation development. The problem arises because a 64-bit version of the Windows operating system will see that Visual Studio 2010 is a 32-bit application and will remap the path to load the 32-bit version of powershell.exe. By using the virtual path %WINDIR%\SysNative, you're able to avoid this problem and ensure that you successfully launch the 64-bit version.
To execute a Windows PowerShell script, you should call powershell.exe followed by the –Command parameter, which passes the path to the target Windows PowerShell script. For example, if you've written a Windows PowerShell script named MyScript.ps1 and added it to the project at the root directory, you can then execute the script by calling powershell.exe in the following manner.
powershell.exe -Command "$(ProjectDir)\MyScript.ps1"
Notice that this example uses the $(ProjectDir) token supported by Visual Studio 2010. Before executing this command, Visual Studio will replace this token with the actual path to the project so that powershell.exe can locate and execute the Windows PowerShell script MyScript.ps1. Now let's say you want to execute a Windows PowerShell script and pass it an input parameter, such as the name of the current project. You can use another Visual Studio token, named $(ProjectName), and modify the call to powershell.exe to look like this:
powershell.exe -Command "$(ProjectDir)\MyScript.ps1 $(ProjectName)"
Now that you understand the basics of executing a Windows PowerShell script from within a Visual Studio 2010 project, we can explain how you can execute any of the Windows PowerShell scripts in WingtipDevProject1. The post-build event command line in WingtipDevProject1 has been written to call the script BuildCommandDispatcher.ps1 and to pass four parameters using the Visual Studio tokens $(ProjectName), $(ProjectDir), $(TargetPath), and $(ConfigurationName).The control of flow logic inside BuildCommandDispatcher.ps1 uses a Windows PowerShell switch statement to examine the active configuration and executes the appropriate Windows PowerShell script passing the required parameters. This makes it possible for you to execute any of these Windows PowerShell scripts by changing the project's configuration, as shown in Figure 2-8, and then executing the project's Build command.
Figure 2-8 To execute a specific script, change the project configuration and run the Build command.
For example, if you set the project configuration to BuildSolution and then run the Build command, the logic in BuildCommandDispatcher.ps1 executes BuildSolution.ps1 and passes the project name and project directory as parameters. BuildSolution.ps1 has been written to build the solution package using the following code.
$ProjectName = $args[0]
$ProjectDirectory = $args[1]
$DeploymentFilesDirectory = $ProjectDirectory + 'DeploymentFiles\'
$DiamondDefinitionFilePath = $DeploymentFilesDirectory +
'input\BuildSolution.ddf'
Write-Host 'Building Solution Package using MAKECAB.EXE'
Set-Location $ProjectDirectory
$MAKECAB = "C:\Windows\SysWOW64\MAKECAB.EXE"
& $MAKECAB /V1 /F $DiamondDefinitionFilePath
Installing and Deploying a Solution Package
Once you've built the solution package, you must be able to install and deploy it so that you can test and debug your code. The act of installing a solution package simply copies the solution package file to the configuration database and registers it as a named solution that is ready for deployment. The act of deploying the solution package pushes the code out to each Web server for deployment and makes it possible to test and debug your work.
You can install a solution package by loading the Microsoft.SharePoint.Powershell snap-inand then calling the Add-SPSolution cmdlet, passing the –LiteralPath parameter using a valuethat contains the physical path to the solution package file.
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
Add-SPSolution -LiteralPath "C:\Solutions\WingtipDevProject1.wsp"
Once you've installed the solution package using the Add-SPSolution cmdlet, you can deploy it using the Install-SPSolution cmdlet. Keep in mind that the names of these cmdlets can cause confusion because you install a solution package using the Add-SPSolution cmdlet and you deploy it using the Install-SPSolution cmdlet. Also note that once the solution package has been installed, you refer to it using the -Identity parameter whose value should be the name of the solution package file without any path.
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
Add-SPSolution -LiteralPath "C:\Solutions\WingtipDevProject1.wsp"
Install-SPSolution -Identity "WingtipDevProject1.wsp" -Local -GACDeployment
You should also observe that the call to Install-SPSolution includes two additional parameters, -Local and -GACDeployment. The -Local parameter tells SharePoint Foundation that it needs to worry about deploying the solution package only on the local server, which can speed things up when developing on a single-server farm. The other parameter, -GACDeployment, is required whenever you're deploying a solution package that installs an assembly in the GAC.
Retracting and Removing a Solution
If you want to remove a solution package from a farm after it has been deployed, you must retract it and then remove it. The act of retracting reverses the act of deployment. For example, retracting a solution forces SharePoint Foundation to delete all the files it copied during deployment as well as uninstall features and delete assemblies from the GAC. Once you've retracted a solution, you can then remove it, which deletes the solution package file from the configuration database.
You can retract a solution package using the Uninstall-SPSolution cmdlet. When calling Uninstall-SPSolution, you should pass the -Identity parameter and the -Local parameter in the same manner as when calling Install-SPSolution. You should also pass the -Confirm parameter with a value of $false because failing to do so can cause the cmdlet to prompt the user. This user prompt can cause problems because it freezes Visual Studio when a script that prompts the user for a response is executed.
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
$SolutionPackageName = "WingtipDevProject1.wsp"
Uninstall-SPSolution -Identity $SolutionPackageName -Local -Confirm:$false
Once you've retracted the solution using the Uninstall-SPSolution cmdlet, you can then remove it by calling Remove-SPSolution, which instructs SharePoint Foundation to delete the solution package file from the configuration database.
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
$SolutionPackageName = "WingtipDevProject1.wsp"
Uninstall-SPSolution -Identity $SolutionPackageName -Local -Confirm:$false
Remove-SPSolution -Identity $SolutionPackageName -Confirm:$false
These calls to Uninstall-SPSolution and Remove-SPSolution will fail if the solution package isn't currently installed and deployed. Therefore, it makes sense to add a call to Get-SPSolution and conditional logic to determine whether the solution package is currently installed and deployed before attempting to retract or remove it.
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
$SolutionPackageName = "WingtipDevProject1.wsp"
$solution = Get-SPSolution | where-object {$_.Name -eq $SolutionPackageName}
# check to see if solution package has been installed
if ($solution -ne $null) {
# check to see if solution package is currently deployed
if($solution.Deployed -eq $true){
Uninstall-SPSolution -Identity $SolutionPackageName -Local -Confirm:$false
}
}
Now that you've seen how to retract and remove a solution package, it's time to walk through the DeploySolution.ps1 script in WingtipDevProject1. When you're developing a SharePoint solution, you will frequently be deploying solution packages, and you must ensure that you properly retract and remove any older version before you deploy the latest one. Listing 2-6 shows how that step can be automated with a single Windows PowerShell script.
Listing 2-6 This Windows PowerShell script is written to install and deploy a solution package with conditional logic to uninstall and delete a previous version when required.
$SolutionPackageName = $args[0]
$SolutionPackagePath = $args[1]
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction "SilentlyContinue"
$solution = Get-SPSolution | where-object {$_.Name -eq $SolutionPackageName}
if ($solution -ne $null) {
if($solution.Deployed -eq $true){
Uninstall-SPSolution -Identity $SolutionPackageName -Local -Confirm:$false
}
Remove-SPSolution -Identity $SolutionPackageName -Confirm:$false
}
Add-SPSolution -LiteralPath $SolutionPackagePath
Install-SPSolution -Identity $SolutionPackageName -Local -GACDeployment
Now that you've learned about deployment, it's time to test the solution. After WingtipDevProject1.wsp has been deployed, the Wingtip Lead Tracker feature definition should be installed and available for activation within a site on your local farm. You can manually activate the feature definition inside a test site using the site feature management page, as shown in Figure 2-9.
Figure 2-9 After deployment, the feature from WingtipDevProject1 can be activated through the site's feature management page.
When you activate Wingtip Lead Tracker, the underlying feature definition provisions a Contacts list entitled Sales Leads because of the declarative element we defined inside elements.xml. Feature activation should also force SharePoint Foundation to execute the FeatureActivated event handler, which should change the site icon and update the site title to Feature Activated.
Updating a Solution
After one of your solution packages has been deployed in a production farm, you might need to update some of the files that have already been deployed. SharePoint Foundation supports the concept of updating a solution package. This technique allows you to replace existing files that have already been deployed as well as to deploy new files that weren't part of the original solution package deployment.
For example, imagine a simple scenario in which WingtipDevProject1.wsp has already been deployed and its feature definition has been activated in several sites. What if, after the fact, the Wingtip Sales Director decides he doesn't like the SiteLogo.gif file you decided to use for the site logo. SharePoint Foundation makes it easy to accommodate this change. You can simply update your Visual Studio project by replacing the old SiteLogo.gif file with a new one of the same name. Then you can rebuild the solution package with the updated .gif file and run the Update-SPSolution cmdlet to automate replacing the old version of SiteLogo.gif file with the new one on each of the front-end Web servers.
$SolutionPackageName = "WingtipDevProject1.wsp"
$SolutionPackagePath = "C:\Solutions\WingtipDevProject1.wsp"
Update-SPSolution -Identity $SolutionPackageName
-LiteralPath $SolutionPackagePath
-Local -GACDeployment
Now that you've seen how to update a solution package using the Update-SPSolution cmdlet, let's walk through the UpdateSolution.ps1 script in WingtipDevProject1. You should observe that a call to Get-SPSolution will allow you to determine whether a previous version of the solution package has been installed and deployed. Knowing this is important because a call to Update-SPSolution will fail if the solution package isn't already deployed.
$SolutionPackageName = $args[0]
$SolutionPackagePath = $args[1]
Add-PSSnapin Microsoft.SharePoint.Powershell
# ensure previous version of solution package solution package is already deployed
$solution = Get-SPSolution | where-object {$_.Name -eq $SolutionPackageName}
if ($solution -ne $null) {
if($solution.Deployed -eq $true){
Update-SPSolution -Identity $SolutionPackageName
-LiteralPath $SolutionPackagePath
-Local -GACDeployment
}
else {
Write-Host "Solution package cannot be updated because it is not deployed"
}
}
As you'll see in the next section, using the technique of updating a solution provides the means to update a feature definition when you need to upgrade a feature. Updating a solution also provides a simple means to replace files and components with enhanced versions. For example, imagine you need to fix a bug in the C# code you've written inside the ActivateFeature event handler. You can simply update the code and rebuild a new version of the solution package. When you update the solution, the old copy of WingtipDevProject1.dll in the GAC is replaced with the new copy and the SharePoint worker process restarts to ensure that the new version gets loaded. You can use the same technique to update many other types of files, including Cascading Style Sheets (.css) files, JavaScript (.js) files, and Silverlight application (.xap) files.
Feature Upgrade Enhancements in SharePoint 2010
Although SharePoint 2007 tracks a version number for each feature definition, it doesn't go any further than that to provide support for feature versioning. Many companies and developers using SharePoint 2007 have been forced to create their own infrastructure to upgrade sites to a more recent version of a feature definition. Fortunately, the new feature upgrade enhancements in SharePoint Foundation mean that in SharePoint 2010 you won't have to do this anymore.
SharePoint Foundation supports feature activation at four different levels: site, site collection, Web application, and farm. Whenever a feature definition is activated, SharePoint Foundation creates a feature instance that tracks metadata about the underlying feature definition. The feature instance tracks feature properties such as Id, Title, and Version. Make sure you understand that the Version property of a feature instance represents the current version of the feature definition at the time of activation.
Once you've pushed a feature definition into a production farm and it's been activated, you might be required to update it to deal with changing business requirements. You can define upgrade actions inside a feature definition that will be processed when a feature instance is upgraded to the new version. Upgrade actions can be created inside the feature.xml file using declarative XML and can also execute custom event handlers written in C# or Visual Basic.
Once you've updated a feature definition in a Visual Studio project such as WingtipDevProject1, you can rebuild the solution package and push the new version out into a production farm or a staging farm using the Update-SPSolution cmdlet. However, pushing out a new version of a feature definition using Update-SPSolution is only half the story.
The farm now has an updated feature definition, but all the existing feature instances are still based on a previous version of the feature definition. You must run a query to find all the feature instances that require updating, and you then must call the Upgrade method on each feature instance to trigger the feature upgrade process. When the Upgrade method is called on a feature instance, SharePoint Foundation triggers the upgrade actions you've defined in your feature definition. To help you better understand this process, let's work through an example using the Wingtip Lead Tracker feature definition.
Note
Upgrading Sites Using an Updated Feature Definition
You've been developing the WingtipDevProject1 solution in which you've created the Wingtip Lead Tracker feature definition. After you created and tested your implementation of the initial requirements, you built a solution package named WingtipDevProject1.wsp and gave it to the Wingtip IT staff. They deployed it in the production farm. That was six months ago. Now there are ten different sales sites in the production farm that have activated the Lead Tracker feature.
Earlier this morning, the Wingtip Sales Director sent you a request to update the Lead Tracker feature. The Sales Director has asked you to revise Wingtip Lead Tracker so that it creates a second list, named Customers, in addition to the Sales Leads list. Both these lists should be created using the Contacts list type built into SharePoint Foundation.
Updating a Feature Definition with Upgrade Actions
SharePoint Foundation tracks the version numbers for feature definitions using a four-part number similar to version numbers for assemblies in the .NET Framework. The initial version of the Wingtip Lead Tracker feature definition had a version number of 1.0.0.0. All the sites in which the Wingtip Lead Tracker feature has been activated also have version 1.0.0.0 feature instances.
Recall from earlier in this chapter that the initial version of the Wingtip Lead Tracker feature definition includes a single element manifest named elements.xml. This element manifest is referenced using an ElementManifest element in the feature.xml file.
<Feature
Id="86689158-7048-4421-AD21-E0DEF0D67C81"
Title="Wingtip Lead Tracker"
Version="1.0.0.0" . . .
>
<ElementManifests>
<ElementManifest Location="elements.xml" />
</ElementManifests>
</Feature>
Now we'll begin updating the Wingtip Lead Tracker feature definition. We start by incrementing the Version number in feature.xml.
<Feature
Id="86689158-7048-4421-AD21-E0DEF0D67C81"
Title="Wingtip Lead Tracker"
Version="2.0.0.0"
.
.
.
The next step is to add a new ListInstance element to the feature definition to create the Customers list. Where should this new ListInstance element be added? We could add it into elements.xml along with the ListInstance element used to create the Sales Leads list. This would be a mistake, however, because the feature definition update would affect only sites that activated Wingtip Lead Tracker in the future; existing feature instances that were created by activating version 1.0.0.0 wouldn't be affected.
The recommended approach for dealing with this scenario is to create a second element manifest for elements that need to be provisioned during the upgrade process. In our example, we add a second element manifest to the Wingtip Lead Tracker feature definition. We name the second element manifest elements_v2.xml and add into it a second ListInstance element to create the Customers list.
<Elements xmlns="https://schemas.microsoft.com/sharepoint/">
<ListInstance
FeatureId="00BFEA71-7E6D-4186-9BA8-C047AC750105"
TemplateType="105"
Id="Customers"
Title="Customers"
Url="Customers"
OnQuickLaunch="TRUE" />
</Elements>
When you add a new element manifest that is to be processed during feature upgrade, you must modify the feature.xml file by adding an UpgradeActions element inside the toplevel Feature element. The UpgradeActions element must contain a VersionRange element that defines the EndVersion attribute and optionally the BeginVersion attribute. Inside the VersionRange element, you should add an ApplyElementManifests element with an inner ElementManifest element. Making the following modification to the feature.xml file, we can ensure that SharePoint Foundation will inspect elements_v2.xml during the feature upgrade process for a site and provision the Customers list as well as any new sites that activate the feature.
<Feature Id="86689158-7048-4421-AD21-E0DEF0D67C81" Version="2.0.0.0" >
<ElementManifests>
<ElementManifest Location="elements.xml" />
<ElementManifest Location="elements_v2.xml" />
</ElementManifests>
<UpgradeActions>
<VersionRange BeginVersion="1.0.0.0" EndVersion="2.0.0.0">
<ApplyElementManifests>
<ElementManifest Location="elements_v2.xml"/>
</ApplyElementManifests>
</VersionRange>
</UpgradeActions>
</Feature>
The updated version of feature.xml has two different references to elements_v2.xml. Why? The bottom reference is used when a feature instance based on version 1.0.0.0 is upgraded to version 2.0.0.0. The top reference is needed as well. SharePoint Foundation uses the top reference to elements_v2.xml when a user activates the Wingtip Lead Tracker feature definition in a site for the first time using version 2.0.0.0. In this case, the feature instance is never upgraded, but the feature definition must still provision a new Customers list.
Adding Code Behind Custom Upgrade Actions
Earlier in this chapter, you saw how to extend a feature definition with a Feature Receiver by creating a public class that inherited from SPFeatureReceiver. In addition to the event handler methods we added, FeatureActivated and FeatureDeactivating, we now want to add another event handler method, FeatureUpgrading, which must be overwritten to execute code during a custom upgrade action.
public class FeatureReceiver : SPFeatureReceiver {
public override void FeatureActivated(SPFeatureReceiverProperties props) {}
public override void FeatureDeactivating(SPFeatureReceiverProperties props) {}
public override void FeatureUpgrading(SPFeatureReceiverProperties props,
string upgradeActionName,
IDictionary<string, string> parameters) {
// this event handler executes once for each custom upgrade action
}
}
The FeatureUpgrading event handler is a bit more complicated than the event handlers for FeatureActivated and FeatureDeactivating. First, you need to add ReceiverAssembly and ReceiverClass attributes to the UpgradeActions element, as shown in Listing 2-7. Second, you must add one or more CustomUpgradeAction elements inside a VersionRange element inside the UpgradeActions element.
Listing 2-7 You define upgrade actions by adding an UpgradeActions element to the feature.xml file.
<Feature Id="86689158-7048-4421-AD21-E0DEF0D67C81" Version="2.0.0.0" >
<UpgradeActions
ReceiverAssembly="WingtipDevProject1, [four-part assembly name]"
ReceiverClass="WingtipDevProject1.FeatureReceiver" >
<VersionRange BeginVersion="1.0.0.0" EndVersion="2.0.0.0">
<CustomUpgradeAction Name="UpdateSiteTitle">
<Parameters>
<Parameter Name="NewSiteTitle">A much better site title</Parameter>
</Parameters>
</CustomUpgradeAction>
</VersionRange>
</UpgradeActions>
</Feature>
As you can see from Listing 2-7, a CustomUpgradeAction element is defined with the Name attribute and a parameter collection, which here contains only a single entry. It is essential to understand that the FeatureUpgrading event handler will execute once for each CustomUpgradeAction element. If you don't add at least one CustomUpgradeAction element, the FeatureUpgrading event handler will never fire.
Remember that you can add as many CustomUpgradeAction elements as you want. When you implement the FeatureUpgrading event handler, you can use the parameter named upgradeActionName argument to determine which custom upgrade action is being processed, and you can use the argument-named parameters to retrieve the parameters defined by the currently processing custom upgrade action.
In our case, the only custom upgrade action is named UpdateSiteTitle, and it contains a single parameter, NewSiteTitle. The implementation of the FeatureUpgrading event handler in Listing 2-8 uses a C# switch statement to execute the correct code for the custom upgrade action, named UpdateSiteTitle. Notice how the implementation must retrieve the value for the NewSiteTitle parameter to properly update the site title.
Listing 2-8 The FeatureUpgrading method will execute once for each CustomUpgradeAction element.
public override void FeatureUpgrading(SPFeatureReceiverProperties props,
string upgradeActionName,
IDictionary<string, string> parameters) {
// perform common initialization for all custom upgrade actions
SPWeb site = props.Feature.Parent as SPWeb;
if (site != null) {
// determine which custom upgrade action is executing
switch (upgradeActionName) {
case "UpdateSiteTitle":
| //*** begin code for UpdateSiteTitle upgrade action
string NewTitle = parameters["NewSiteTitle"];
site.Title = NewTitle;
site.Update();
//*** end for UpdateSiteTitle upgrade action
break;
default:
// unexpected feature upgrade action
break;
}
}
}
We've now walked through adding an UpgradeActions element to the feature.xml file to upgrade a feature instance from version 1.0.0.0 to version 2.0.0.0. Keep in mind that this structure of the UpgradeActions element provides a good deal of flexibility to deal with future updates. Consider the scenario of pushing out a version 3.0.0.0 update. You might need to upgrade some feature instances that are currently version 1.0.0.0 in addition to feature instances that are version 2.0.0.0. You can add multiple VersionRange elements to differentiate between these two scenarios.
<UpgradeActions>
<VersionRange BeginVersion="1.0.0.0" EndVersion="2.0.0.0">
<!-- upgrade actions for upgrading from version 1 to 2 -->
</VersionRange>
<VersionRange BeginVersion="1.0.0.0" EndVersion="3.0.0.0">
<!-- upgrade actions for upgrading from version 1 to 3 -->
</VersionRange>
<VersionRange BeginVersion="2.0.0.0" EndVersion="3.0.0.0">
<!-- upgrade actions for upgrading from version 2 to 3 -->
</VersionRange>
</UpgradeActions>
Upgrading Feature Instances
Once you've updated the feature definition, you must then push out the updated files using the Update-SPSolution cmdlet, as shown earlier in the chapter. You're still not done, however. SharePoint Foundation doesn't provide an administrative user interface to help you upgrade feature instances. Instead, you must write code to find all the feature instances requiring upgrade and explicitly call the Upgrade method supplied by the server-side object model.
Keep in mind that SharePoint Foundation doesn't allow two versions of a feature definition to be installed side by side within the same farm. Instead, an updated version of a feature definition overwrites the earlier version. For example, the updated feature.xml file replaces the original feature.xml file, and that's what SharePoint Foundation uses to determine a feature definition's current version number. This is another reason that the updated feature definition must contain all the original information as well as the new information for the updated version.
You can use the server-side object model to query for all the feature instances that require an upgrade. The SPWebApplication class exposes a QueryFeatures method that accepts the GUID identifier for a feature definition and returns all the associated feature instances. The QueryFeatures method has an overloaded implementation, which also allows you to filter the query based on whether the feature instance is up to date with the farm's current version of the feature definition. Here's a simple C# console application that executes a query to retrieve all feature instances requiring upgrade and explicitly calls the Upgrade method.
// get reference to target Web Application
Uri webAppUrl = new Uri("http://intranet.wingtip.com");
SPWebApplication webApp = SPWebApplication.Lookup(webAppUrl);
// query Web Application for feature instances needing an upgrade
Guid featureDefinitionId = new Guid("86689158-7048-4421-AD21-E0DEF0D67C81");
SPFeatureQueryResultCollection features =
webApp.QueryFeatures(featureDefinitionId, true);
// enumerate through feature instances and call Upgrade
foreach (SPFeature feature in features) {
feature.Upgrade(true);
}
Although the C# code from the console application is easy to read and understand, it doesn't provide a practical way to upgrade feature instances in a production farm. It makes more sense to add the equivalent code to a Windows PowerShell script. The Windows PowerShell script in Listing 2-9 has been written to upgrade all the feature instances in the Web application at http://intranet.wingtip.com.
Listing 2-9 You can write a Windows PowerShell script to explicitly upgrade feature instances.
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
$WebAppUrl = "http://intranet.wingtip.com"
$featureId = New-Object System.Guid
-ArgumentList 86689158-7048-4421-AD21-E0DEF0D67C81"
$webApp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup($WebAppUrl)
$features = $webApp.QueryFeatures($FeatureId, $true)
foreach($feature in $features){
$feature.Upgrade($true)
}
Now that you've seen the entire process, let's summarize how feature upgrade works. Remember that feature upgrade only makes sense in a scenario in which a feature definition has been deployed and feature instances have already been created. The first step is updating the feature definition to include one or more upgrade actions. The second step is to rebuild the solution package and to push the updates out into the farm using the Update-SPSolution cmdlet. The final step is to run a Windows PowerShell script or use another approach to trigger the upgrade process on specific feature instances. When you trigger the upgrade process, SharePoint Foundation begins to process your upgrade actions.
Conclusion
In this chapter, we focused on teaching you the fundamental concepts and skills required to develop SharePoint solutions on the SharePoint Foundation platform. You've seen how to program against the server-side object model using C# code written in a Visual Studio 2010 project as well as using code written in a Windows PowerShell script. You also learned the basics of developing features and building a solution package for deployment.
You've also seen how to leverage the new feature upgrade support introduced in SharePoint Foundation. This new enhancement to the SharePoint platform is very welcome, as it finally gives developers a supported technique to evolve the functionality of a feature definition over time.
We apologize that we made you suffer through the theoretical exercise of building a Visual Studio 2010 project without the assistance of the new SharePoint Developer Tools. However, we feel the sacrifice you made was worth it because you now have a much better understanding of how SharePoint Foundation works internally. This new understanding will allow you to fully appreciate what the SharePoint Developer Tools do for you behind the scenes. It will also make it possible for you to extend a SharePoint project when you need to extend a feature definition or SharePoint solution with CAML elements that aren't supported by the SharePoint Developer Tools.
Additional Resources
For more information, see the following resources:
Chapter 1: SharePoint 2010 Developer Roadmap (Inside SharePoint 2010)
Chapter 3: SharePoint Developer Tools in Microsoft Visual Studio 2010 (Inside SharePoint 2010)
About the Authors
Ted 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 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.
Scot 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.