Programmgesteuertes Erstellen eines Service Hooks-Abonnements

Azure DevOps Services | Azure DevOps Server 2020 | Azure DevOps Server 2019 | TFS 2018 - TFS 2017

Mithilfe der Abonnement-REST-APIs können Sie programmgesteuert ein Abonnement erstellen, das eine Aktion für einen externen Dienst (Consumer) ausführt, wenn ein bestimmtes Ereignis in einem Projekt auftritt. Beispielsweise können Sie ein Abonnement erstellen, um Ihren Dienst zu benachrichtigen, wenn ein Build fehlschlägt.

Unterstützte Ereignisse:

  • Build abgeschlossen
  • Per Push übertragener Code (für Git-Projekte)
  • Erstellen oder Aktualisieren von Pull Requests (für Git-Projekte)
  • Eingecheckter Code (TFVC-Projekte)
  • Arbeitselement erstellt, aktualisiert, gelöscht, wiederhergestellt oder kommentiert

Sie können Filter für Ihre Abonnements konfigurieren, um zu steuern, welche Ereignisse eine Aktion auslösen. Beispielsweise können Sie das Build completed-Ereignis basierend auf dem Buildstatus filtern. Einen vollständigen Satz unterstützter Ereignisse und Filteroptionen finden Sie in der Ereignisreferenz.

Einen vollständigen Satz unterstützter Consumerdienste und -aktionen finden Sie in der Consumerreferenz.

Erstellen eines Abonnements für ein Projekt

Um ein Abonnement für ein Ereignis zu erstellen, wählen Sie den zu verwendenden Consumer und die Aktion aus, die Sie ergreifen möchten. Erstellen Sie eine HTTP POST-Anforderung an die Abonnement-URL für die Azure DevOps Organisation mit dem Ereignis, dem Consumer und der Aktion, die für das Abonnement ergriffen werden soll.

Vorbereitung

Zum Erstellen eines Abonnements sind die folgenden Daten erforderlich:

Erstellen der Anforderung

Erstellen Sie den Text der HTTP POST-Anforderung, um das Abonnement basierend auf der Projekt-ID, dem Ereignis, dem Consumer und der Aktion zu erstellen.

Im Folgenden finden Sie eine Beispielanforderung zum Erstellen eines Abonnements, das bewirkt, dass ein Buildereignis bei einem Build WebSite.CI misslingthttps://myservice/event.

{
    "publisherId": "tfs",
    "eventType": "build.complete",
    "resourceVersion": "1.0",
    "consumerId": "webHooks",
    "consumerActionId": "httpRequest",
    "publisherInputs": {
        "buildStatus": "failed",
        "definitionName": "WebSite.CI",
        "projectId": "56479caf-1eeb-4bca-86ab-aaa6f29399d9",
    },
    "consumerInputs": {
        "url": " https://myservice/event"
    },
}

Sichere HTTPS-URLs werden dringend für die Sicherheit der privaten Daten im JSON-Objekt empfohlen.

Hier ist die Antwort auf die Anforderung zum Erstellen des Abonnements:

{
    "id": "74aeeed0-bf5d-48dc-893f-f862b80987e9",
    "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/hooks/subscriptions/74aeeed0-bf5d-48dc-893f-f862b80987e9",
    "publisherId": "tfs",
    "eventType": "build.complete",
    "resourceVersion": "1.0",
    "consumerId": "webHooks",
    "consumerActionId": "httpRequest",
    "createdBy": {
        "id": "00ca946b-2fe9-4f2a-ae2f-40d5c48001bc"
    },
    "createdDate": "2014-03-28T16:10:06.523Z",
    "modifiedBy": {
        "id": "1c4978ae-7cc9-4efa-8649-5547304a8438"
    },
    "modifiedDate": "2014-04-25T18:15:26.053Z",
    "publisherInputs": {
        "buildStatus": "failed",
        "definitionName": "WebSite.CI",
        "hostId": "17f27955-99bb-4861-9550-f2c669d64fc9",
        "projectId": "56479caf-1eeb-4bca-86ab-aaa6f29399d9",
        "tfsSubscriptionId": "29cde8b4-f37e-4ef9-a6d4-d57d526d82cc"
    },
    "consumerInputs": {
        "url": "http://myservice/event"
    }
}

