Написание кода в пользовательском соединителе

Пользовательский код преобразует полезные данные запросов и ответов, выходящие за рамки существующих шаблонов политик. Когда используется код, он имеет приоритет над определением без кода.

Для получения дополнительной информации перейдите в Создание пользовательского соединителя с нуля.

Класс скрипта

В вашем коде должен быть реализован метод ExecuteAsync, который вызывается во время выполнения. При необходимости вы можете создавать другие методы в этом классе и вызывать их из метода ExecuteAsync. Имя класса должно быть Сценарий и он должен реализовать ScriptBase.

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

Определение вспомогательных классов и интерфейсов

На следующие классы и интерфейсы ссылается класс "Сценарий". Их можно использовать для локального тестирования и компиляции.

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

Примеры

Сценарий Hello World

Этот пример сценария всегда возвращает Hello World в качестве ответа на все запросы.

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

Сценарий регулярных выражений

В следующем примере берется некоторый текст для сопоставления и регулярное выражение и возвращается результат сопоставления в ответе.

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

Сценарий пересылки

В следующем примере входящий запрос перенаправляется на серверную часть.

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

Сценарий пересылки и преобразования

Следующий пример пересылает входящий запрос и преобразует ответ, возвращаемый серверной частью.

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

Поддерживаемые пространства имен

Не все пространства имен C# поддерживаются. В настоящее время вы можете использовать функции только из следующих пространств имен.

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

Примеры в соединителе DocuSign вы найдете в соединителях Power Platform в GitHub.

Вопросы и ответы по пользовательскому коду

Чтобы узнать больше о пользовательском коде, см. Шаг 4. (Необязательно) Используйте поддержку пользовательского кода.

Вопрос. Можно ли использовать несколько сценариев для одного настраиваемого соединителя?
Ответ. Нет, поддерживается только один файл сценария на настраиваемый соединитель.

Вопрос. При обновлении настраиваемого соединителя возникает внутренняя ошибка сервера. В чем может быть проблема?
Ответ. Скорее всего, это проблема компиляции вашего кода. В будущем мы отобразим полный список ошибок компиляции, чтобы улучшить этот опыт. Мы рекомендуем использовать вспомогательные классы, чтобы локально протестировать ошибки компиляции, в качестве временного решения.

Вопрос. Могу ли я добавить ведение журнала в свой код и получить трассировку для отладки?
Ответ. В настоящее время нет, но поддержка этого будет добавлена в будущем.

Вопрос. Как я могу сейчас протестировать свой код?
Вопрос. Протестируйте его локально и убедитесь, что вы можете скомпилировать код, используя только пространства имен, указанные в поддерживаемых пространствах имен. Для получения информации о локальном тестировании см. в Написание кода в пользовательском соединителе.

Вопрос. Есть ли ограничения?
Ответ. Да. Ваш сценарий должен завершиться в течение 5 секунд, а размер файла сценария не может превышать 1 МБ.

Вопрос. Могу ли я создать собственный HTTP-клиент в коде сценария?
Ответ. В настоящее время да, но мы заблокируем это в будущем. Рекомендуемый способ — использовать метод this.Context.SendAsync.

Вопрос. Можно ли использовать пользовательский код с локальным шлюзом данных?
Ответ. Нет, в настоящее время нельзя.

Поддержка виртуальной сети

Когда соединитель используется в среде Power Platform, связанной с виртуальной сетью, применяются ограничения:

  • Context.SendAsync использует общедоступную конечную точку, поэтому он не может получить доступ к данным из частных конечных точек, представленных в виртуальной сети.

Общие известные проблемы и ограничения

В некоторых регионах заголовок OperationId может возвращаться в кодировке Base64. Если значение OperationId требуется для какой-либо реализации, его необходимо декодировать из Base64, чтобы использовать следующим образом.

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
}

Следующий шаг

Создание пользовательского соединителя с нуля

Предоставление отзывов

Для нас очень важны отзывы о проблемах с нашей платформой соединителей и новые идеи о функциях. Чтобы оставить отзыв, выберите пункт Сообщить о проблемах или получить помощь с соединителями и выберите тип отзыва.