使用 Azure 逻辑应用的标准工作流添加并运行 C# 脚本(预览版)

适用于:Azure 逻辑应用(标准)

注意

此功能为预览版,受 Azure 预览版补充使用条款约束。

若要使用 Azure 逻辑应用中的标准工作流执行自定义集成任务,可以直接从你的工作流中添加并运行 C# 脚本。 对于此任务,请使用名为“执行 CSharp 脚本代码”的“内联代码”操作。 此操作返回脚本的结果,以便你可以在工作流的后续操作中使用此输出。

此功能提供以下优势:

  • 在工作流设计器中编写你自己的脚本,因此无需使用 Azure Functions 即可解决更复杂的集成难题。 不需要其他服务计划。

    这一优势简化了工作流开发,并降低了管理更多服务的复杂性和成本。

  • 生成一个专用代码文件,该文件在你的工作流中提供个性化的脚本编写空间。

  • 与工作流一起部署脚本。

本指南介绍如何在工作流中添加操作并添加要运行的 C# 脚本代码。

先决条件

  • Azure 帐户和订阅。 如果没有订阅,可以注册 Azure 帐户

  • 要在其中添加 C# 脚本的标准逻辑应用工作流。 工作流须以触发器开头。 有关详细信息,请参阅创建示例标准逻辑应用工作流

    你可以根据自己的情况使用任何触发器,作为示例,本指南使用名为“收到 HTTP 请求时”的请求触发器以及“响应”操作。 当另一个应用程序或工作流向触发器的终结点 URL 发送请求时,工作流就会运行。 示例脚本将代码执行的结果作为输出返回,你可以在后续操作中使用该输出。

示例方案

以下列表描述了一些可以使用脚本帮助完成某些集成任务的示例方案:

  • 分析并对有效负载执行超出内置表达式和数据操作能力的转换或操作。 例如,可以使用脚本返回修改的架构以供下游处理。

  • 管理虚拟机等 Azure 资源,并根据某种业务逻辑启动或逐步运行它们。

  • 在需要按计划运行的 SQL 服务器上运行存储过程,并将结果存储在 SharePoint 上。

  • 通过保存到 Azure 存储或通过电子邮件或通知团队来记录工作流错误的详细信息。

  • 加密和解密数据以符合 API 安全标准。

  • 将文件传入脚本,以便对 HTTP 请求进行压缩或解压缩。

  • 聚合来自各种 API 和文件的数据以创建每日报告

注意事项

  • Azure 门户将脚本作为 C# 脚本文件 (.csx) 保存在与 workflow.json 文件相同的文件夹中,该文件存储了工作流的 JSON 定义,并将该文件与工作流定义一起部署到逻辑应用资源中。 Azure 逻辑应用编译此文件,使脚本可供执行。

    使用 .csx 文件格式可以减少“样板”编写工作量,你只需专注于编写 C# 函数。 可以重命名 .csx 文件,以便在部署期间简化管理。 但是,每次重命名脚本时,新版本都会覆盖以前的版本。

  • 脚本在工作流的本地。 若要在其他工作流中使用同一脚本,请在 KuduPlus 控制台中查看脚本文件,然后复制该脚本以便在其他工作流中重用

限制

名称 限制 备注
脚本运行持续时间 10 分钟 如果你的方案需要更长的持续时间,请使用产品反馈选项提供有关需求的详细信息。
输出大小 100 MB 输出大小取决于操作的输出大小限制,通常为 100 MB。

