Procedimiento para ejecutar código en todos los servidores web

Última modificación: lunes, 19 de abril de 2010

Hace referencia a: SharePoint Foundation 2010

Muchos de los escenarios de desarrollo, sobre todo aquellos que tienen funciones administrativas, necesitan que se ejecute el mismo código en todos los servidores front-end web de un conjunto o granja de servidores. Por ejemplo, todos los servidores front-end web de una granja de Microsoft SharePoint Foundation están configurados y aprovisionados de forma idéntica, al menos con respecto a la forma en que responden a solicitudes HTTP. Por lo tanto, los cambios en los valores de configuración de un archivo web.config se deben realizar en todos los servidores front-end web. Para obtener otro ejemplo, se debe implementar un ensamblado que forme parte de una solución de granja de servidores en la memoria caché global de ensamblados (GAC) de cada servidor front-end web.

Para muchos tipos de configuración y tareas de implementación, SharePoint Foundation viene con API creadas para propósitos específicos que garantizan que los servidores permanezcan sincronizados. Por ejemplo, la clase SPWebConfigModification proporciona funcionalidad para modificar la configuración de la pila de archivos web.config. Para obtener más información, vea Procedimiento para agregar y quitar la configuración de Web.config mediante programación. De forma similar, al implementar una solución de granja de servidores, ya sea en la aplicación de Administración central o en la Consola de administración de SharePoint, cualquier ensamblado que forma parte de la solución se implementa en la GAC de cada servidor front-end web. Se puede crear una funcionalidad de implementación de soluciones de granja personalizada con el método SPSolution.Deploy().

Todas estas API creadas con propósitos específicos usan trabajos del temporizador de SharePoint Foundation para realizar su trabajo. Se denominan trabajos del temporizador, ya que cada uno de ellos se puede establecer para ejecutarse en un momento determinado en el futuro (o inmediatamente después de su creación). Pero también podrían denominarse trabajos multiservidor porque se pueden establecer para ejecutarse en varios servidores web, incluidos todos los servidores front-end web, o en un servidor específico. Cuando se necesita que una función se ejecute en todos los servidores front-end web y no hay ninguna API creada específicamente para la función, se pueden usar las API del trabajo del temporizador. En este tema se explica cómo.

Nota

La distinción entre un servidor front-end web y un servidor de aplicaciones es más nocional en SharePoint Foundation 2010 que en las versiones anteriores del producto. Con una excepción, todo el software de SharePoint Foundation se instala en todos los servidores independientemente de su estado como servidores de aplicaciones o servidores front-end web. (La excepción es el servidor que hospeda la base de datos de Microsoft SQL Server para la granja de servidores. Por lo general, SharePoint Foundation no se instala en absoluto en este equipo. En lo sucesivo, en este tema, la frase "todos los servidores" excluirá cualquier servidor de bases de datos dedicado en el que SharePoint Foundation no esté instalado). Como regla general, un servidor de SharePoint Foundation es un un servidor front-end web si el servicio de la aplicación web de Microsoft SharePoint Foundation se ejecuta en él; de lo contrario, se trata de un servidor de aplicaciones.

