Freigeben über


Code in einen benutzerdefinierten Connector schreiben

Benutzerdefinierter Code transformiert Anforderungs- und Antwortnutzlasten über den Umfang vorhandener Richtlinienvorlagen hinaus. Wenn Code verwendet wird, hat er Vorrang vor der codelosen Definition.

Weitere Informationen finden Sie unter Erstellen eines benutzerdefinierten Connectors von Grund auf.

Skriptklasse

Ihr Code muss eine Methode namens ExecuteAsync implementieren, die während der Runtime aufgerufen wird. Sie können nach Bedarf andere Methoden in dieser Klasse erstellen und diese über die ExecuteAsync-Methode aufrufen. Der Klassenname muss Script sein und ScriptBase implementieren.

public class Script : ScriptBase
{
    public override Task<HttpResponseMessage> ExecuteAsync()
    {
        // Your code here
    }
}

Definition unterstützender Klassen und Schnittstellen

Auf die folgenden Klassen und Schnittstellen verweist die Scriptklasse. Sie können zum lokalen Testen und Kompilieren verwendet werden.

public abstract class ScriptBase
{
    // Context object
    public IScriptContext Context { get; }

    // CancellationToken for the execution
    public CancellationToken CancellationToken { get; }

    // Helper: Creates a StringContent object from the serialized JSON
    public static StringContent CreateJsonContent(string serializedJson);

    // Abstract method for your code
    public abstract Task<HttpResponseMessage> ExecuteAsync();
}

public interface IScriptContext
{
    // Correlation Id
    string CorrelationId { get; }

    // Connector Operation Id
    string OperationId { get; }

    // Incoming request
    HttpRequestMessage Request { get; }

    // Logger instance
    ILogger Logger { get; }

    // Used to send an HTTP request
    // Use this method to send requests instead of HttpClient.SendAsync
    Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken);
}

Beispiele

„Hallo Welt“-Skript

Dieses Beispielskript gibt immer „Hallo Welt“ als Antwort für alle Anforderungen zurück.

public override async Task<HttpResponseMessage> ExecuteAsync()
{
    // Create a new response
    var response = new HttpResponseMessage();

    // Set the content
    // Initialize a new JObject and call .ToString() to get the serialized JSON
    response.Content = CreateJsonContent(new JObject
    {
        ["greeting"] = "Hello World!",
    }.ToString());

    return response;
}

Regex-Skript

Das folgende Beispiel nimmt zu vergleichenden Text und den Regex-Ausdruck und gibt das Ergebnis der Übereinstimmung in der Antwort zurück.

public override async Task<HttpResponseMessage> ExecuteAsync()
{
    // Check if the operation ID matches what is specified in the OpenAPI definition of the connector
    if (this.Context.OperationId == "RegexIsMatch")
    {
        return await this.HandleRegexIsMatchOperation().ConfigureAwait(false);
    }

    // Handle an invalid operation ID
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
    response.Content = CreateJsonContent($"Unknown operation ID '{this.Context.OperationId}'");
    return response;
}

private async Task<HttpResponseMessage> HandleRegexIsMatchOperation()
{
    HttpResponseMessage response;

    // We assume the body of the incoming request looks like this:
    // {
    //   "textToCheck": "<some text>",
    //   "regex": "<some regex pattern>"
    // }
    var contentAsString = await this.Context.Request.Content.ReadAsStringAsync().ConfigureAwait(false);

    // Parse as JSON object
    var contentAsJson = JObject.Parse(contentAsString);

    // Get the value of text to check
    var textToCheck = (string)contentAsJson["textToCheck"];

    // Create a regex based on the request content
    var regexInput = (string)contentAsJson["regex"];
    var rx = new Regex(regexInput);

    JObject output = new JObject
    {
        ["textToCheck"] = textToCheck,
        ["isMatch"] = rx.IsMatch(textToCheck),
    };

    response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = CreateJsonContent(output.ToString());
    return response;
}

Weiterleitungsskript

Das folgende Beispiel leitet die eingehende Anforderung an das Back-End weiter.

public override async Task<HttpResponseMessage> ExecuteAsync()
{
    // Check if the operation ID matches what is specified in the OpenAPI definition of the connector
    if (this.Context.OperationId == "ForwardAsPostRequest")
    {
        return await this.HandleForwardOperation().ConfigureAwait(false);
    }

    // Handle an invalid operation ID
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
    response.Content = CreateJsonContent($"Unknown operation ID '{this.Context.OperationId}'");
    return response;
}

