Compartilhar via


Criar um receptor de eventos de suplemento em suplementos do SharePoint

É útil se você primeiro entender os suplementos do SharePoint hospedados pelo provedor e tiver desenvolvido alguns que vão pelo menos um pouco além do nível "Olá, Mundo". Consulte Começar a criar suplementos do SharePoint hospedados pelo provedor.

Além disso, você deve estar familiarizado com a Manipulação de eventos nos suplementos do SharePoint.

Obter mais exemplos de código

Se você trabalhar com o exemplo contínuo neste artigo, terá um exemplo de código concluído. A seguir estão alguns outros exemplos. Nem todos seguem a arquitetura descrita neste artigo. Pode haver mais de uma boa maneira de arquitetar um receptor de eventos de suplemento e ter em mente também que as diretrizes da Microsoft podem evoluir ao longo do tempo.

Adicionar um receptor de eventos instalado de suplemento

  1. No Visual Studio, abra o projeto para o Suplemento do SharePoint hospedado pelo provedor. (Se você adicionar um manipulador de eventos de suplemento a um suplemento hospedado pelo SharePoint, as Ferramentas de Desenvolvedor do Office para Visual Studio o converterão em um aplicativo hospedado pelo provedor.)

  2. No Explorador de Soluções, selecione o nó para o suplemento do SharePoint.

  3. Na janela Propriedades, defina o valor de Manipular Suplemento Instalado como Verdadeiro.

    Eventos de aplicativo na janela de propriedades

    As Ferramentas para Desenvolvedores do Office para o Visual Studio fazem o seguinte:

    • Adicione um arquivo chamado AppEventReceiver.svc que contenha algum código esqueletal C# (ou VB.NET). Este é o serviço que manipula o evento de suplemento.

    • Adicione a seguinte entrada à seção Propriedades do arquivo AppManifest.xml: <InstalledEventEndpoint>~remoteAppUrl/AppEventReceiver.svc</InstalledEventEndpoint>. Essa entrada registra o receptor de evento de suplemento no SharePoint.

      Observação

      O token ~remoteAppUrl é o mesmo usado para o aplicativo da Web remoto no suplemento do SharePoint hospedado pelo provedor. As Ferramentas para Desenvolvedores do Office para Visual Studio pressupõem que o domínio do aplicativo da web e o manipulador de eventos são o mesmo. No caso raro em que não o são, você precisa substituir manualmente o token ~remoteAppUrl pelo domínio real do seu serviço.

    • Crie um projeto da Web se o projeto do suplemento do SharePoint ainda não tiver um. As ferramentas também garantem que um manifesto de suplemento seja configurado para um suplemento hospedado pelo provedor. Elas também adicionam páginas, scripts, arquivos CSS e outros artefatos. Se o único componente remoto que seu suplemento precisa é o serviço da Web de manipulação de eventos, você poderá excluí-los do projeto. Você também deve garantir que o elemento StartPage no manifesto do suplemento não esteja apontando para uma página que você tenha excluído.

  4. Se o seu farm de teste do SharePoint não estiver no mesmo computador que está executando o Visual Studio, configure o projeto para depuração usando o Barramento de Serviço do Microsoft Azure. Para obter mais informações, confira Depurar e solucionar problemas de um receptor de eventos remoto em um suplemento do SharePoint.

  5. Se houver um método ProcessOneWayEvent no arquivo AppEventReceiver.svc, sua implementação deve consistir em apenas a linha throw new NotImplementedException(); porque esse método não pode ser usado em um manipulador de eventos de suplemento.

    Os manipuladores de eventos de suplemento devem retornar um objeto que informe ao SharePoint se deve concluir ou reverter o evento, e o método ProcessOneWayEvent não retorna nada.

  6. O arquivo inclui um método ProcessEvent parecido com o seguinte. (Também pode haver um bloco de código que ilustra como obter um contexto de cliente. Exclua-o ou comente-o.)

    public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
    {
        SPRemoteEventResult result = new SPRemoteEventResult();
    
        return result;
    }
    

    Observe o seguinte sobre este código:

    • O objeto SPRemoteEventProperties é enviado para o seu serviço da Web de manipulador como uma mensagem SOAP que contém informações de contexto do SharePoint, incluindo uma propriedade EventType que identifica o evento.

    • O objeto SPRemoteEventResult que seu manipulador retorna contém uma propriedade Status cujos valores possíveis são SPRemoteEventServiceStatus.Continue, SPRemoteEventServiceStatus.CancelNoError e SPRemoteEventServiceStatus.CancelWithError. O valor padrão da propriedade Status é Continuar, o que informa ao SharePoint para finalizar o evento. Os outros dois valores dizem ao SharePoint para:

      • Execute o manipulador até três vezes mais.
      • Se ele ainda estiver obtendo um status de cancelamento, cancele o evento e reverta tudo que ele tiver feito como parte do evento.
  7. Imediatamente após a linha que declara a variável result, adicione a seguinte estrutura de alternância para identificar qual evento está sendo manipulado.

    switch (properties.EventType)
    {
        case SPRemoteEventType.AppInstalled:
            break;
        case SPRemoteEventType.AppUpgraded:
            break;
        case SPRemoteEventType.AppUninstalling:
            break;
    }
    

    Observação

    Se você tiver manipuladores para os eventos AppInstalled, AppUpdated e AppInstalling, cada um deles terá sua própria URL registrada no manifesto do suplemento. Portanto, você pode ter pontos de extremidade diferentes para eles, mas este artigo (e as Ferramentas de Desenvolvedor do Office para Visual Studio) assumem que eles têm exatamente o mesmo ponto de extremidade; é por isso que o código precisa determinar qual evento o chamou.

  8. Conforme explicado em Incluir lógica de reversão e lógica de "já concluído" em seus manipuladores de eventos de suplemento, se algo der errado na sua lógica de instalação, você quase sempre deseja que a instalação do suplemento seja cancelada e que o SharePoint retroceda o que foi feito para a instalação, e deseja reverter o que manipulador fez.

    Uma maneira de atingir esses objetivos é adicionar o seguinte código dentro do caso do evento AppInstalled.

    case SPRemoteEventType.AppInstalled:
    try
    {
        // Add-in installed event logic goes here.
    }
    catch (Exception e)
    {
        result.ErrorMessage = e.ErrorMessage;
        result.Status = SPRemoteEventServiceStatus.CancelWithError;
    
        // Rollback logic goes here.
    }
    break;
    

    Observação

    Mova o código de instalação que leva mais de 30 segundos para o próprio suplemento. Você pode adicioná-lo à lógica de "primeira execução" executada pela primeira vez em que o suplemento é executado. O suplemento pode exibir uma mensagem dizendo algo como "Estamos preparando as coisas para você". Como alternativa, o suplemento pode solicitar que o usuário execute o código de inicialização.

    Se a lógica de "primeira execução" não for viável para o seu suplemento, outra opção é fazer com que o manipulador de eventos inicie um processo assíncrono remoto e, em seguida, retorne imediatamente um objeto SPRemoteEventResult com o Status definido como Continuar. Um ponto fraco dessa estratégia é que, se o processo remoto falhar, não será possível dizer ao SharePoint para reverter a instalação do suplemento.

  9. Conforme explicado nas estratégias de arquitetura de manipulador de eventos de suplemento, a estratégia de delegação do manipulador é preferida, embora não seja possível em todos os cenários. No exemplo a continuação, mostramos como implementar a estratégia de delegação do manipulador ao adicionar uma lista ao host da Web. Para obter informações sobre como criar um manipulador de eventos AppInstalled semelhante que não usa a estratégia de delegação do manipulador, consulte o exemplo SharePoint/PnP/Samples/Core.AppEvents.

    A seguir está a nova versão do bloco de casos AppInstalled. Observe que a lógica de inicialização que se aplica a todos os eventos fica acima do bloco de alternância. Como a mesma lista instalada é removida do manipulador AppUninstalling, a lista é identificada lá.

    SPRemoteEventResult result = new SPRemoteEventResult();
    String listTitle = "MyList";
    
    switch (properties.EventType)
    {               
        case SPRemoteEventType.AppInstalled:
    
    try
    {
            string error = TryCreateList(listTitle, properties);
            if (error != String.Empty)
            {
                throw new Exception(error);            
            }
    }
        catch (Exception e)
    {
            // Tell SharePoint to cancel the event.
            result.ErrorMessage = e.Message;
            result.Status = SPRemoteEventServiceStatus.CancelWithError;               
        }
            break;
        case SPRemoteEventType.AppUpgraded:
        break;
        case SPRemoteEventType.AppUninstalling:
        break;
    }                      
    
  10. Adicione o método de criação de lista à classe AppEventReceiver como um método privado com o seguinte código. Observe que a classe TokenHelper tem um método especial que é otimizado para obter um contexto de cliente para um evento de suplemento. Passar false para o último parâmetro garante que o contexto seja para o host da Web.

    private string TryCreateList(String listTitle, SPRemoteEventProperties properties)
    {    
        string errorMessage = String.Empty;          
    
        using (ClientContext clientContext =
            TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
        {
            if (clientContext != null)
            {
            }
        }
        return errorMessage;
    }
    
    
  11. A lógica de reversão é basicamente lógica de tratamento de exceções e o CSOM do SharePoint (modelo de objeto do lado do cliente) possui um ExceptionHandlingScope que permite que o serviço da Web delegue tratamento de exceções ao servidor do SharePoint (confira Como usar o escopo de tratamento de exceções).

    Adicione o seguinte código ao bloco se no trecho de código anterior.

    ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext); 
    
    using (scope.StartScope()) 
    { 
        using (scope.StartTry()) 
        { 
        }         
        using (scope.StartCatch()) 
        {                                 
        } 
        using (scope.StartFinally()) 
        { 
        } 
    } 
    clientContext.ExecuteQuery();
    
    if (scope.HasException)
    {
        errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
            scope.ServerErrorTypeName, scope.ErrorMessage, 
            scope.ServerErrorDetails, scope.ServerErrorValue, 
            scope.ServerStackTrace, scope.ServerErrorCode);
    }
    
  12. Há apenas uma chamada para o SharePoint (ExecuteQuery) no trecho de código anterior, mas infelizmente não podemos exatamente ter apenas uma. Todo objeto que será referenciado em nosso escopo de exceção deve primeiro ser carregado no cliente.

    Adicione o seguinte código acima do construtor para o ExceptionHandlingScope.

    ListCollection allLists = clientContext.Web.Lists;
    IEnumerable<List> matchingLists =
        clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
    clientContext.ExecuteQuery();
    
    var foundList = matchingLists.FirstOrDefault();
    List createdList = null;
    
  13. O código para criar uma lista de host da Web vai para o bloco StartTry, mas o código deve primeiro verificar se a lista já foi adicionada (conforme explicado em Incluir lógica de reversão e lógica de "já concluído" em seus manipuladores de eventos de suplemento). A lógica se-então-senão pode ser delegada para o servidor do SharePoint usando a classe ConditionalScope (confira Como usar o escopo condicional).

    Adicione o seguinte código dentro do bloco StartTry.

    ConditionalScope condScope = new ConditionalScope(clientContext, 
            () => foundList.ServerObjectIsNull.Value == true, true);
    using (condScope.StartScope())
    {
        ListCreationInformation listInfo = new ListCreationInformation();
        listInfo.Title = listTitle;
        listInfo.TemplateType = (int)ListTemplateType.GenericList;
        listInfo.Url = listTitle;
        createdList = clientContext.Web.Lists.Add(listInfo);                                
    }
    
  14. O bloco StartCatch deve desfazer a criação da lista, mas ele precisa primeiro verificar se a lista foi criada, porque uma exceção pode ter sido lançada no bloco StartTry antes de terminar de criar a lista.

    Adicione o seguinte código ao bloco StartCatch.

    ConditionalScope condScope = new ConditionalScope(clientContext, 
            () => createdList.ServerObjectIsNull.Value != true, true);
    using (condScope.StartScope())
    {
        createdList.DeleteObject();
    } 
    

    Dica

    RESOLUÇÃO DE PROBLEMAS: Para testar se o seu bloco StartCatch está inserido quando deveria, você precisa de uma maneira de lançar uma exceção de tempo de execução no servidor do SharePoint. Usar um lançamento ou dividir por zero não funcionará porque eles causam exceções do lado do cliente antes que o tempo de execução do cliente possa agrupar o código e enviá-lo para o servidor (com o método ExecuteQuery).

    Em vez disso, adicione as seguintes linhas ao bloco StartTry. O tempo de execução do lado do cliente aceita isso, mas causa uma exceção do lado do servidor, que é o que você deseja.

    List fakeList = clientContext.Web.Lists.GetByTitle("NoSuchList");

    clientContext.Load(fakeList);

