Escrever código num conector personalizado

O código personalizado transforma os payloads de pedido e resposta para além do âmbito dos modelos de política existentes. Quando o código é utilizado, tem precedência sobre a definição sem código.

Para obter mais informações, aceda a Criar um conector personalizado a partir do zero.

Classe de guião

O seu código precisa de implementar um método chamado ExecuteAsync, que é chamado durante o runtime. Pode criar outros métodos nesta classe conforme necessário e chamá-los a partir do método ExecuteAsync. O nome da classe tem de ser Script e tem de implementar ScriptBase.

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

Definição de classes e interfaces de suporte

As classes e interfaces seguintes são referenciadas pela classe Script. Podem ser utilizadas para testes e compilação locais.

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

Exemplos

Script do Hello World

Este script de exemplo devolve sempre Hello World como resposta a todos os pedidos.

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

O exemplo seguinte utiliza algum texto para correspondência e a expressão regex e devolve o resultado da correspondência na resposta.

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 reencaminhamento

O exemplo seguinte remete o pedido de entrada para o back-end.

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 reencaminhamento e transformação

O exemplo seguinte reencaminha o pedido de entrada e transforma a resposta devolvida a partir do back-end.

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

Espaços de nomes suportados

Nem todos os espaços de nomes de C# são suportados. Atualmente, pode utilizar funções apenas a partir dos espaços de nomes seguintes.

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;

Exemplos do GitHub

Por exemplo, no conector DocuSign, aceda ao Conectores do Power Platform no GitHub.

FAQ de código personalizado

Para saber mais sobre código personalizado, aceda ao Passo 4: (Opcional) Utilizar suporte de código personalizado.

P: É possível utilizar múltiplos scripts por conector personalizado?
R: Não, apenas um ficheiro de script por conector personalizado é suportado.

P: Estou a receber um erro interno do servidor ao atualizar o meu conector personalizado. Qual pode ser o problema?
R: O mais provável é que este seja um problema de compilação do seu código. No futuro, iremos mostrar a lista completa de erros de compilação para melhorar esta experiência. Recomendamos a utilização das classes de suporte para, por agora, testar os erros de compilação localmente, como solução.

P: Posso adicionar um registo ao meu código e obter um rastreio para depuração?
R: Atualmente não, mas o suporte para este efeito será adicionado no futuro.

P: Como posso testar o meu código entretanto?
R: Teste-o localmente e certifique-se de que pode compilar o código usando apenas os espaços de nomes fornecidos em espaços de nomes suportados. Para obter informações sobre testes locais, aceda ao Escrever código num conector personalizado.

P: Existem limites?
R: Sim. O seu script tem de concluir a execução dentro de 5 segundos e o tamanho do seu ficheiro de script não pode ser superior a 1 MB.

P: Posso criar o meu próprio cliente HTTP no código do script?
A: Atualmente sim, mas vamos bloquear esta abordagem no futuro. A forma recomendada é utilizar o método this.Context.SendAsync.

P.: Posso utilizar código personalizado com o gateway de dados no local?
R.: Atualmente, não.

Suporte de Rede Virtual

Quando o conector é utilizado num ambiente do Power Platform ligado a uma Rede Virtual, aplicam-se limitações:

  • Context.SendAsync usa um ponto final público, portanto, não pode aceder aos dados de pontos finais privados expostos na Rede Virtual.

Problemas e gerais conhecidos e limitações

O cabeçalho OperationId pode ser devolvido no formato codificado base64 em determinadas regiões. Se o valor do OperationId for obrigatório para uma implementação, deve ser descodificado de acordo com base64 para utilização de forma semelhante à seguinte.

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
}

Próximo passo

Criar um conector personalizado do zero

Enviar comentários

Apreciamos os comentários sobre problemas com a nossa plataforma de conectores ou novas ideias de funcionalidades. Para enviar comentários, aceda a Submeter problemas ou obter ajuda com conectores e selecione o tipo de comentários.