如何:在所有 Web 服务器上运行代码

上次修改时间: 2010年4月19日

适用范围: SharePoint Foundation 2010

许多开发方案(特别是那些具有管理功能的开发方案)都需要在服务器场中的每个前端 Web 服务器上运行相同的代码。例如,Microsoft SharePoint Foundation 场中所有前端 Web 服务器的配置和设置必须相同,至少在响应 HTTP 请求的方式上应该如此。因此,必须在所有前端 Web 服务器上对 web.config 文件的设置进行更改。再比如,必须将作为场解决方案一部分的程序集部署到每个前端 Web 服务器的全局程序集缓存 (GAC)。

对于很多类型的配置和部署任务,SharePoint Foundation 附带了专用 API,以确保服务器保持同步状态。例如,SPWebConfigModification 类提供了用于修改 web.config 文件的堆栈设置的功能。有关详细信息,请参阅如何:以编程方式添加和删除 Web.config 设置。同样,无论是在管理中心应用程序中还是在 SharePoint Management Shell 中部署场解决方案,作为解决方案一部分的任何程序集都将部署到每个前端 Web 服务器的 GAC。可以使用 SPSolution.Deploy() 方法创建自定义场解决方案部署功能。

所有这些专用 API 都使用 SharePoint Foundation 计时器作业完成其工作。这些作业之所以被称为计时器作业 是因为可以将每个作业设置为在将来的某个指定时间执行(或者在创建后立即执行)。也可以将它们称为多服务器作业,因为可以将它们设置为在多台 Web 服务器(包括所有前端 Web 服务器)或特定服务器上运行。如果需要某个功能在所有前端 Web 服务器上运行,但是没有适用于该功能的专用 API,则可以使用计时器作业 API。本主题介绍相关操作方法。

备注

前端 Web 服务器和应用程序服务器之间的区别在 SharePoint Foundation 2010 中比在早期版本的产品中更加抽象。可将 SharePoint Foundation 软件的全部功能安装在所有服务器上,不管这些服务器的状态是作为应用程序服务器还是前端 Web 服务器。但是有一个例外,即,承载场的 Microsoft SQL Server 数据库的服务器。通常,根本不会在此计算机上安装 SharePoint Foundation。因此,在本主题中,"所有服务器"一词不包括未安装 SharePoint Foundation 的任何此类专用数据库服务器。一般而言,如果 Microsoft SharePoint Foundation Web 应用程序服务在 SharePoint Foundation 服务器上运行,则该服务器就是前端 Web 服务器;否则是应用程序服务器。