添加“执行 CSharp 脚本代码”操作

  1. Azure 门户的设计器中,打开标准逻辑应用资源和工作流。

  2. 在设计器中,按照以下常规步骤将名为“执行 CSharp 脚本代码”的“内联代码操作”操作添加到工作流

  3. 操作信息窗格打开后,在“参数”选项卡上的“代码文件”框中,使用自己的脚本代码更新预先填充的示例代码

    以下示例演示操作的“参数”选项卡以及示例脚本代码

    屏幕截图显示了 Azure 门户、标准工作流设计器、请求触发器、打开的信息窗格中的“执行 CSharp 脚本代码”操作和“响应”操作。信息窗格显示了示例 C# 脚本。

    以下示例演示示例脚本代码:

    /// Add the required libraries.
    #r "Newtonsoft.Json"
    #r "Microsoft.Azure.Workflows.Scripting"
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    using Microsoft.Extensions.Logging;
    using Microsoft.Azure.Workflows.Scripting;
    using Newtonsoft.Json.Linq;
    
    /// <summary>
    /// Executes the inline C# code.
    /// </summary>
    /// <param name="context">The workflow context.</param>
    /// <remarks> The entry-point to your code. The function signature should remain unchanged.</remarks>
    public static async Task<Results> Run(WorkflowContext context, ILogger log)
    {
        var triggerOutputs = (await context.GetTriggerResults().ConfigureAwait(false)).Outputs;
    
        /// Dereferences the 'name' property from the trigger payload.
        var name = triggerOutputs?["body"]?["name"]?.ToString();
    
        /// To get the outputs from a preceding action, you can uncomment and repurpose the following code.
        // var actionOutputs = (await context.GetActionResults("<action-name>").ConfigureAwait(false)).Outputs;
    
        /// The following logs appear in the Application Insights traces table.
        // log.LogInformation("Outputting results.");
        // var name = null;
    
        return new Results
        {
            Message = !string.IsNullOrEmpty(name) ? $"Hello {name} from CSharp action" : "Hello from CSharp action."
        };
    }
    
    public class Results
    {
        public string Message {get; set;}
    }
    

    有关详细信息,请参阅“#r”- 引用外部程序集

  4. 完成后,保存工作流。

运行工作流后,可以在 Application Insights(如果已启用)中查看工作流输出。 有关详细信息,请参阅在 Application Insights 中查看日志

导入命名空间

若要导入命名空间,请照常使用 using 子句进行操作。 以下列表包含自动导入的命名空间,因此可以选择将其包含在脚本中:

System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading.Tasks
Microsoft.Azure.WebJobs
Microsoft.Azure.WebJobs.Host

添加对外部程序集的引用

若要引用 .NET Framework 程序集,请使用 #r "<assembly-name> 指令,例如:

/// Add the required libraries.
#r "Newtonsoft.Json"
#r "Microsoft.Azure.Workflows.Scripting"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Workflows.Scripting;
using Newtonsoft.Json.Linq;

public static async Task<Results> Run(WorkflowContext context)
{
    <...>
}

public class Results
{
    <...>
}

以下列表包括 Azure Functions 托管环境自动添加的程序集:

mscorlib
System
System.Core
System.Xml
System.Net.Http
Microsoft.Azure.WebJobs
Microsoft.Azure.WebJobs.Host
Microsoft.Azure.WebJobs.Extensions
System.Web.Http
System.Net.Http.Formatting
Newtonsoft.Json

将输出记录到流

Run 方法中,包含一个类型为 ILogger 且名称为 log 的参数,例如:

public static void Run(WorkflowContext context, ILogger log)
{
    log.LogInformation($"C# script successfully executed.");
}

将输出记录到 Application Insights

若要在 Application Insights 中创建自定义指标,请对 ILogger 使用 LogMetric 扩展方法。

以下示例演示示例方法调用:

logger.LogMetric("TestMetric", 1234);

访问脚本中的工作流触发器和操作输出

