演练:创建 SharePoint 项目扩展
本演练阐释如何创建 SharePoint 项目的扩展。 可以使用项目扩展来响应项目级事件,例如,在添加、删除项目或更改项目名称时。 还可以添加自定义属性或在某个属性值更改时做出响应。 与项目项扩展不同,无法将项目扩展与特定的 SharePoint 项目类型相关联。 创建项目扩展后,当在 Visual Studio 中打开任何类型的 SharePoint 项目时,此扩展都会加载。
在本演练中,您将创建一个将添加到 Visual Studio 中创建的任何 SharePoint 项目中的自定义布尔属性。 如果设置为 True,则新属性会将 Images 资源文件夹添加或映射到您的项目。 如果设置为 False,则将删除 Images 文件夹(如果存在)。 有关更多信息,请参见如何:添加和移除映射文件夹。
本演练将演示以下任务:
为 SharePoint 项目创建可执行以下操作的 Visual Studio 扩展:
将自定义项目属性添加到“属性”窗口。 该属性将应用于任何 SharePoint 项目。
使用 SharePoint 项目对象模型将映射文件夹添加到项目中。
使用 Visual Studio 自动化对象模型 (DTE) 从项目中删除映射文件夹。
生成 Visual Studio 扩展 (VSIX) 包以部署项目属性的扩展程序集。
调试并测试项目属性。
系统必备
您需要在开发计算机上安装以下组件才能完成本演练:
支持的 Microsoft Windows、SharePoint 和 Visual Studio 版本。 有关更多信息,请参见开发 SharePoint 解决方案的要求。
Visual Studio 2010 SDK。 本演练使用 SDK 中的**“VSIX 项目”**模板来创建 VSIX 包以部署项目属性扩展。 有关更多信息,请参见扩展 Visual Studio 中的 SharePoint 工具。
创建项目
若要完成本演练,您必须创建以下两个项目:
一个用于创建 VSIX 包以部署项目扩展的 VSIX 项目。
一个用于实现项目扩展的类库项目。
从创建项目开始本演练。
创建 VSIX 项目
启动 Visual Studio。
在**“文件”菜单上指向“新建”,再单击“项目”**。
在**“新建项目”对话框中,展开“Visual C#”或“Visual Basic”节点,然后单击“扩展性”**节点。
提示
只有在安装 Visual Studio 2010 SDK 之后,“扩展性”节点才可用。 有关更多信息,请参见本主题前面的系统必备部分。
在对话框顶部的组合框中,选择**“.NET Framework 4”**。 SharePoint 工具扩展需要此版本的 .NET Framework 中的功能。
单击**“VSIX 项目”**模板。
在**“名称”**框中键入 ProjectExtensionPackage。
单击**“确定”**。
Visual Studio 会将**“ProjectExtensionPackage”项目添加到“解决方案资源管理器”**中。
创建扩展项目
在**“解决方案资源管理器”中,右击解决方案节点,单击“添加”,再单击“新建项目”**。
提示
在 Visual Basic 项目中,仅当在“选项”对话框 ->“项目和解决方案”->“常规”中选中“总是显示解决方案”复选框时,解决方案节点才会出现在“解决方案资源管理器”中。
在**“新建项目”对话框中,展开“Visual C#”或“Visual Basic”节点,然后单击“Windows”**。
在对话框顶部的组合框中,选择**“.NET Framework 4”**。
选择**“类库”**项目模板。
在**“名称”**框中,键入 ProjectExtension。
单击**“确定”**。
Visual Studio 将**“ProjectExtension”**项目添加到解决方案中,并打开默认的 Class1 代码文件。
从项目中删除 Class1 代码文件。
配置项目
在编写代码以创建项目扩展之前,请将代码文件和程序集引用添加到扩展项目中。
配置项目
将名为 CustomProperty 的新代码文件添加到 ProjectExtension 项目中。
在**“项目”菜单上,单击“添加引用”**。
在**“.NET”选项卡上,按住 Ctrl 的同时单击下列程序集,然后单击“确定”**:
Microsoft.VisualStudio.SharePoint
System.ComponentModel.Composition
System.Windows.Forms
EnvDTE
在**“解决方案资源管理器”中,在 ProjectExtension 项目的“引用”文件夹下,单击“EnvDTE”**。
在**“属性”窗口中,将“嵌入互操作类型”属性更改为“False”**。
定义新的 SharePoint 项目属性
创建一个定义项目扩展和新项目属性的行为的类。 若要定义新的项目扩展,此类应实现 ISharePointProjectExtension 接口。 每当需要定义 SharePoint 项目扩展时,就要实现此接口。 另外,将 ExportAttribute 添加到此类中。 此特性使 Visual Studio 能够发现和加载您的 ISharePointProjectExtension 实现。 将 ISharePointProjectExtension 类型传递给特性的构造函数。
定义新的 SharePoint 项目属性
如果 CustomProperty 代码文件尚未打开,请双击此文件进行编辑。
将下面的代码粘贴到此文件中。
Imports System Imports System.Linq Imports System.ComponentModel Imports System.ComponentModel.Composition Imports System.Windows.Forms Imports Microsoft.VisualStudio.SharePoint Imports EnvDTE Namespace Contoso.SharePointProjectExtensions.MapImagesFolder ' Export attribute: Enables Visual Studio to discover and load this extension. ' MapImagesFolderProjectExtension class: Adds a new Map Images Folder property to any SharePoint project. <Export(GetType(ISharePointProjectExtension))> _ Public Class MapImagesFolderProjectExtension Implements ISharePointProjectExtension Public Sub Initialize(ByVal projectService As ISharePointProjectService) Implements ISharePointProjectExtension.Initialize AddHandler projectService.ProjectPropertiesRequested, AddressOf Me.projectService_ProjectPropertiesRequested End Sub Private Sub projectService_ProjectPropertiesRequested(ByVal sender As Object, ByVal e As SharePointProjectPropertiesRequestedEventArgs) Dim propertiesObject As CustomProjectProperties = Nothing ' If the properties object already exists, get it from the project's annotations. If False = e.Project.Annotations.TryGetValue(propertiesObject) Then ' Otherwise, create a new properties object and add it to the annotations. propertiesObject = New CustomProjectProperties(e.Project) e.Project.Annotations.Add(propertiesObject) End If e.PropertySources.Add(propertiesObject) End Sub End Class Public Class CustomProjectProperties Private sharePointProject As ISharePointProject = Nothing Private Const MapImagesFolderPropertyDefaultValue As Boolean = False Private Const MapImagesFolderPropertyId = "ContosoMapImagesFolderProperty" Public Sub New(ByVal myProject As ISharePointProject) sharePointProject = myProject End Sub ' Represents the new boolean property MapImagesFolder. ' True = Map an Images folder to the project if one does not already exist; otherwise, do nothing. ' False = Remove the Images folder from the project, if one exists; otherwise, do nothing. <DisplayName("Map Images Folder")> _ <DescriptionAttribute("Specifies whether an Images folder is mapped to the SharePoint project.")> _ <DefaultValue(MapImagesFolderPropertyDefaultValue)> _ Public Property MapImagesFolder As Boolean Get Dim propertyStringValue As String = String.Empty ' Try to get the current value from the .user file; if it does not yet exist, return a default value. If Not sharePointProject.ProjectUserFileData.TryGetValue(MapImagesFolderPropertyId, propertyStringValue) Then Return MapImagesFolderPropertyDefaultValue Else Return CBool(propertyStringValue) End If End Get Set(ByVal value As Boolean) If value Then If Not ImagesMappedFolderInProjectExists(sharePointProject) Then ' An Images folder is not mapped to the project, so map one. Dim mappedFolder As IMappedFolder = sharePointProject.MappedFolders.Add(MappedFolderType.Images) sharePointProject.ProjectService.Logger.WriteLine( _ mappedFolder.Name & " mapped folder added to the project.", LogCategory.Status) End If ElseIf (ImagesMappedFolderInProjectExists(sharePointProject) AndAlso UserSaysDeleteFile()) Then ' An Images folder is mapped to the project and the user wants to remove it. DeleteFolder() End If sharePointProject.ProjectUserFileData(MapImagesFolderPropertyId) = value.ToString() End Set End Property Private Function ImagesMappedFolderInProjectExists(ByVal sharePointProject As ISharePointProject) As Boolean Dim returnValue As Boolean = False For Each folder As IMappedFolder In sharePointProject.MappedFolders ' Check to see if an Images folder is already mapped. If (folder.FolderType = MappedFolderType.Images) Then returnValue = True End If Next Return returnValue End Function Private Function UserSaysDeleteFile() As Boolean ' Ask the user whether they want to delete the Images folder. Dim returnValue As Boolean = False If (MessageBox.Show("Do you want to delete the Images folder from the project?", _ "Delete the Images folder?", MessageBoxButtons.YesNo) = DialogResult.Yes) Then returnValue = True End If Return returnValue End Function Private Sub DeleteFolder() ' The Visual Studio DTE object model is required to delete the mapped folder. Dim dteProject As EnvDTE.Project = _ sharePointProject.ProjectService.Convert(Of ISharePointProject, EnvDTE.Project)(sharePointProject) Dim targetFolderName As String = _ sharePointProject.MappedFolders.First(Function(mf) mf.FolderType = MappedFolderType.Images).Name Dim mappedFolderItem As EnvDTE.ProjectItem = dteProject.ProjectItems.Item(targetFolderName) mappedFolderItem.Delete() sharePointProject.ProjectService.Logger.WriteLine("Mapped Folder " & _ targetFolderName & " deleted", LogCategory.Status) End Sub End Class End Namespace
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.ComponentModel.Composition; using System.Windows.Forms; using Microsoft.VisualStudio.SharePoint; using EnvDTE; // Adds a new property called MapImagesFolder to any SharePoint project. // When MapImagesFolder is set to true, the Image folder is mapped to the project. // When MapImagesFolder is set to false, the Image folder is deleted from the project. namespace SP_Project_Extension { // Export attribute: Enables Visual Studio to discover and load this extension. [Export(typeof(ISharePointProjectExtension))] // Defines a new custom project property that applies to any SharePoint project. public class SPProjectExtension : ISharePointProjectExtension { // Implements ISharePointProjectService.Initialize, which determines the behavior of the new property. public void Initialize(ISharePointProjectService projectService) { // Handle events for when a project property is changed. projectService.ProjectPropertiesRequested += new EventHandler<SharePointProjectPropertiesRequestedEventArgs>(projectService_ProjectPropertiesRequested); } void projectService_ProjectPropertiesRequested(object sender, SharePointProjectPropertiesRequestedEventArgs e) { // Add a new property to the SharePoint project. e.PropertySources.Add((object)new ImagesMappedFolderProperty(e.Project)); } } public class ImagesMappedFolderProperty { ISharePointProject sharePointProject = null; public ImagesMappedFolderProperty(ISharePointProject myProject) { sharePointProject = myProject; } static bool MapFolderSetting = false; [DisplayName("Map Images Folder")] [DescriptionAttribute("Specifies whether an Images folder is mapped to the SharePoint project.")] public bool MapImagesFolder // Represents the new boolean property MapImagesFolder. // True = Map an Images folder to the project if one does not already exist; otherwise, do nothing. // False = Remove the Images folder from the project, if one exists; otherwise, do nothing. { get { // Get the current property value. return MapFolderSetting; } set { if (value) { if (!ImagesMappedFolderInProjectExists(sharePointProject)) { // An Images folder is not mapped to the project, so map one. IMappedFolder mappedFolder1 = sharePointProject.MappedFolders.Add(MappedFolderType.Images); // Add a note to the logger that a mapped folder was added. sharePointProject.ProjectService.Logger.WriteLine("Mapped Folder added:" + mappedFolder1.Name, LogCategory.Status); } } else { if (ImagesMappedFolderInProjectExists(sharePointProject) && UserSaysDeleteFile()) { // An Images folder is mapped to the project and the user wants to remove it. // The Visual Studio DTE object model is required to delete the mapped folder. // Reference the Visual Studio DTE model, get handles for the SharePoint project and project items. EnvDTE.Project dteProject = sharePointProject.ProjectService.Convert<ISharePointProject, EnvDTE.Project>(sharePointProject); string targetFolderName = sharePointProject.MappedFolders.First(mf => mf.FolderType == MappedFolderType.Images).Name; EnvDTE.ProjectItem mappedFolderItem = dteProject.ProjectItems.Item(targetFolderName); mappedFolderItem.Delete(); sharePointProject.ProjectService.Logger.WriteLine("Mapped Folder " + targetFolderName + " deleted", LogCategory.Status); } } MapFolderSetting = value; } } private bool ImagesMappedFolderInProjectExists(ISharePointProject sharePointProject) { bool retVal = false; foreach (IMappedFolder folder in sharePointProject.MappedFolders) { // Check to see if an Images folder is already mapped. if (folder.FolderType == MappedFolderType.Images) retVal = true; } return retVal; } private bool UserSaysDeleteFile() { // Prompt the user whether they want to delete the Images folder. bool retVal = false; if (MessageBox.Show("Do you want to delete the Images folder from the project?", "Delete the Images folder?", MessageBoxButtons.YesNo) == DialogResult.Yes) { retVal = true; } return retVal; } } }
生成解决方案
接下来,生成解决方案以确保编译时不会出错。
生成解决方案
- 在**“生成”菜单上,单击“生成解决方案”**。
创建 VSIX 包以部署项目属性扩展
若要部署项目扩展,请使用解决方案中的 VSIX 项目来创建 VSIX 包。 首先,通过修改 VSIX 项目中包含的 source.extension.vsixmanifest 文件来配置 VSIX 包。 然后,通过生成解决方案来创建 VSIX 包。
配置并创建 VSIX 包
在**“解决方案资源管理器”中,双击“source.extension.vsixmanifest”**文件。
Visual Studio 将在清单编辑器中打开该文件。 此编辑器提供可用于编辑清单中的 XML 的 UI。 此信息稍后会显示在**“扩展管理器”**中。所有 VSIX 包都需要 extension.vsixmanifest 文件。 有关此文件的更多信息,请参见VSIX Extension Schema Reference。
在**“产品名称”**框中键入“自定义项目属性”。
在**“作者”**框中键入 Contoso。
在**“说明”**框中,键入“用于将 Images 资源文件夹的映射切换到项目的自定义 SharePoint 项目属性”。
在编辑器的**“内容”部分中,单击“添加内容”**按钮。
在**“选择内容类型”下拉框中选择“MEF 组件”**。
提示
此值对应于 extension.vsixmanifest 文件中的 MEFComponent 元素。 此元素指定 VSIX 包中的扩展程序集的名称。 有关更多信息,请参见 MEFComponent Element (VSX Schema)。
在**“选择源”部分中,单击“项目”**选项,然后在下拉框中选择“ProjextExtension”。
此值标识您在项目中生成的程序集的名称。
完成后,单击**“确定”以关闭“添加内容”**对话框。
完成后,单击**“文件”菜单上的“全部保存”**,然后关闭清单设计器。
在**“生成”菜单上,单击“生成解决方案”**。 确保项目在编译时不会出错。
在**“解决方案资源管理器”中单击“ProjectExtensionPackage”项目,单击“显示所有文件”**按钮,然后打开 ProjectExtensionPackage 项目的生成输出文件夹。 此文件夹现在应包含一个名为 ProjectExtensionPackage.vsix 的文件。
默认情况下,生成输出文件夹为 包含项目文件的文件夹下的 ..\bin\Debug 文件夹。
测试项目属性
现在您可以对自定义项目属性进行测试了。 在 Visual Studio 的实验实例中调试和测试新的项目属性扩展最为简单。 这是您在运行 VSIX 或其他扩展性项目时创建的 Visual Studio 实例。 调试项目后,您可以将扩展安装在系统上,然后在 Visual Studio 的常规实例中继续调试和测试扩展。
在 Visual Studio 的实验实例中调试和测试扩展
利用管理凭据重新启动 Visual Studio,然后打开 ProjectExtensionPackage 解决方案。
按**“F5”**以启动项目的调试版本。
Visual Studio 将扩展安装到 %UserProfile%\AppData\Local\Microsoft\VisualStudio\10.0Exp\Extensions\Contoso\Custom Project Property\1.0 中,并启动 Visual Studio 的实验实例。
在 Visual Studio 的实验实例中,创建一个新的场解决方案 SharePoint 项目,例如一个模块。 对向导中的其他值使用默认值。
在**“解决方案资源管理器”**中单击项目节点。
“属性”窗口中将显示新的自定义属性“映射 Images 文件夹”,其默认值为 False。
将**“映射 Images 文件夹”**更改为 True。
Images 资源文件夹将添加到 SharePoint 项目中。
将**“映射 Images 文件夹”**更改为 False。
将从 SharePoint 项目中删除 Images 资源文件夹。
关闭 Visual Studio 的实验实例。