Todo o método TryCreateList deve se parecer com o seguinte. (O bloco StartFinally é necessário mesmo quando não está sendo usado.)

    private string TryCreateList(String listTitle, SPRemoteEventProperties properties)
    {    
        string errorMessage = String.Empty;  

        using (ClientContext clientContext = 
            TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
        {
            if (clientContext != null)
            {
                ListCollection allLists = clientContext.Web.Lists;
                IEnumerable<List> matchingLists = 
                    clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
                clientContext.ExecuteQuery();
                var foundList = matchingLists.FirstOrDefault();
                List createdList = null;

                ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext); 
                using (scope.StartScope()) 
                { 
                    using (scope.StartTry()) 
                    { 
                        ConditionalScope condScope = new ConditionalScope(clientContext, 
                                () => foundList.ServerObjectIsNull.Value == true, true);  
                        using (condScope.StartScope())
                        {
                            ListCreationInformation listInfo = new ListCreationInformation();
                            listInfo.Title = listTitle;
                            listInfo.TemplateType = (int)ListTemplateType.GenericList;
                            listInfo.Url = listTitle;
                            createdList = clientContext.Web.Lists.Add(listInfo);
                        }
                    } 
                    
                    using (scope.StartCatch()) 
                    { 
                        ConditionalScope condScope = new ConditionalScope(clientContext, 
                                () => createdList.ServerObjectIsNull.Value != true, true);
                        using (condScope.StartScope())
                        {
                            createdList.DeleteObject();
                        }    
                    } 

                    using (scope.StartFinally()) 
                    { 
                    } 
                } 
                clientContext.ExecuteQuery();

                if (scope.HasException)
                {
                        errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
                        scope.ServerErrorTypeName, scope.ErrorMessage, 
                        scope.ServerErrorDetails, scope.ServerErrorValue, 
                        scope.ServerStackTrace, scope.ServerErrorCode);
                }
            }
        }
        return errorMessage;
    }