定义计时器作业

  1. 在 Visual Studio 中,启动一个空白 SharePoint 项目。将其设置为场解决方案,而不是沙盒解决方案。

  2. 在"解决方案资源管理器"中,突出显示项目名称,并确保"属性"窗格中的"将程序集包含在包中"设置为"true"。

  3. 向该项目中添加 C# 或 Visual Basic 类文件。

  4. 打开类文件,并为 Microsoft.SharePoint 和 Microsoft.SharePoint.Administration 命名空间添加 using 语句(在 Visual Basic 中为 Imports)。您可能需要添加其他 using 语句,具体取决于您要在所有服务器上执行的代码调用的命名空间。对于本主题中的运行示例,请为 System.Xml.Linq、System.Xml.XPath、System.IO 和 System.Runtime.InteropServices 添加 using 语句。

  5. 更改命名空间,使其符合命名空间命名指南(该链接可能指向英文页面)中的指导原则;例如 Contoso.SharePoint.Administration。

  6. 更改类声明,以指定类继承自 SPJobDefinition 或从 SPJobDefinition 派生的一些类。

  7. 使用 GuidAttribute 属性修饰类声明。这是对直接或间接从 SPPersistedObject 派生的所有类的要求。

  8. 向类中添加直接调用基本构造函数的默认(无参数)构造函数。

  9. 添加具有 StringSPWebApplicationSPServerSPJobLockType 类型的参数的构造函数。它的实现应该调用基本构造函数 SPJobDefinition(String, SPWebApplication, SPServer, SPJobLockType)。此时,您的 C# 代码应该类似于下面的示例。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;
    
    using System.Xml.Linq;
    using System.Xml.XPath;
    using System.IO; 
    using System.Runtime.InteropServices;
    
    namespace Contoso.SharePoint.Administration
    {
        [Guid("9573FAD9-ED89-45E8-BD8B-6A5034E03895")]
        public class MyTimerJob : SPJobDefinition
        {
            public MyTimerJob() : base() { }
    
            public MyTimerJob(String name, SPWebApplication wApp, SPServer server, SPJobLockType lockType)
                : base(name, wApp, server, lockType) { }
        }
    }
    

    备注

    如果希望作业在所有 服务器(包括应用程序服务器)上运行,您的类应该派生自 SPServiceJobDefinition。将定时服务 (SPFarm.Local.TimerService) 作为 SPServiceJobDefinition(String, SPService) 构造函数的 SPService 参数传递。

  10. 添加 DisplayNameDescription 属性的重写。DisplayName 属性是显示在管理中心应用程序的作业定义、计划作业和作业历史记录列表中的作业的友好名称。Description 是作业的说明。为方便起见,在下面的示例中,重写代码是分配的文本字符串。在实际情形中,请考虑为每个重写代码分配本地化字符串。

    public override string DisplayName
    {
        get
        {
            // TODO: return a localized name
            return "Add Contoso Mobile Adapter";
        }
    }
    
    public override string Description
    {
        get
        {
            // TODO: return a localized description
            return "Adds a mobile adapter for Contoso's voting Web Part.";
        }
    }
    
  11. 向类中添加 Execute(Guid) 方法的重写。

    public override void Execute(Guid targetInstanceId)
    {
        // INSERT HERE CODE THAT SHOULD RUN ON ALL SERVERS.
    }
    
  12. 方法的实现仅包含要在所有前端 Web 服务器上运行的代码。此代码由 SharePoint 2010 定时服务调用,该服务应该在服务器场中的所有 SharePoint Foundation 服务器上运行。在执行作业时并在定时服务的用户上下文中运行作业时,Execute(Guid) 方法由定时服务调用。在单服务器 SharePoint Foundation 安装中,此用户通常为 Network Service。但需要关注的情况是多服务器场,在这种情况下,定时服务在同一域用户帐户的上下文中运行,而该服务器场使用此帐户对内容和配置数据库执行读取和写入操作。此用户不是任何服务器上的计算机管理员,而是所有服务器上 WSS_ADMIN_WPG 用户组的成员。它对 %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ 树和 c:\Inetpub\wwwroot\wss 树中的文件夹具有读取、写入和执行权限。

    在本主题中,假定您已经创建了一个移动 Web 部件适配器,以便在移动版本的 Web 部件页面上使用。必须在 Web 应用程序的 compat.browser 文件中注册该适配器。此文件位于 C:\Inetpub\wwwroot\wss\VirtualDirectories\port_number\App_Browsers 文件夹中,其中 port_number 是 Web 应用程序的端口号,如"80"。每个前端 Web 服务器都有自己的文件副本,并且必须对所有文件副本进行相同的编辑。以下代码在其上执行有计时器作业的每台服务器的 compat.browser 文件中注册该适配器。(本主题稍后将讨论如何确保在所有服务器上执行该注册作业。)

    提示提示

    为方便起见,本示例仅更改默认 URL 区域的 compat.browser。在实际情形中,请考虑遍历 IisSettings 的所有成员。

    public override void Execute(Guid targetInstanceId)
    {
        // Set the path to the file. The code that creates the MyTimerJob object associates
        // the job with a Web application.
        String pathToCompatBrowser = this.WebApplication.IisSettings[SPUrlZone.Default].Path 
                                   + @"\App_Browsers\compat.browser";
    
        XElement compatBrowser = XElement.Load(pathToCompatBrowser);
    
        // Get the node for the default browser.
        XElement controlAdapters = compatBrowser.XPathSelectElement("./browser[@refID = \"default\"]/controlAdapters");
    
        // Create and add the markup.
        XElement newAdapter = new XElement("adapter");
        newAdapter.SetAttributeValue("controlType", 
            "Contoso.SharePoint.WebPartPages.VotingWebPart, Contoso, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ffec2e2af2b4c675");
        newAdapter.SetAttributeValue("adapterType",
            "Contoso.SharePoint.WebPartPages.VotingWebPartMobileAdapter, Contoso, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ffec2e2af2b4c675");
        controlAdapters.Add(newAdapter);
    
        // Overwrite the old version of compat.browser with your new version.
        compatBrowser.Save(pathToCompatBrowser);
    }
    
  13. 在 Visual Studio 的"生成"菜单中,选择"部署解决方案"。如果 Visual Studio 中空白 SharePoint 项目的"网站 URL"属性指向单服务器 SharePoint Foundation 场,则此操作将编译程序集,将其打包到 SharePoint Foundation 解决方案 (wsp) 文件中,将该包上载到场的解决方案库,并将程序集部署到 GAC。但在本主题中,需要关注的是如何部署多服务器场。可以将"网站 URL"属性设置为指向此类服务器场,但在这种情况下,将解决方案上载到场的解决方案库后单击"部署解决方案"时不会执行任何操作。这时,您需要部署解决方案。一种方法是在管理中心应用程序中直接从库部署。还可以使用 SharePoint Management Shell。另一方法是从 Visual Studio 的"生成"菜单中选择"打包"。此操作将编译程序集并将其打包。然后您可以将该程序包安装到场的解决方案库中并使用 SharePoint Management Shell 对其进行部署。不管您采用哪种方法,在多服务器场中,部署步骤都会将程序集安装到所有前端 Web 服务器的 GAC 中。

