65.9K
CodeProject 正在变化。 阅读更多。
Home

导入列表 VS 2010 SharePoint 扩展

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2010 年 9 月 24 日

CPOL

11分钟阅读

viewsIcon

54240

downloadIcon

697

一个 Visual Studio 2010 SharePoint 2010 扩展,用于将现有列表定义和架构导入 SharePoint 2010 项目

引言

SharePoint 2010 是 SharePoint 2007 的一次重大升级,并且与 Visual Studio 2010 集成后,SharePoint 相关项目的开发效率得到了极大的提升。Visual Studio 2010 包含许多内置模板和功能,使开发人员只需几次鼠标点击即可完成之前需要痛苦的手动编码才能实现的功能。然而,尽管取得了这些进步,仍然有一些领域可以改进。

其中一个可以改进的领域是能够将现有列表导入到站点定义项目中。

在创建站点定义的场景下,有时您可能需要将一个现有的列表(可能已包含数据)导入到项目中。使用目前可用的方法,这可能会成为一个漫长而繁琐的过程。在本文中,我将探讨当前的方法,并提出一种使用 Visual Studio SharePoint 项目扩展的替代方案。

创建站点模板

Visual Studio 2010 现在包含一个 SharePoint 2010 项目“导入 SharePoint 解决方案包”,允许您将现有站点及其所有项(如内容类型、网站列和列表)导入到站点定义项目中。

此过程的第一步是通过“站点操作”->“将站点另存为模板”来创建现有站点的模板。这里有一个缺点是它不能用于发布站点。

点击此链接后,会出现一个表单,要求填写模板名称以及是否包含内容(例如列表中的现有数据)。保存后,模板将放置在解决方案库中。

可以从解决方案库中下载模板文件,方法是点击模板名称,在本例中是:MySiteDef

导入 SharePoint 解决方案包

在 Visual Studio 2010 中,您可以按照标准方式创建新项目,并在“新建项目”对话框中选择“导入 SharePoint 解决方案包”。

选择项目后,会显示一系列向导对话框,允许您选择站点以及项目是用于场解决方案还是沙盒解决方案。下一个对话框允许您选择早期步骤中保存的模板文件的位置。

点击“下一步”按钮后,Visual Studio 将读取指定的解决方案包并提取有关站点及其内容的信息。

如您所见,此列表包括站点中的所有内容。虽然您可以取消选择不想导入的项,但如果站点很大,找到所需的项可能会很繁琐。请注意,如果您取消选择列表所依赖的内容类型或字段,您将收到包含它或忽略警告的提示。项目创建后,您需要打开要导入列表的站点定义项目在同一个解决方案中。然后,您可以将导入项目的列表定义文件夹复制到站点定义项目中。这不会自动复制任何相关对象,如字段或内容类型;它们必须以相同的方式单独复制。

如您所见,导入单个列表定义的过程相当麻烦。

手动完成

另一种可能性是全部手动完成。您可以向项目添加一个新的“列表定义”项,并在列表的 `schema.xml` 和 `elements.xml` 文件中添加必要的元素。当然,您还必须为任何必需的字段和内容类型执行相同的操作。如果您习惯于 SharePoint 2007 开发,这种方法应该比较熟悉,但也很耗时。对于具有标准字段且没有现有实例的小型列表,这可能是一个可行的选项;然而,有更好的工具可用。

Visual Studio 扩展包

在早期版本的 Visual Studio 中,添加额外功能的方法是创建 Addin。虽然这种方法仍然支持,但在 Visual Studio 2010 中,首选方法是创建 Visual Studio 扩展包 (VSIX),因为它使用 Managed Extensibility Framework 构建。创建 SharePoint 扩展的第一个必要步骤是下载并安装 Visual Studio 2010 SDK。安装 SDK 后,您将在“新建项目”对话框的“其他项目类型”->“可扩展性”下找到一个额外的项。

创建 Visual Studio 扩展包本身就可以写成一本书,因此我将集中讨论与我们的目的相关的方面,即创建一个 Visual Studio 2010 SharePoint 项目扩展。

扩展清单

选择此项目类型后,您将看到一系列对话框,询问有关项目的其他信息,如名称和描述、要使用的图标、支持的语言以及是否支持菜单命令。回答完这些问题后,项目将创建好,其中包含几个已填充的文件,其中大部分与本次讨论无关,并且不是创建扩展所必需的。它们确实有大量的内联文档来解释它们的用途。然而,我们将重点关注的文件是 `source.extension.vsixmanifest`。此 XML 文件包含在 Visual Studio 中安装和配置扩展以及显示有关它的信息所需的所有内容。一个自定义查看器用于以漂亮的 UI 显示它。

一个令人感兴趣的部分是“支持的 VS 版本”。点击“选择版本”按钮将显示此对话框,允许您指定扩展支持的 Visual Studio 版本。对于本文的目的,默认值就足够了。

