Écrire du code dans un connecteur personnalisé

Le code personnalisé transforme les charges utiles des demandes et des réponses au-delà de la portée des modèles de stratégie existants. Lorsque le code est utilisé, il prévaut sur la définition sans code.

Pour plus d’informations, consultez Créer un connecteur personnalisé de A à Z.

Classe de script

Votre code doit implémenter une méthode appelée ExecuteAsync, qui est appelée lors de l’exécution. Vous pouvez créer d’autres méthodes dans cette classe selon vos besoins et les appeler à partir de la méthode ExecuteAsync. Le nom de la classe doit être Script et il doit mettre en œuvre ScriptBase.

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

Définition des classes et interfaces de support

Les classes et interfaces suivantes sont référencées par la classe Script. Ils peuvent être utilisés pour les tests et la compilation locaux.

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);
}

Exemples

Script Hello World

Cet exemple de script renvoie toujours Hello World comme réponse pour toutes les demandes.

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;
}

Script d’expression régulière

L’exemple suivant prend du texte à faire correspondre et l’expression regex et renvoie le résultat de la correspondance dans la réponse.

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;
}

Script de transfert

L’exemple suivant transfère la demande entrante au backend.

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;
}

Script de transfert et de transformation

L’exemple suivant transfère la demande entrante et transforme la réponse renvoyée par le backend.

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;
}

Espaces de noms pris en charge

Tous les espaces de noms C# ne sont pas pris en charge. Actuellement, vous pouvez utiliser les fonctions des espaces de noms suivants uniquement.

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;

Exemples GitHub

Pour consulter des exemples dans le connecteur DocuSign, accédez à Connecteurs Power Platform dans GitHub.

FAQ sur le code personnalisé

Pour en savoir plus sur le code personnalisé, rendez-vous sur Étape 4 : (Facultatif) Utiliser la prise en charge du code personnalisé.

Q : Est-il possible d’utiliser plusieurs scripts par connecteur personnalisé ?
R : Non, un seul fichier de script par connecteur personnalisé est pris en charge.

Q : J’obtiens une erreur de serveur interne lors de la mise à jour de mon connecteur personnalisé. Quel pourrait être le problème ?
R : Il s’agit certainement d’un problème lors de la compilation de votre code. À l’avenir, nous afficherons la liste complète des erreurs de compilation pour améliorer cette expérience. Nous vous recommandons d’utiliser les classes de support pour tester les erreurs de compilation localement pour l’instant comme solution de contournement.

Q : Puis-je ajouter la journalisation à mon code et obtenir une trace pour le débogage ?
R : Pas actuellement, mais cette prise en charge sera ajoutée à l’avenir.

Q : Comment puis-je tester mon code en attendant ?
R : Testez-le localement et assurez-vous que vous pouvez compiler du code en utilisant uniquement les espaces de noms fournis dans espaces de noms pris en charge. Pour plus d’informations sur les tests locaux, rendez-vous sur Écrire du code dans un connecteur personnalisé.

Q : Existe-t-il des limitations ?
R : Oui. Votre script doit terminer son exécution dans les 5 secondes et la taille de votre fichier de script ne peut pas dépasser 1 Mo.

Q : Puis-je créer mon propre client http en code de script ?
R : Actuellement oui, mais nous bloquerons cela à l’avenir. La méthode recommandée est d’utiliser la méthode this.Context.SendAsync.

Q : Puis-je utiliser le code personnalisé avec la passerelle de données locale ?
R : Pas actuellement, non.

Prise en charge du réseau virtuel

Lorsque le connecteur est utilisé dans un environnement Power Platform lié à un réseau virtuel, des limitations s’appliquent :

  • Context.SendAsync utilise un point de terminaison public, il ne peut donc pas accéder aux données des points de terminaison privés exposés sur le réseau virtuel.

Problèmes généraux connus et limitations

L'en-tête OperationId peut être renvoyé au format encodé base64 dans certaines régions. Si la valeur de l'OperationId est requise pour une mise en œuvre, elle doit être décodée en base64 pour être utilisée de manière similaire à ce qui suit.

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
}

Étape suivante

Créer un connecteur personnalisé à partir de zéro

Fournir des commentaires

Nous apprécions grandement les commentaires sur les problèmes liés à notre plateforme de connecteurs ou les idées de nouvelles fonctionnalités. Pour fournir des commentaires, accédez à Soumettre des problèmes ou obtenir de l’aide avec les connecteurs et sélectionnez votre type de commentaire.