创建计时器作业实例

将定义计时器作业类型的程序集部署到所有服务器之后,可以通过编程方式创建和安排计时器作业实例。可以将执行此操作的代码包含在各种开发上下文中。如果自定义计时器作业是要部署为 SharePoint Foundation 功能的解决方案的一部分,则可以在功能接收器的 FeatureActivated(SPFeatureReceiverProperties) 方法的重写中包含作业创建代码。(此功能的作用域必须为场或 Web 应用程序)。另一可行方法是使用创建作业的自定义操作扩展管理中心应用程序。还可以创建能够在 SharePoint Management Shell 中运行的自定义 PowerShell cmdlet。在本主题中,我们使用简单的控制台应用程序。无论作业创建代码位于什么位置,它必须在服务器场管理员的用户上下文中运行。

代码必须完成的主要任务包括:构造自定义计时器作业类型的对象,将其与 Web 应用程序相关联,并设置其 Schedule 属性。

创建和安排计时器作业

  1. 在 Visual Studio 中启动控制台应用程序项目。它可以与您的计时器作业项目位于同一 Visual Studio 解决方案中,也可以是完全独立的 Visual Studio 解决方案。两种方法各有利弊。在本主题中,我们假定使用完全独立的 Visual Studio 解决方案。

  2. 右键单击"解决方案资源管理器"中的项目名称并选择"属性"。

  3. 在"应用程序"选项卡上,确保"目标框架"为 .NET Framework 3.5。

  4. 在"生成"选项卡上,确保"目标平台"为"x64"或"任何 CPU"。有关进行选择的信息,请参阅如何:设置正确的目标框架和 CPU

  5. 在相应菜单上单击"保存所有文件"按钮。

  6. 在"解决方案资源管理器"中,右键单击项目名称并选择"添加引用"。使用"项目"或"浏览"选项卡添加对在上述过程中创建的程序集的引用。

  7. 添加对 Microsoft.SharePoint 和 Microsoft.SharePoint.Security 程序集的引用。

  8. 打开代码文件,并为 Microsoft.SharePointMicrosoft.SharePoint.Administration 命名空间添加 using 语句(在 Visual Basic 中为 Imports)。

  9. 更改命名空间,使其与在上述过程中用于您的自定义计时器作业的命名空间相符,或者使用其他命名空间,但是,要为在其中声明您的自定义计时器作业的命名空间添加 using 语句。

  10. 将以下代码添加到 Main 方法中。

    // Get a reference to the Web application for which you want to register the mobile Web Part adapter.
    SPWebApplication webApp = SPWebApplication.Lookup(new Uri("https://localhost/"));
    

    这只是获取对 Web 应用程序的引用的一种方法。有关详细信息,请参阅获取对网站、Web 应用程序和其他关键对象的引用

  11. 将以下代码添加到 Main 方法中。

    // Create the timer job.
    MyTimerJob myTJ = new MyTimerJob("contoso-job-add-mobile-adapter", webApp, null, SPJobLockType.None);
    

    对于此代码,请注意下列几点:

    • MyTimerJob 的构造函数的第一个参数是作业的内部名称。按照惯例,内部作业名称采用小写、带有连字符并且以单词"job"开头。请考虑在内部作业名称的开头添加您的公司名称,如本示例所示。

    • 第二个参数指定应该应用此作业的 Web 应用程序。

    • 第三个参数可用于指定应在其上运行此作业的特定服务器。如果应在所有前端 Web 服务器上运行此作业,则为 null。

    • 第四个参数确定是否在所有前端 Web 服务器上执行此作业。传递 SPJobLockType.None 可确保在运行 Microsoft SharePoint Foundation Web 应用程序服务的所有服务器上运行此作业。相反,传递 SPJobLockType.Job 可确保仅在运行 Microsoft SharePoint Foundation Web 应用程序服务的第一台可用服务器上运行此作业。(还有第三个可能的值。有关详细信息,请参阅 SPJobDefinition 以及有关其构造函数和其他成员的主题。)

  12. 将以下代码添加到 Main 方法中。

    // Schedule the job.
    myTJ.Schedule = new SPOneTimeSchedule(DateTime.Now.AddSeconds(30.0));
    

    SPOneTimeSchedule 是从 SPSchedule 类派生并且可用作 Schedule 属性的值的几个类之一。可以通过向构造函数传递 Now 来将作业设置为立即运行。但是添加一个较短的时间(在本示例中为 30 秒)在开发阶段可能会有所帮助,因为这将使您有时间查看作业是否显示在管理中心应用程序的作业定义和计划的作业列表中。完成作业后,它将显示在作业历史记录列表中。定期作业始终位于作业定义列表中,但是在从创建完成到运行作业的这段时间内(在本示例中为 30 秒)存在一次性作业(即 Schedule 属性的值设置为 SPOneTimeSchedule 对象的作业),因此该作业显示在作业定义列表中。运行后,该作业将被自动删除。

  13. 将以下代码添加到 Main 方法中。

    // Save the scheduled job instance to the configuration database.
    myTJ.Update();
    

编译代码

  1. 在 Visual Studio 中生成项目。

  2. 在服务器场管理员的用户上下文中,在场中的任意服务器上运行可执行文件。

    在根据计划执行之前,作业同时显示在管理中心应用程序的作业定义和计划作业列表中。在本示例中为 30 秒后运行。在计划时间,作业在所有服务器上执行。然后,它显示在管理中心应用程序的作业历史记录列表中,并不再显示在计划的作业列表中。因为它是一次性作业,所以在成功完成后,该作业将不再显示在作业定义列表中。

  3. 确认作业已在所有服务器上执行。在运行示例中,可通过执行以下操作进行确认:在每台服务器上,确认已将具有两个属性的 controlAdapter 元素添加到 compat.browser 文件中,该文件位于 C:\Inetpub\wwwroot\wss\VirtualDirectories\port_number\App_Browsers 文件夹中,其中 port_number 是 Web 应用程序的端口号。