若要访问工作流中的数据,请使用适用于 WorkflowContext 上下文对象的以下方法:

  • GetTriggerResults 方法

    若要访问触发器输出,请使用此方法返回代表触发器及其输出的对象,这些输出可通过 Outputs 属性获取。 该对象具有 JObject 类型,可以使用方括号 ([]) 作为索引器来访问触发器输出中的各种属性。

    以下示例从触发器输出中的 body 属性获取数据:

    public static async Task<Results> Run(WorkflowContext context, ILogger log)
    {
    
        var triggerOutputs = (await context.GetTriggerResults().ConfigureAwait(false)).Outputs;
        var body = triggerOutputs["body"];
    
        return new Results;
    
    }
    
    public class Results
    {
        <...>
    }
    
  • GetActionResults 方法

    若要访问操作输出,请使用此方法返回表示操作及其输出的对象,这些对象可通过 Outputs 属性获取。 该方法接受操作名称作为参数。 以下示例从名为 action-name 的操作的输出中的 body 属性获取数据:

    public static async Task<Results> Run(WorkflowContext context, ILogger log)
    {
    
        var actionOutputs = (await context.GetActionResults("action-name").ConfigureAwait(false)).Outputs;
        var body = actionOutputs["body"];
    
        return new Results;
    
    }
    
    public class Results
    {
        <...>
    }
    

访问环境变量或应用设置值

若要获取环境变量或某个应用程序设置值,请使用 System.Environment.GetEnvironmentVariable 方法,例如:

public static void Run(WorkflowContext context, ILogger log)
{
    log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
    log.LogInformation(GetEnvironmentVariable("AzureWebJobsStorage"));
    log.LogInformation(GetEnvironmentVariable("WEBSITE_SITE_NAME"));
}

public static string GetEnvironmentVariable(string name)
{
    return name + ": " +
    System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}

将数据返回到工作流

对于此任务,请实现带有返回类型和 return 语句的 Run 方法。 如果你需要异步版本,请实现带有 Task<return-type> 属性和 async 关键字的 Run 方法。 返回值设置为脚本操作的输出 body 属性,然后任何后续工作流操作都可以引用该属性。

以下示例显示了带有 Task<Results> 属性、async 关键字和 return 语句的 Run 方法:

public static async Task<Results> Run(WorkflowContext context, ILogger log)
{
    return new Results
    {
        Message = !string.IsNullOrEmpty(name) ? $"Returning results with status message."
    };
}

public class Results
{
    public string Message {get; set;}
}

查看脚本文件

  1. Azure 门户中,打开具有所需工作流的标准逻辑应用资源。

  2. 在逻辑应用资源菜单中的“开发工具”下,选择“高级工具”

  3. 在“高级工具”页面上,选择“转到”,这会打开 KuduPlus 控制台

  4. 打开“调试控制台”菜单,然后选择“CMD”

  5. 转到逻辑应用的根位置:site/wwwroot

  6. 按照以下路径转到包含 .csx 文件的工作流文件夹:site/wwwroot/{workflow-name}

  7. 在文件名旁边,选择“编辑”打开并查看该文件

在 Application Insights 中查看日志

  1. Azure 门户中,在逻辑应用资源菜单的“设置”下,选择“Application Insights”,然后选择你的逻辑应用。

  2. 在“Application Insights”菜单上,在“监视”下,选择“日志”。

  3. 创建查询以从工作流执行中查找任何跟踪或错误,例如:

    union traces, errors
    | project TIMESTAMP, message
    

编译错误

在此版本中,基于 Web 的编辑器包含有限的 IntelliSense 支持,目前仍在改进中。 保存工作流时会检测任何编译错误,并且 Azure 逻辑应用运行时会编译脚本。 这些错误显示在逻辑应用的错误日志中。

运行时错误

如果脚本执行时发生错误,Azure 逻辑应用将执行以下步骤:

  • 将错误传回到工作流。
  • 将脚本操作标记为“失败”
  • 提供一个代表从脚本引发的异常的错误对象。

以下示例演示示例错误:

函数“CSharp_MyLogicApp-InvalidAction_execute_csharp_script_code.csx”失败,错误为“工作流中不存在操作‘nonexistent’。” (执行时)。 请验证函数代码是否有效。

示例脚本

以下示例脚本执行各种任务

将包含来自 HTTP 操作的文本文件的 ZIP 文件解压缩为字符串数组

