Uso de código personalizado en un conector personalizado

El código personalizado transforma las cargas útiles de solicitudes y respuestas más allá del alcance de las plantillas de políticas existentes. Cuando se usa código, tiene prioridad sobre la definición sin código.

Para obtener más información, consulte Crear un conector personalizado desde cero.

Clase de script

El código debe implementar un método denominado ExecuteAsync, que se ejecuta durante el tiempo de ejecución. Puede crear otros métodos en esta clase según sea necesario y llamarlos desde el ExecuteAsync método . El nombre de clase debe ser Script y debe implementar ScriptBase.

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

Definición de clases e interfaces de apoyo

La Script clase hace referencia a las siguientes clases e interfaces. Úselos para pruebas y compilación locales.

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

Ejemplos

Guión de Hola mundo

Este script de ejemplo siempre devuelve Hola mundo como respuesta para todas las solicitudes.

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 regex

El siguiente ejemplo toma un texto en el que buscar coincidencias y la expresión regular, y devuelve en la respuesta el resultado de la coincidencia.

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 reenvío

El siguiente ejemplo reenvía la solicitud entrante al 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 reenvío y transformación

El siguiente ejemplo reenvía la solicitud entrante y transforma la respuesta devuelta desde el 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;
}

Espacios de nombres admitidos

No se admiten todos los espacios de nombres de C#. Actualmente, solo puede usar funciones de los siguientes espacios de nombres.

Importante

La versión de .NET admitida actualmente es .NET Standard 2.0.

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;

Muestras de GitHub

Para ver ejemplos del conector de DocuSign, vaya a Conectores de Power Platform en GitHub.

Preguntas frecuentes sobre código personalizado

Para obtener más información sobre el código personalizado, consulte Paso 4: (Opcional) Usar compatibilidad con código personalizado.

P: ¿Puedo usar varios scripts por conector personalizado?R: No, solo puede usar un archivo de script por conector personalizado.

P: Recibo un error interno del servidor al actualizar mi conector personalizado. ¿Cual podría ser el problema? R: Lo más probable es que haya un problema al compilar el código. En el futuro, el sistema muestra la lista completa de errores de compilación para mejorar esta experiencia. Por ahora, use las clases auxiliares para probar los errores de compilación localmente como solución alternativa.

P: ¿Puedo agregar el registro a mi código y obtener un seguimiento para la depuración?R: Actualmente no, pero la compatibilidad con esta característica se agregará en el futuro.

P: ¿Cómo puedo probar mi código mientras tanto?R: Pruóbelo localmente y asegúrese de que puede compilar código mediante solo los espacios de nombres proporcionados en los espacios de nombres admitidos. Para obtener información sobre las pruebas locales, consulte Escritura de código en un conector personalizado.

P: ¿Existen límites?R: Sí. Su script debe finalizar la ejecución en 2 minutos y el tamaño de su archivo de secuencia de comandos no puede ser superior a 1 MB. Este nuevo tiempo de espera de 2 minutos se aplica a los conectores personalizados recién creados. Para los conectores personalizados existentes, debe actualizar el conector para aplicar el nuevo tiempo de espera.

P: ¿Puedo crear mi propio cliente HTTP en el código de script?R: Actualmente, sí, pero el sistema bloquea esta característica en el futuro. La forma recomendada es utilizar el método this.Context.SendAsync.

P: ¿Puedo usar código personalizado con la puerta de enlace de datos local?R: Actualmente no, no.

Soporte de Virtual Network

Al usar el conector en un entorno de Power Platform vinculado a una red virtual, se aplican las siguientes limitaciones:

  • Context.SendAsync usa un punto de conexión público, por lo que no puede acceder a datos desde puntos de conexión privados en la red virtual.

Problemas y limitaciones generales conocidos

En algunas regiones, la cabecera OperationId se devuelve en formato codificado en Base64. Si su implementación requiere el valor OperationId, descodifíquelo de base64 para usarlo.

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
}

Siguiente paso

Creación de un conector personalizado desde cero

Proporcionar comentarios

Agradecemos enormemente los comentarios sobre problemas con nuestra plataforma de conectores o nuevas ideas de funciones. Para enviar comentarios, vaya a Enviar problemas u obtener ayuda con los conectores y seleccione el tipo de comentario.