在自定义连接器中编写代码

自定义代码转换超出现有策略模板范围的请求和响应有效负载。 使用代码时,它优先于无代码定义。

有关详细信息,请转到从头开始创建自定义连接器

脚本类

您的代码需要实现一个名为 ExecuteAsync 的方法,该方法在运行时调用。 您可以根据需要在此类中创建其他方法,然后从 ExecuteAsync 方法调用它们。 类名称必须是 Script,并且必须实现 ScriptBase

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

支持类和接口的定义

以下类和接口由 Script 类引用。 它们可用于本地测试和编译。

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 连接器中的示例,请转到 GitHub 中的 Power Platform 连接器

自定义代码常见问题

要了解有关自定义代码的详细信息,请转到步骤 4:(可选)使用自定义代码支持

问:每个自定义连接器是否可以使用多个脚本?
答: 不可以,每个自定义连接器仅支持一个脚本文件。

问:更新自定义连接器时出现内部服务器错误。可能是什么问题?
答: 最有可能是编译代码的问题。 将来,我们会显示编译错误的完整列表以改进此体验。 我们建议暂时使用支持类在本地测试编译错误,以此来解决这一问题。

问:我能否在我的代码中添加日志记录并获取对调试的跟踪?
答: 目前还不行,但将来会增加这一支持。

问:在此期间如何测试我的代码?
答: 在本地进行测试,并确保您能够在只使用支持的命名空间中提供的命名空间的情况下编译代码。 有关本地测试的信息,请转到在自定义连接器中编写代码

问:是否有限制?
答: 可以。 您的脚本必须在 5 秒内完成执行,并且脚本文件的大小不能超过 1 MB。

问:我能否在脚本代码中创建自己的 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
}

下一步

从头开始创建自定义连接器

提供反馈

我们非常感谢大家提出有关连接器平台问题或新功能想法的反馈。 要提供反馈,请转到提交问题或获取连接器帮助,然后选择反馈类型。