Para definir un trabajo del temporizador

  1. En Visual Studio, inicie un Proyecto de SharePoint vacío. Conviértalo en una solución de granja, en lugar de una solución de espacio aislado.

  2. Resalte el nombre del proyecto en el Explorador de soluciones y asegúrese de que Incluir ensamblado en paquete está establecido en true en el panel Propiedades.

  3. Agregue un archivo de clase de Visual Basic o C# al proyecto.

  4. Abra el archivo de clase y agregue using instrucciones (Imports en Visual Basic) para los nombres de espacios Microsoft.SharePoint y Microsoft.SharePoint.Administration. Es posible que tenga que agregar otras instrucciones using en función de los nombres de espacios llamados por el código que desea ejecutar en todos los servidores. En el caso del ejemplo que se ejecuta en este tema, agregue instrucciones using para System.Xml.Linq, System.Xml.XPath, System.IO y System.Runtime.InteropServices.

  5. Cambie el espacio de nombres para que cumpla las instrucciones de nomenclatura de espacios de nombres; por ejemplo, Contoso.SharePoint.Administration.

  6. Cambie la declaración de clase para especificar que la clase hereda de SPJobDefinition o de una clase que se deriva de SPJobDefinition.

  7. Decore la declaración de clase con un atributo GuidAttribute. Este requisito se aplica a cualquier clase que se deriva directa o indirectamente de SPPersistedObject.

  8. Agregue un constructor predeterminado (sin parámetros) a la clase que simplemente llama al constructor base.

  9. Agregue un constructor con parámetros de tipos String, SPWebApplication, SPServer y SPJobLockType. Su implementación debe llamar al constructor base SPJobDefinition(String, SPWebApplication, SPServer, SPJobLockType). A continuación, se muestra el aspecto que en este momento debería tener el código en 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) { }
        }
    }
    

    Nota

    Si desea que un trabajo se ejecute en todos los servidores, incluidos los servidores de aplicaciones, la clase debería derivarse de SPServiceJobDefinition. Pase el servicio del temporizador (SPFarm.Local.TimerService) como el parámetro SPService del constructor SPServiceJobDefinition(String, SPService).

  10. Agregue invalidaciones de las propiedades DisplayName y Description. La propiedad DisplayName es el nombre descriptivo del trabajo que aparece en las definiciones de trabajo, trabajos programados e historiales de trabajos en la aplicación de Administración central. Description es una descripción del trabajo. Por motivos de simplicidad, en el siguiente ejemplo éstas son cadenas literales asignadas. En un escenario más realista, considere la posibilidad de asignar una cadena localizada a cada uno.

    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. Agregue una invalidación del método Execute(Guid) a la clase.

    public override void Execute(Guid targetInstanceId)
    {
        // INSERT HERE CODE THAT SHOULD RUN ON ALL SERVERS.
    }
    
  12. La implementación del método consiste simplemente en el código que se va a ejecutar en todos los servidores front-end web. El servicio Temporizador de SharePoint 2010 llama a este código; dicho servicio debería ejecutarse en todos los servidores de SharePoint Foundation en la granja. El servicio del temporizador llama al método Execute(Guid) cuando el trabajo se lleva a cabo y se ejecuta en el contexto de usuario del servicio del temporizador. En una instalación de SharePoint Foundation de un solo servidor, este usuario es normalmente Servicio de red. Pero el caso interesante es una granja de varios servidores, donde el servicio del temporizador se ejecuta en el contexto de la misma cuenta de usuario de dominio que la granja usa para leer y escribir en las bases de datos de configuración y contenido. Este usuario no es un administrador del equipo en ningún servidor, pero es un miembro del grupo de usuarios WSS_ADMIN_WPG en todos los servidores. Tiene permisos de lectura, escritura y ejecución en las carpetas del árbol %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ y el árbol c:\Inetpub\wwwroot\wss.

    En este tema, se asume un escenario en el que se creó un adaptador de elemento web móvil para usarse en la versión móvil de las páginas de elementos web. Es necesario registrar el adaptador en el archivo compat.browser de la aplicación web. Este archivo se encuentra en la carpeta C:\Inetpub\wwwroot\wss\VirtualDirectories\número_puerto\App_Browsers, donde número_puerto es el número de puerto de la aplicación web, por ejemplo "80". Cada servidor front-end web tiene su propia copia del archivo y todos éstos deben editarse de forma idéntica. El siguiente código registra el adaptador en el archivo compat.browser de cada servidor en el que se ejecuta el trabajo del temporizador. (La forma en que se garantiza que el trabajo se ejecute en todos los servidores se explica más adelante en este tema).

    SugerenciaSugerencia

    Por motivos de simplicidad, este ejemplo cambia el archivo compat.browser sólo para la zona de la dirección URL predeterminada. En un escenario más realista, considere la posibilidad de recorrer en bucle todos los miembros de 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. Seleccione Implementar solución en el menú Generar de Visual Studio. Si la propiedad URL del sitio del Proyecto de SharePoint vacío en Visual Studio apunta a una granja de SharePoint Foundation de solo servidor, esta acción compila el ensamblado, lo empaqueta en un archivo de solución (wsp) de SharePoint Foundation, carga el paquete a la galería de soluciones de la granja e implementa el ensamblado en la GAC. Pero para este tema, el caso interesante es la implementación de una granja de varios servidores. Se puede establecer la propiedad URL del sitio para que apunte a dicha granja de servidores, pero en ese caso, si se hace clic en Implementar solución, no ocurre nada después de que se carga la solución en la galería de soluciones de la granja de servidores. A partir de ahí, es necesario implementarla. Una forma de hacerlo es directamente desde la galería en la aplicación de Administración central. También se puede usar Consola de administración de SharePoint. Otra opción es seleccionar Paquete en el menú Generar de Visual Studio. Con esta acción se compila y se empaqueta el ensamblado. A continuación, se puede instalar el paquete en la galería de soluciones de la granja de servidores e implementarlo mediante Consola de administración de SharePoint. Independientemente de la técnica que se use, en una granja de varios servidores, el paso de implementación instala al ensamblado en la GAC de todos los servidores front-end web.