另一个让我们感兴趣的部分是“内容”部分。这允许您向包添加或删除程序集。

点击“添加内容”按钮将显示一个对话框,允许您选择内容类型及其位置。对于本文,我们正在从解决方案中的另一个项目添加 MEF 组件。

现在,让我们进入实现阶段。

导入列表扩展实现

实现程序集是一个简单的类库项目。但是,要使 MEF 和 Visual Studio 2010 识别此程序集中的任何类,您需要应用 `System.ComponentModel.Composition.ExportAttribute`。但是,在此之前,您需要向项目添加两个引用:*Microsoft.VisualStudio.SharePoint.dll* 和 *System.ComponentModel.Composition.dll*。后者包含 `ExportAttribute`,前者包含您需要实现的接口。

[Export(typeof(ISharePointProjectExtension))]
public class ImportListExtention : ISharePointProjectExtension
{
    public void Initialize(ISharePointProjectService projectService)
    {
            
    }
}

从这个代码片段可以看出,`ISharePointProjectExtention` 接口只有一个方法需要实现:`Initialize`。在此方法中,您可以订阅传递给它的 `ISharePointProjectService` 对象上的许多事件。为此,我将订阅 `ProjectMenuItemsRequested` 事件。当在解决方案资源管理器窗口中右键单击 SharePoint 项目以显示上下文菜单时,会发生此事件。在此事件的处理程序中,您可以使用 `ActionMenuItems` 集合将菜单项添加到上下文菜单的操作区域,或者在这种情况下,添加到 `AddMenuItems` 集合,该集合会将项添加到“添加”子菜单中。

void OnProjectMenuItemsRequested
	(object sender, SharePointProjectMenuItemsRequestedEventArgs e)
{
    IMenuItem menu = e.AddMenuItems.Add("Import List");
    menu.Click += new EventHandler(OnMenuClick);
}

void OnMenuClick(object sender, MenuItemEventArgs e)
{
    ImportList import = new ImportList(e.Owner as ISharePointProject);
    import.Import();
}

导入列表

单击“导入列表”菜单项后,将显示一个对话框以收集有关要导入的列表的更多信息。为了获取指定站点可用的列表,您需要通过 SharePoint 命令使用 SharePoint 服务器对象模型。

SharePoint 命令

SharePoint 不能直接从 Visual Studio 内部访问,因此您必须创建命令,通过 Visual Studio 中连接到 SharePoint 项目的 `SharePointProject` 对象提供的 `ISharePointConnection` 实现来执行。`ISharePointConnection` 接口的目的是允许通过 64 位 Visual Studio 进程 vssphost.exe 访问 SharePoint。

命令是在单独的程序集中构建的,该程序集必须针对 .NET Framework 3.5 进行编译。这一点最初让我很困惑,并导致我花了几个小时冥思苦想。该程序集可以使用 .NET Framework 4.0 进行编译和运行;但是,在调用 `ExecuteCommand` 时将生成一个异常,似乎表明命令字符串未定义。

命令是通过使用 `SharePointCommandAttribute` 装饰一个方法并为其提供一个名称来定义的。在使用 `ISharePointConnection` 对象的 `ExecuteCommand` 方法时,必须将相同的 `string` 作为第一个参数提供。

 Project.SharePointConnection.ExecuteCommand<string>
	(SharePointCommands.Commands.GetLists, SiteURL); 

对 `ISharePointConnection.ExecuteCommand` 的一些限制是,除了命令名称之外,只能传递一个其他参数,并且输入和返回对象都必须是可序列化的。`ExecuteCommand` 是一个模板方法,它允许您指定输入和返回值的类型,在上面的情况下分别是 `string` 和 `List<string>`。我们正在传递一个 `string`(要从中检索列表的站点的 URL),并返回一个 `string` 集合,代表列表名称。

[SharePointCommand("MANSoftDev.Commands.GetLists")]
public static List<string> GetLists(ISharePointCommandContext context, string siteUrl)
{
    List<string> lists = new List<string>();
    using(SPSite site = new SPSite(siteUrl))
    {
        using(SPWeb web = site.OpenWeb())
        {
            foreach(SPList item in web.Lists)
            {
                if(item.Hidden == false)
                {
                    lists.Add(item.Title);
                }
            }
        }
    }

    return lists;
}

`SharePointCommand` 方法的实现从 `ISharePointConnection` 实例接收一个 `ISharePointCommandContext` 对象,该实例正在进行调用。此对象提供了对命令将执行的 SharePoint 上下文的访问,并包含 Site 和 Web 的属性以及一个 Logger,允许将消息写入 Visual Studio 的输出窗口。