// Add the required libraries.
#r "Newtonsoft.Json"
#r "Microsoft.Azure.Workflows.Scripting"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Azure.Workflows.Scripting;
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Collections.Generic;

/// <summary>
/// Executes the inline C# code.
/// </summary>
/// <param name="context">The workflow context.</param>
public static async Task<List<string>> Run(WorkflowContext context)
{

    var outputs = (await context.GetActionResults("HTTP_1").ConfigureAwait(false)).Outputs;
    var base64zipFileContent = outputs["body"]["$content"].ToString();

    // Decode base64 to bytes.
    byte[] zipBytes = Convert.FromBase64String(base64zipFileContent);

    List<string> fileContents = new List<string>();

    // Creates an in-memory stream from the zip bytes.
    using (MemoryStream zipStream = new MemoryStream(zipBytes))
    {

        // Extracts files from the zip archive.
        using (ZipArchive zipArchive = new ZipArchive(zipStream))
        {

            foreach (ZipArchiveEntry entry in zipArchive.Entries)
            {

                // Read each file's content.
                using (StreamReader reader = new StreamReader(entry.Open()))
                {
                    string fileContent = reader.ReadToEnd();
                    fileContents.Add(fileContent);
                }
            }
        }
    }

    return fileContents;
}

使用应用设置中的密钥加密数据

// Add the required libraries.
#r "Newtonsoft.Json"
#r "Microsoft.Azure.Workflows.Scripting"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Azure.Workflows.Scripting;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// Executes the inline csharp code.
/// </summary>
/// <param name="context">The workflow context.</param>
public static async Task<string> Run(WorkflowContext context)
{

    var compose = (await context.GetActionResults("compose").ConfigureAwait(false)).Outputs;
    var text = compose["sampleData"].ToString();

    return EncryptString(text);

}

public static string EncryptString(string plainText)
{

    var key = Environment.GetEnvironmentVariable("app-setting-key");
    var iv = Environment.GetEnvironmentVariable("app-setting-iv");

    using (Aes aesAlg = Aes.Create())
    {

        aesAlg.Key = Encoding.UTF8.GetBytes(key);
        aesAlg.IV = Encoding.UTF8.GetBytes(iv);
        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        using (MemoryStream msEncrypt = new MemoryStream())
        {

            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            {

                using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                {
                    swEncrypt.Write(plainText);
                }

            }

             return Convert.ToBase64String(msEncrypt.ToArray());

        }
    }
}

WorkflowContext 类

表示工作流上下文。

方法

GetActionResult(string actionName)

获取工作流中特定操作的结果。

异步版本使用 Task<> 作为返回类型,例如:

Task<WorkflowOperationResult> GetActionResult(string actionName)

参数

actionName:操作名称。

返回

异步版本返回代表异步操作的 Task 对象。 任务结果包含 WorkflowOperationResult 对象。 有关 WorkflowOperationResult 对象属性的信息,请参阅 WorkflowOperationResult 类

RunTriggerResult()

从工作流中的触发器获取结果。

异步版本使用 Task<> 作为返回类型,例如:

Task<WorkflowOperationResult> RunTriggerResult()

参数

无。

返回

异步版本返回代表异步操作的 Task 对象。 任务结果包含 WorkflowOperationResult 对象。 有关 WorkflowOperationResult 对象属性的信息,请参阅 WorkflowOperationResult 类

WorkflowOperationResult 类

表示工作流操作的结果。

属性

名称 Type 说明
名称 字符串 获取或设置操作名称。
输入 JToken 获取或设置操作执行输入。
输出 JToken 获取或设置操作执行输出。
StartTime DateTime? 获取或设置操作开始时间。
EndTime DateTime? 获取或设置操作结束时间。
OperationTrackingId 字符串 获取或设置操作跟踪 ID。
代码 字符串 获取或设置操作的状态代码。
Status 字符串 获取或设置操作的状态。
错误 JToken 获取或设置操作的错误。
TrackedProperties JToken 获取或设置操作的跟踪属性。

添加和运行 JavaScript 代码片段