Wenn die Abonnementanforderung fehlschlägt, wird der HTTP-Antwortcode 400 mit einer Nachricht zurückgegeben, die weitere Details enthält.

Was geschieht, wenn das Ereignis eintritt?

Wenn ein Ereignis auftritt, werden alle aktivierten Abonnements im Projekt ausgewertet, und die Consumeraktion wird für alle übereinstimmenden Abonnements ausgeführt.

Ressourcenversionen (erweitert)

Die Ressourcenversionsverwaltung ist anwendbar, wenn sich eine API in der Vorschau befindet. In den meisten Szenarien ist die Angabe 1.0 als Ressourcenversion die sicherste Route.

Die an bestimmte Consumer (z. B. Webhooks, Azure Service Bus, Azure Storage) gesendete Ereignisnutzlast enthält eine JSON-Darstellung der Betreffressource (z. B. ein Build- oder Arbeitselement). Die Darstellung dieser Ressource kann unterschiedliche Formen (oder Versionen) aufweisen. Sie können die Version der Ressource angeben, die Sie über das Feld im Abonnement an den Consumerdienst resourceVersion gesendet haben möchten. Die Ressourcenversion entspricht der API-Version. Wenn Sie keine Ressourcenversion angeben, bedeutet dies "neueste Veröffentlichung". Sie sollten immer eine Ressourcenversion angeben. Dadurch wird eine konsistente Ereignisnutzlast im Zeitverlauf sichergestellt.

F&A

F: Gibt es Dienste, die ich manuell abonnieren kann?

A: Ja. Hier sind die Dienste, die Sie über die Verwaltungsseite für ein Projekt abonnieren können.

F: Gibt es C#-Bibliotheken, die ich zum Erstellen von Abonnements verwenden kann?

A: Nein, aber hier ist ein Beispiel für den Einstieg.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Mvc;

namespace Microsoft.Samples.VisualStudioOnline
{
    public class ServiceHookEventController : Controller
    {

        // POST: /ServiceHookEvent/workitemcreated
        [HttpPost]
        public HttpResponseMessage WorkItemCreated(Content workItemEvent)
        {
            //Grabbing the title for the new workitem
            var value = RetrieveFieldValue("System.field", workItemEvent.Resource.Fields);

            //Acknowledge event receipt
            return new HttpResponseMessage(HttpStatusCode.OK);
        }

        /// <summary>
        /// Gets the value for a specified work item field.
        /// </summary>
        /// <param name="key">Key used to retrieve matching value</param>
        /// <param name="fields">List of fields for a work item</param>
        /// <returns></returns>
        public String RetrieveFieldValue(String key, IList<FieldInfo> fields)
        {
            if (String.IsNullOrEmpty(key))
                return String.Empty;

            var result = fields.Single(s => s.Field.RefName == key);

            if (result == null)
                return String.Empty;

            return result.Value;
        }

	}

    public class Content
    {
        public String SubscriptionId { get; set; }

        public int NotificationId { get; set; }

        public String EventType { get; set; }

        public WorkItemResource Resource { get; set; }

    }

    public class WorkItemResource
    {
        public String UpdatesUrl { get; set; }

        public IList<FieldInfo> Fields { get; set;}

        public int Id { get; set; }

        public int Rev { get; set; }

        public String Url { get; set; }

        public String WebUrl { get; set; }
    }

    public class FieldInfo
    {
        public FieldDetailedInfo Field { get; set; }

        public String Value { get; set; }

    }

    public class FieldDetailedInfo
    {
        public int Id { get; set; }

        public String Name { get; set; }

        public String RefName { get; set; }
    }
}