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 Konnektors ohne Vorlage.

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 lauten Skript und es muss ScriptBase umsetzen.

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 im DocuSign-Connector finden Sie unter Power Platform-Connectors auf GitHub.

Häufig gestellte Fragen zu benutzerdefiniertem Code

Um mehr über benutzerdefinierten Code zu erfahren, gehen Sie zu Schritt 4 (optional): Verwenden Sie den Support für benutzerdefinierten Code.

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

F: Beim Aktualisieren meines benutzerdefinierten Konnektors erhalte ich einen internen Serverfehler. Was könnte das Problem sein?
A: Höchstwahrscheinlich ist dies ein Problem beim Kompilieren Ihres Codes. In Zukunft werden wir die vollständige Liste der Kompilierungsfehler anzeigen, um diese Umgebung zu verbessern. Wir empfehlen die Verwendung der unterstützenden Klassen, um Kompilierungsfehler zunächst lokal als Problemumgehung zu testen.

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

F: Wie kann ich meinen Code bis dahin testen?
A: Testen Sie ihn lokal und stellen Sie sicher, dass Sie Code nur mit den in unterstützte Namescapes bereitgestellten Namespaces kompilieren können. Informationen zu lokalen Testen finden Sie unter Code in einen benutzerdefinierten Konnektor schreiben.

F: Gibt es irgendwelche Grenzwerte?
A: Ja. Die Ausführung Ihres Skripts muss innerhalb von 5 Sekunden abgeschlossen sein und die Größe Ihrer Skriptdatei darf 1 MB nicht überschreiten.

F: Kann ich meinen eigenen Http-Client im Skriptcode erstellen?
A: Derzeit ja, aber wir werden dies in Zukunft blockieren. Die empfohlene Methode ist, die this.Context.SendAsync-Methode zu verwenden.

F: Kann ich benutzerdefinierte Codes mit dem lokalen Datengateway verwenden?
A: Nein, wird derzeit nicht verwendet.

Unterstützung von Virtual Network

Wenn der Connector in einer Power Platform-Umgebung verwendet wird, die mit einem Virtual Network 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

Erstellen Sie einen benutzerdefinierten Konnektor von Grund auf neu

Feedback senden

Wir freuen uns sehr über Feedback zu Problemen mit unserer Connector-Plattform oder neuen Feature-Ideen. Wenn Sie Feedback geben möchten, gehen Sie zu Probleme melden oder Hilfe zu Connectors und wählen Sie einen Feedbacktyp aus.