Dica

DEPURAÇÃO: Independentemente de você estar usando a estratégia de delegação do manipulador, quando estiver percorrendo o código com o depurador, lembre-se que, em qualquer cenário em que seu manipulador retorna um status de cancelamento, o SharePoint chamará seu manipulador novamente, até mais três vezes. Portanto, o depurador percorre o código até quatro vezes.

Dica

ARQUITETURA DO CÓDIGO: Como você pode instalar componentes na Web do suplemento com marcação declarativa fora do seu manipulador, geralmente não vai querer usar nenhum dos 30 segundos que o manipulador tem disponíveis para interagir com a Web do suplemento. Mas se você fizer isso, lembre-se de que seu código requer um objeto ClientContext separado para a Web do suplemento. Isso significa que a Web do suplemento e o host da Web são componentes diferentes, da mesma forma que um banco de dados do SQL Server é diferente de cada um deles. Portanto, um método que chama a Web do suplemento está no bloco try do bloco de casos AppInstalled, assim como o método TryCreateList no exemplo a continuação. No entanto, o manipulador não precisa reverter as ações realizadas na Web de suplemento. Se encontrar um erro, ele só precisará cancelar o evento, porque o SharePoint excluirá toda a web do suplemento se o evento for cancelado.

Criar um receptor de eventos de desinstalação de suplemento

  1. Defina a propriedade Manipular Desinstalação de Suplemento do projeto como True. As ferramentas não criam outro arquivo de serviço da Web se um já existir, mas adicionam um elemento UninstallingEventEndpoint ao manifesto do suplemento.

  2. O código no bloco de casos AppUninstalling deve remover os artefatos do suplemento que não são necessários depois que o suplemento é removido da lixeira de segundo estágio, que é o que aciona o evento. No entanto, sempre que possível, você precisa "aposentar" os componentes, em vez de excluí-los totalmente. Isso porque você precisa restaurá-los se o evento de desinstalação precisar ser revertido. Se isso acontecer, o suplemento ainda estará na lixeira de segundo estágio, e um usuário poderá restaurá-lo e começar a usá-lo novamente. A simples recriação de um componente excluído em sua lógica de reversão pode ser suficiente para permitir que o suplemento funcione novamente, mas quaisquer dados ou definições de configuração no componente serão perdidos.

    Essa estratégia é relativamente fácil para os componentes do SharePoint, porque o SharePoint tem uma lixeira da qual as coisas podem ser restauradas e há APIs do CSOM para acessá-la. Etapas posteriores deste procedimento mostram como. Para outras plataformas, diferentes técnicas podem ser necessárias. Por exemplo, se você deseja retirar uma linha em uma tabela do SQL Server em seu manipulador de desinstalação de suplemento, um procedimento armazenado T-SQL no manipulador pode adicionar uma coluna IsDeleted à tabela e defini-la como True para a linha. Se o procedimento encontrar um erro, a lógica de reversão redefinirá o valor para False. Se o procedimento for concluído sem erros, pouco antes de retornar um sinalizador de sucesso, ele poderá definir um trabalho de temporizador para excluir a linha posteriormente.

    Às vezes, você deseja manter dados, como listas, mesmo depois que o suplemento é excluído; no entanto, como um exemplo para este artigo, o seguinte é um manipulador de eventos de desinstalação que exclui a lista que foi criada com o manipulador de eventos instalado.

    case SPRemoteEventType.AppUninstalling:
    
    try
    {
        string error = TryRecycleList(listTitle, properties);
        if (error != String.Empty)
        {
            throw new Exception(error);
        }
    }
    catch (Exception e)
    {
        // Tell SharePoint to cancel the event.
        result.ErrorMessage = e.Message;
        result.Status = SPRemoteEventServiceStatus.CancelWithError;
    }
    break;
    
  3. Adicione o método auxiliar para reciclar a lista. Observe o seguinte sobre este código:

    • O código recicla a lista, em vez de excluí-la permanentemente. Isso torna possível restaurá-lo, incluindo seus dados, se o evento falhar, que é o que o bloco StartCatch faz. Portanto, se o método for bem-sucedido e o evento for concluído, o suplemento será permanentemente excluído da lixeira de segundo estágio, mas a lista ainda estará na lixeira de primeiro estágio.

    • O código testa a existência da lista antes de reciclá-la porque um usuário pode já tê-la reciclado na interface do usuário do SharePoint. Similiarly, o código de reversão verifica a existência da lista na lixeira antes de restaurá-la, porque um usuário pode já tê-la restaurado ou movido para a lixeira do segundo estágio.

    • Existem dois escopos condicionais que testam a existência de uma lista verificando se uma referência a ela é nula. Mas ambos têm um bloco se interno que testa o mesmo objeto para nulidade uma segunda vez. Os testes externos, com blocos de escopo condicionais, são executados no servidor, mas os testes internos de nulidade também são necessários. Isso ocorre porque o tempo de execução do cliente se move pelo código linha por linha para criar a mensagem XML que o método ExecuteQuery envia ao servidor. Quando as referências aos objetos foundList e recycledList forem atingidas, uma ou outra dessas linhas emitirá uma exceção de Referência Nula, a menos que elas sejam colocadas dentro das verificações de anulação internas.

      private string TryRecycleList(String listTitle, SPRemoteEventProperties properties)
      {
          string errorMessage = String.Empty;
      
          using (ClientContext clientContext = 
              TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
          {
              if (clientContext != null)
              {
                  ListCollection allLists = clientContext.Web.Lists;
                  IEnumerable<List> matchingLists = 
                      clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
                  RecycleBinItemCollection bin = clientContext.Web.RecycleBin;
                  IEnumerable<RecycleBinItem> matchingRecycleBinItems = 
                      clientContext.LoadQuery(bin.Where(item => item.Title == listTitle));        
                  clientContext.ExecuteQuery();
      
                  List foundList = matchingLists.FirstOrDefault();
                  RecycleBinItem recycledList = matchingRecycleBinItems.FirstOrDefault();    
      
                  ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext);
                  using (scope.StartScope())
                  {
                      using (scope.StartTry())
                      {
                          ConditionalScope condScope = new ConditionalScope(clientContext, 
                              () => foundList.ServerObjectIsNull.Value == false, true);
                          using (condScope.StartScope())
                          {
                              if (foundList != null)
                              {
                                  foundList.Recycle();
                              }
                          }
                      }
                      using (scope.StartCatch())
                      {
                          ConditionalScope condScope = new ConditionalScope(clientContext, 
                              () => recycledList.ServerObjectIsNull.Value == false, true);
                          using (condScope.StartScope())
                          {
                              if (recycledList != null)
                              {
                                  recycledList.Restore(); 
                              }
                          }
                      }
                      using (scope.StartFinally())
                      {
                      }
                  }
                  clientContext.ExecuteQuery();
      
                  if (scope.HasException)
                  {
                      errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
                          scope.ServerErrorTypeName, scope.ErrorMessage, 
                          scope.ServerErrorDetails, scope.ServerErrorValue, 
                          scope.ServerStackTrace, scope.ServerErrorCode);
                  }
              }
          }
          return errorMessage;
      }
      