`GetLists` 命令的实现非常简单。我们从传入的 URL 创建一个 `SPSite`,然后遍历 Web 中可见的 `SPList`,然后返回每个列表名称的集合。但是,创建列表定义和实例的文件会更复杂。

创建列表定义和实例

要获取构造文件所需的信息,您必须提供站点 URL 和列表名称。但是,由于 `ExecuteCommand` 方法只能传递一个参数,因此您需要使用包装器对象。

[Serializable]
public struct GetListData
{
    public string SiteUrl { get; set; }
    public string ListName { get; set; }
    public bool IncludeContent { get; set; }
}

project.SharePointConnection.ExecuteCommand<sharepointcommands.getlistdata>
                (SharePointCommands.Commands.GetListData, data);

虽然从 `SharePointCommand` 返回 `SPList` 会很好,但它不可序列化。这意味着创建定义和实例文件所需的所有内容都必须在命令实现方法中创建。好消息是,大部分信息都可以从 `SPList` 对象中轻松获得。

Schema、列表定义和列表实例文件

列表 schema 文件描述了列表的内容类型、字段、视图等。此文件的基本结构如下:

<List>
    <MetaData>
        <ContentTypes />
        <Fields />
        <Views>
            <View>
                <Query />
            </View>
        </Views>
    </MetaData>
</List>

可以很容易地从 `SPList` 获取 `View` 和 `Fields` 元素。

string views = list.Views.SchemaXml;
string fields = list.Fields.SchemaXml;

`ContentType` 只需多一点操作即可提取所需的 XML,而不会有任何额外的位。

string contentTypes = "<contenttypes>";

foreach(SPContentType item in list.ContentTypes)
{
    contentTypes += item.Parent.SchemaXmlWithResourceTokens;
}
contentTypes += "</contenttypes>";

从这里开始,只需构建 XML 文件即可。

XElement schema = new XElement(ns + "List",
    new XAttribute("Title", title),
    new XAttribute("Direction", "none"),
    new XAttribute("Url", "Lists/" + url),
    new XAttribute("BaseType", baseType),
    new XAttribute("Type", templateType),
    new XAttribute("BrowserFileHandling", "Permissive"),
    new XAttribute("FolderCreation", "FALSE"),
    new XAttribute("Catalog", "FALSE"),
    new XAttribute("SendToLocation", "|"),
    new XAttribute("ImageUrl", "/_layouts/images/itgen.png"),
    new XAttribute(XNamespace.Xmlns + "ows", "Microsoft SharePoint"),
    new XAttribute(XNamespace.Xmlns + "spctf", 
	"http://schemas.microsoft.com/sharepoint/v3/contenttype/forms"),

    new XElement("MetaData",
        contentTypesXML,
        fieldsXML,
        CreateFormsElement(),
        CleanViews(viewsXML)
        )
    );

并稍微清理一下 `View` 元素。

private static XElement CleanViews(XElement views)
{
    views.Elements("View").Attributes("Name").Remove();

    foreach(XElement view in views.Elements("View"))
    {
        // Need to remove pathing info from this attribute
        view.SetAttributeValue("Url", "AllItems.aspx");
        // Although these attributes are optional, they must be
        // set for Visual Studio to deploy
        view.SetAttributeValue("SetupPath", @"pages\viewpage.aspx");
        view.SetAttributeValue("WebPartZoneID", "Main");
    }

    return views;
}

列表定义和实例非常容易创建。

private static XElement CreateListTemplate
  (string name, int templateType, int baseType, string displayName, string description)
{
    XNamespace ns = "http://schemas.microsoft.com/sharepoint/";

    XElement xml = new XElement(ns + "Elements",
        new XElement("ListTemplate",
            new XAttribute("Name", name), // Name of project item also
            new XAttribute("Type", templateType),  // Matches TemplateType 
                                                   // in ListInstance
            new XAttribute("BaseType", baseType),
            new XAttribute("OnQuickLaunch", "TRUE"),
            new XAttribute("SecurityBits", "11"),
            new XAttribute("Sequence", "410"),
            new XAttribute("DisplayName", displayName),
            new XAttribute("Description", description),
            new XAttribute("Image", "/_layouts/images/itgen.png"))
        );

    // .NET wants to add an empty xmlns element
    // to the child elements when a namespace is 
    // added to the parent. They only way to remove it
    // is from the string representation of the element
    return XElement.Parse(xml.ToString().Replace("xmlns=\"\"", ""));
}

private static XElement CreateListInstance
    (string title, int templateType, string url, string description)
{
    XNamespace ns = "http://schemas.microsoft.com/sharepoint/";

    XElement xml = new XElement(ns + "Elements",
        new XElement("ListInstance",
            new XAttribute("Title", title + " Instance"),
            new XAttribute("OnQuickLaunch", "TRUE"),
            new XAttribute("TemplateType", templateType), // Matches type 
                                                          // in ListTemplate
            new XAttribute("Url", "Lists/" + url),
            new XAttribute("Description", description))
        );

    // .NET wants to add an empty xmlns element
    // to the child elements when a namespace is 
    // added to the parent. They only way to remove it
    // is from the string representation of the element
    return XElement.Parse(xml.ToString().Replace("xmlns=\"\"", ""));
}