Crear una instancia de un trabajo del temporizador

Una vez que el ensamblado que define el trabajo del temporizador se implementa en todos los servidores, se pueden crear mediante programación instancias del mismo, y programarlas. El código que lo hace se puede incluir en una gran variedad de contextos de desarrollo. Si el trabajo del temporizador personalizado forma parte de una solución que se va a implementar como característica de SharePoint Foundation, se puede incluir el código de creación de trabajos en una invalidación del método FeatureActivated(SPFeatureReceiverProperties) en un receptor de características. (El ámbito de esta característica debe ser la granja de servidores o la aplicación web). Otra posibilidad es ampliar la aplicación de Administración central con una acción personalizada que cree el trabajo. También se puede crear un cmdlet personalizado de PowerShell que se puede ejecutar en Consola de administración de SharePoint. En este tema, se usa una aplicación de consola sencilla. Independientemente de la ubicación en que se encuentra el código de creación, éste debe ejecutarse en el contexto de usuario de un administrador de la granja de servidores.

Las principales tareas que el código debe realizar son la construcción de un objeto del tipo de trabajo del temporizador personalizado, su asociación con una aplicación web y el establecimiento de su propiedad Schedule.

Para crear y programar un trabajo del temporizador

  1. Inicie un proyecto de aplicación de consola en Visual Studio. Puede tratarse de un segundo proyecto dentro de la misma solución de Visual Studio como el proyecto del trabajo del temporizador o una solución de Visual Studio totalmente independiente. Ambos métodos presentan ventajas y desventajas. En este tema, se asume que se usa una solución de Visual Studio totalmente independiente.

  2. Haga clic con el botón secundario en el nombre del proyecto en el Explorador de soluciones y seleccione Propiedades.

  3. En la ficha Aplicación, asegúrese de que el marco de trabajo de destino sea .NET Framework 3.5.

  4. En la ficha Generar, asegúrese de que el valor de Plataforma de destino sea x64 o Cualquier CPU. Para obtener información sobre cómo elegir, vea Procedimiento para establecer el marco de destino y CPU correctos.

  5. Haga clic en el botón Guardar todos los archivos del menú.

  6. Haga clic con el botón secundario en el nombre del proyecto en el Explorador de soluciones y seleccione Agregar referencia. Use la ficha Proyectos o Examinar para agregar una referencia al ensamblado creado en el procedimiento anterior.

  7. Agregue una referencia a los ensamblados Microsoft.SharePoint y Microsoft.SharePoint.Security.

  8. Abra el archivo de código y agregue las instrucciones using (Imports en Visual Basic) para los espacios de nombres Microsoft.SharePoint y Microsoft.SharePoint.Administration.

  9. Para que coincida con el espacio de nombres que se usó para el trabajo del temporizador personalizado en el procedimiento anterior, cambie el espacio de nombres o use uno diferente, pero agregue una instrucción using para el espacio de nombres en el que se declara el trabajo del temporizador personalizado.

  10. Agregue el siguiente código al método 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/"));
    

    Esta es sólo una de las maneras de obtener una referencia a una aplicación web. Para obtener más información, vea Obtención de referencias a sitios, aplicaciones web y otros objetos clave.

  11. Agregue el siguiente código al método Main.

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

    Tenga en cuenta lo siguiente sobre este código:

    • El primer parámetro del constructor de MyTimerJob es el nombre interno del trabajo. Por convención, los nombres internos de los trabajos están en minúsculas, tienen un guión y empiezan con la palabra "job". Considere la posibilidad de agregar el nombre de la compañía al principio del nombre interno del trabajo, tal como se hace en este ejemplo.

    • El segundo parámetro especifica la aplicación web a la que se debe aplicar el trabajo.

    • El tercer parámetro se puede usar para especificar un servidor determinado en el que se debe ejecutar el trabajo. El valor es null cuando el trabajo debe ejecutarse en todos los servidores front-end web.

    • El cuarto parámetro determina si el trabajo se ejecuta en todos los servidores front-end web. Si se pasa SPJobLockType.None se garantiza que se ejecutará en todos los servidores en los que se ejecuta el servicio de la aplicación web de Microsoft SharePoint Foundation. En cambio, si se pasa SPJobLockType.Job se garantiza que se ejecutará únicamente en el primer servidor disponible en el que se ejecuta el servicio de la aplicación web de Microsoft SharePoint Foundation. (Existe un tercer valor posible. Para obtener más información, vea SPJobDefinition y los temas sobre sus constructores y otros miembros).

  12. Agregue el siguiente código al método Main.

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

    SPOneTimeSchedule es una de las varias clases que se derivan de la clase SPSchedule y que se pueden usar como valor de la propiedad Schedule. Se puede establecer el trabajo para que se ejecute inmediatamente al pasar Now al constructor. Pero la adición de un poco de tiempo, en este caso 30 segundos, puede ser instructiva en la fase de desarrollo ya que da tiempo para ver que el trabajo aparezca en las definiciones de trabajo y listas de trabajos programados en la aplicación de Administración central. Una vez completado, aparece en el historial de trabajos. Un trabajo periódico permanece indefinidamente en la lista de definiciones de trabajo, pero un trabajo único, es decir, un trabajo con un objeto SPOneTimeSchedule como valor de su propiedad Schedule, existe (y, por lo tanto, aparece en la lista de definiciones de trabajo) desde el momento en que se crea hasta el momento en que se ejecuta, que en este ejemplo son 30 segundos. Se eliminará automáticamente tras su ejecución.

  13. Agregue el siguiente código al método Main.

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

Compilación de código

  1. Compile el proyecto en Visual Studio.

  2. Ejecute el archivo ejecutable en cualquier servidor de la granja de servidores en el contexto de usuario de un administrador de la granja.

    El trabajo aparece en las definiciones de trabajo y listas de trabajos programados en la aplicación de Administración central hasta que se programa para ejecutarse. En el ejemplo de ejecución son 30 segundos. A la hora programada se ejecuta en todos los servidores. A continuación, aparece en el historial de trabajos en la aplicación de Administración central y desaparece de la lista de trabajos programados. Dado que es un trabajo único, también desaparece de la lista de definiciones de trabajo una vez que se completa correctamente.

  3. Compruebe que el trabajo se haya ejecutado en todos los servidores. Para ello, en el ejemplo de ejecución, compruebe que el elemento controlAdapter con sus dos atributos se haya agregado al archivo compat.browser que se encuentra en la carpeta C:\Inetpub\wwwroot\wss\VirtualDirectories\número_puerto\App_Browsers, donde número_puerto es el número de puerto de la aplicación web, en cada servidor.