private async Task<HttpResponseMessage> HandleForwardOperation()
{
    // Example case: If your OpenAPI definition defines the operation as 'GET', but the backend API expects a 'POST',
    // use this script to change the HTTP method.
    this.Context.Request.Method = HttpMethod.Post;

    // Use the context to forward/send an HTTP request
    HttpResponseMessage response = await this.Context.SendAsync(this.Context.Request, this.CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
    return response;
}

Weiterleitungs- und Transformierenskript

Das folgende Beispiel leitet die eingehende Anforderung weiter und wandelt die vom Back-End zurückgegebene Antwort um.

public override async Task<HttpResponseMessage> ExecuteAsync()
{
    // Check if the operation ID matches what is specified in the OpenAPI definition of the connector
    if (this.Context.OperationId == "ForwardAndTransformRequest")
    {
        return await this.HandleForwardAndTransformOperation().ConfigureAwait(false);
    }

    // Handle an invalid operation ID
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
    response.Content = CreateJsonContent($"Unknown operation ID '{this.Context.OperationId}'");
    return response;
}

private async Task<HttpResponseMessage> HandleForwardAndTransformOperation()
{
    // Use the context to forward/send an HTTP request
    HttpResponseMessage response = await this.Context.SendAsync(this.Context.Request, this.CancellationToken).ConfigureAwait(continueOnCapturedContext: false);

    // Do the transformation if the response was successful, otherwise return error responses as-is
    if (response.IsSuccessStatusCode)
    {
        var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
        
        // Example case: response string is some JSON object
        var result = JObject.Parse(responseString);
        
        // Wrap the original JSON object into a new JSON object with just one key ('wrapped')
        var newResult = new JObject
        {
            ["wrapped"] = result,
        };
        
        response.Content = CreateJsonContent(newResult.ToString());
    }

    return response;
}

Unterstützte Namespaces

Nicht alle C#-Namespaces werden unterstützt. Derzeit können Sie nur Funktionen aus den folgenden Namespaces verwenden.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

GitHub-Beispiele

Beispiele für den DocuSign Connector finden Sie unter Power Platform Connectors in GitHub.

Häufig gestellte Fragen zu benutzerdefiniertem Code

Weitere Informationen zu benutzerdefiniertem Code finden Sie unter Schritt 4: (Optional) Unterstützung für benutzerdefinierten Code verwenden.

F: Ist es möglich, mehrere Skripte pro benutzerdefiniertem Connector zu verwenden?
A: Nein, es wird nur eine Skriptdatei pro benutzerdefiniertem Connector unterstützt.

F: Beim Aktualisieren meines benutzerdefinierten Connectors wird ein interner Serverfehler angezeigt. Was könnte das Problem sein?
A: Höchstwahrscheinlich liegt ein Problem beim Kompilieren Ihres Codes vor. In Zukunft werden wir die vollständige Liste der Kompilierungsfehler anzeigen, um diese Umgebung zu verbessern. Als Workaround empfehlen wir, die unterstützenden Klassen zu verwenden, um Kompilierungsfehler vorerst lokal zu testen.

F: Kann ich meinem Code eine Protokollierung hinzufügen und eine Ablaufverfolgung zum Debuggen erhalten?
A: Derzeit nicht, aber die Unterstützung hierfür wird in Zukunft hinzugefügt.

F: Wie kann ich meinen Code in der Zwischenzeit testen?
A: Testen Sie es lokal und stellen Sie sicher, dass Sie Code nur mit den in den unterstützten Namespaces bereitgestellten Namespaces kompilieren können. Informationen zum lokalen Testen finden Sie unter Code in einem benutzerdefinierten Connector schreiben.

F: Gibt es irgendwelche Grenzen?
A: Ja. Ihr Skript muss innerhalb von 2 Minuten vollständig ausgeführt werden und die Größe Ihrer Skriptdatei darf 1 MB nicht überschreiten. Dieses neue 2-Minuten-Timeout gilt für alle neu erstellten benutzerdefinierten Konnektoren. Bei vorhandenen benutzerdefinierten Konnektoren müssen Kunden den Konnektor aktualisieren, um das neue Zeitlimit anzuwenden.

F: Kann ich meinen eigenen HTTP-Client im Skriptcode erstellen?
A: Derzeit ja, aber wir werden dies in Zukunft blockieren. Es wird empfohlen, die Methode this.Context.SendAsync zu verwenden.

F: Kann ich benutzerdefinierten Code mit dem lokalen Datengateway verwenden?
A: Derzeit nicht, nein.

Unterstützung von Virtual Network

Wenn der Connector in einer Power Platform Umgebung verwendet wird, die mit einem virtuellen Netzwerk verknüpft ist, gelten Einschränkungen:

  • Context.SendAsync verwendet einen öffentlichen Endpunkt und kann daher nicht auf Daten von privaten Endpunkten zugreifen, die in Virtual Network verfügbar sind.

Allgemeine bekannte Probleme und Einschränkungen

Der OperationId-Header wird in bestimmten Regionen möglicherweise in Base64-kodiertem Format zurückgegeben. Wenn der Wert von OperationId für eine Implementierung erforderlich ist, sollte dieser für die Verwendung in folgender Weise oder ähnlich Base64-dekodiert werden.

public override async Task<HttpResponseMessage> ExecuteAsync()
{
    string realOperationId = this.Context.OperationId;
    // Resolve potential issue with base64 encoding of the OperationId
    // Test and decode if it's base64 encoded
    try {
        byte[] data = Convert.FromBase64String(this.Context.OperationId);
        realOperationId = System.Text.Encoding.UTF8.GetString(data);
    }
    catch (FormatException ex) {}
    // Check if the operation ID matches what is specified in the OpenAPI definition of the connector
    if (realOperationId == "RegexIsMatch")
    // Refer to the original examples above for remaining details
}

Nächster Schritt

Einen benutzerdefinierten Connector von Grund auf neu erstellen

Feedback senden

Wir freuen uns sehr über Feedback zu Problemen mit unserer Connector-Plattform oder neuen Feature-Ideen. Um Feedback abzugeben, gehen Sie zu Probleme übermitteln oder Hilfe zu Konnektoren erhalten und wählen Sie Ihren Feedbacktyp aus.