同样,由于 `SPList` 对象不可序列化,您需要使用包装器对象将此信息返回给调用者。

[Serializable]
public struct ListData
{
    public string ListName { get; set; }
    public XElement Schema { get; set; }
    public XElement ListInstance { get; set; }
    public XElement ListTemplate { get; set; }

    public bool IsError { get; set; }
    public string ErrorMessage { get; set; }
}

将 SharePointCommand 添加到包

现在 SharePoint 命令程序集已构建完成,需要将其添加到 Visual Studio 扩展包中。就像您将 MEF 组件添加到包中一样,还需要添加 `SharePointCommand`,以便进行部署。正如您所见,此程序集的自定义扩展类型为 `Custom Extension Type`,类型设置为 `SharePoint.Commands.v4`。此键使程序集能够被识别为包含命令并可访问。

添加到 Visual Studio

现在所有必需的文件都已创建,您需要将所有内容添加到 Visual Studio 项目中。使用表示 Visual Studio 中当前项目的 `ISharePointProject` 引用,这相对直接。与非 SharePoint 项目一样,项目维护着所有文件和文件夹的集合,并且可以使用 `ISharePointProjectItemsCollection` 进行引用。虽然您可以直接将文件添加到项目树,但这对于 SharePoint 项目不起作用。SharePoint 项目使用名为 `SharePointProjectItem.spdata` 的文件来维护文件夹中文件及其处理方式的元数据。除非您选择“查看所有文件”,否则此文件不会包含在解决方案资源管理器树中。

<ProjectItem Type="Microsoft.VisualStudio.SharePoint.ListDefinition" 
             DefaultFile="Elements.xml" 
             SupportedTrustLevels="All" 
             SupportedDeploymentScopes="Web, Site"
xmlns="http://schemas.microsoft.com/VisualStudio/2010/
SharePointTools/SharePointProjectItemModel">
  <Files>
    <ProjectItemFile Source="Elements.xml" Target="CustomList\" Type="ElementManifest" />
    <ProjectItemFile Source="Schema.xml" Target="CustomList\" Type="ElementFile" />
  </Files>
</ProjectItem>

此文件中的关键元素是 type 属性。对于 `ProjectItem` 元素,它设置为 `Microsoft.VisualStudio.SharePoint.ListDefinition`,告知 Visual Studio 文件夹中包含的项的类型。对于 `ProjectItemFiles` 元素,它们分别设置为 `ElementManifest` 和 `ElementFile`,指示 Visual Studio 在部署解决方案时如何处理文件。

使用 `ISharePointProjectItemsCollection`,我们首先向集合添加文件夹,并将类型指定为 `Microsoft.VisualStudio.SharePoint.ListDefinition`,这会返回一个 `ISharePointProjectItem` 对象。由于项目项是项目树中的物理文件,您必须在将 XML 文件添加到项目之前,将从 `SharePointCommand` 调用返回的 XML 写入文件。

ISharePointProjectItem item = project.ProjectItems.Add(listData.ListName, LIST, false);
                    
string folder = Path.GetDirectoryName(item.FullPath);

// Now to add the files for list definition and schema
string fileName = Path.Combine(folder, "Schema.xml");
WriteFile(fileName, listData.Schema);
ISharePointProjectItemFile file = item.Files.AddFromFile(fileName);
// Must set type to update spdata file
file.DeploymentType = DeploymentType.ElementFile;

private void WriteFile(string fileName, XElement element)
{
    XmlWriterSettings xws = new XmlWriterSettings();
    xws.OmitXmlDeclaration = false;
    xws.Indent = true;

    using(XmlWriter writer = XmlWriter.Create(fileName, xws))
    {
        element.WriteTo(writer);  
    }
}

完成

现在所有文件都已添加到 SharePoint 项目中,并设置了适当的元数据,以便 Visual Studio 在部署期间知道如何处理每个文件。

显然,有些设置可能需要手动调整;但是,您必须承认,进行这些调整比手动创建所有文件要容易得多。

有改进空间

通过在导入列表时将任何自定义网站列和/或内容类型添加到项目,可以改进此解决方案。这是一个更复杂的过程,需要检查每个网站列与内置网站列进行比较,以确定是否需要添加它们,然后检查以确保它们尚未包含在项目中。内容类型也是如此。有了提供的信息,您应该就能理解添加此功能所需的步骤。

资源

历史

  • 2010 年 9 月 24 日:首次上传
© . All rights reserved.