Depurar e testar um receptor de eventos de desinstalação de suplemento

  1. Abra as seguintes páginas em janelas ou abas separadas:

    • Conteúdo do Site
    • Configurações do Site - Lixeira (_layouts/15/AdminRecycleBin.aspx? Ql = 1)
    • Lixeira - Lixeira de segundo estágio (_layouts/15/AdminRecycleBin.aspxView = 2&?ql = 1)
  2. Selecione F5 e confie no suplemento quando solicitado. A página inicial dos suplementos é aberta. Se você for testar apenas o manipulador de desinstalação, poderá fechar essa janela do navegador. Mas se você estiver depurando o manipulador, deixe-o aberto. Fechá-lo encerrará a sessão de depuração.

  3. Atualize a página Conteúdo do site e, quando o suplemento aparecer, remova-o.

  4. Atualize a página Configurações do site - Lixeira. O suplemento aparece como o item principal. Marque a caixa de seleção ao lado deste e clique em Excluir Seleção.

  5. Atualize a página Lixeira - Lixeira de Segundo Estágio. O suplemento aparece como o item principal. Marque a caixa de seleção ao lado deste e clique em Excluir Seleção. O SharePoint chama imediatamente seu manipulador de desinstalação de suplemento.

Criar um receptor de eventos atualizado de suplemento

Para obter detalhes sobre como criar um manipulador atualizado de suplemento, confira Criar um manipulador para o evento de atualização nos suplementos do SharePoint.

Restrições de URL e hospedagem em receptores de eventos de suplemento de produção

O receptor de eventos remotos pode ser hospedado na nuvem ou em um servidor local que também não esteja sendo usado como um servidor do SharePoint. A URL de um receptor de produção não pode especificar uma porta. Isso significa que você deve usar a porta 443 para HTTPS, que recomendamos, ou a porta 80 para HTTP. Se você estiver usando HTTPS e o serviço do receptor estiver hospedado no local, mas o suplemento estiver no SharePoint Online, o servidor de hospedagem deverá ter um certificado publicamente confiável de uma autoridade de certificação. (Um certificado autoassinado funciona somente se o suplemento estiver em um farm do SharePoint no local.)

Confira também