通过 Azure 逻辑应用的标准工作流创建并运行 .NET 代码

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

对于必须在标准逻辑应用工作流中创作和运行 .NET 代码的集成解决方案,可以使用 Visual Studio Code 和 Azure 逻辑应用(标准版)扩展。 此扩展提供以下功能和优势:

  • 通过创建具有灵活性和控制能力的函数来编写自己的代码,以解决最具挑战性的集成问题。
  • 在 Visual Studio Code 中本地调试代码。 在同一调试会话中逐步执行代码和工作流。
  • 同步部署代码与工作流。 不需要其他服务计划。
  • 支持 BizTalk Server 迁移方案,以便你可以将自定义 .NET 投资从本地平稳迁移到云端。

借助自行编写代码的功能,你可以完成如下方案:

  • 自定义业务逻辑实现
  • 自定义分析功能,可从入站消息中提取信息
  • 数据验证和简单转换
  • 为到另一个系统(例如 API)的出站消息进行消息塑形
  • 计算

此功能不适用于以下方案:

  • 需要超过 10 分钟才能运行的进程
  • 大型消息和数据转换
  • 复杂的批处理和解批处理方案
  • 实现流式处理的 BizTalk Server 管道组件

有关 Azure 逻辑应用中的限制的详细信息,请参阅限制和配置 - Azure 逻辑应用

先决条件

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

  • 具有 Azure 逻辑应用(标准版)扩展的最新版 Visual Studio Code。 要满足这些要求,请参阅使用 Visual Studio Code 在单租户 Azure 逻辑应用中创建标准工作流的先决条件。

    • 自定义函数功能目前仅在 Windows 操作系统上运行的 Visual Studio Code 中可用。

    • 自定义函数功能目前支持为 Azure 托管的逻辑应用工作流调用 .NET Framework 和 .NET 8。

  • 用于创建代码项目的本地文件夹

限制

  • 自定义函数创作目前在 Microsoft Azure 门户中不可用。 但是,将函数从 Visual Studio Code 部署到 Azure 后,请按照 Microsoft Azure 门户的从工作流调用代码中的步骤操作。 可以使用名为“调用此逻辑应用中的本地函数”的内置操作从部署的自定义函数中进行选择并运行代码。 工作流中的后续操作可以引用这些函数的输出,就像任何其他工作流一样。 可以查看内置操作的运行历史记录、输入和输出。

  • 自定义函数使用独立辅助角色来调用逻辑应用工作流中的代码。 为了避免自己的函数代码和辅助角色之间的包引用冲突,应使用辅助角色引用的相同包版本。 有关辅助角色引用的完整包列表和版本,请参阅辅助角色和包依赖项

创建代码项目

适用于 Visual Studio Code 的最新 Azure 逻辑应用(标准版)扩展包含一个代码项目模板,该模板为编写、调试和部署你自己的代码和工作流提供了简化体验。 此项目模板创建了一个工作区文件和两个示例项目:一个项目用于编写代码,另一个项目用于创建工作流。

注意

不能对代码和工作流使用相同的项目文件夹。

  1. 打开 Visual Studio Code。 在活动栏中选择 Azure 图标。 (键盘:Shift+Alt+A)

  2. 在打开的 Azure 窗口中,在“工作区”部分工具栏的“Azure 逻辑应用”菜单中选择“新建逻辑应用工作区”。

    该屏幕截图显示了 Visual Studio Code、Azure 窗口、“工作区”部分工具栏和选中的“新建逻辑应用工作区”选项。

  3. 在“选择文件夹”框中,浏览到并选择为项目创建的本地文件夹。

  4. 当出现“创建新的逻辑应用工作区”提示框时,请提供工作区的名称:

    屏幕截图显示 Visual Studio Code 提示你输入工作区名称。

    此示例继续使用 MyLogicAppWorkspace

  5. 当出现“选择逻辑应用工作区的项目模板”提示框时,请选择“具有自定义代码项目的逻辑应用”。

    屏幕截图显示 Visual Studio Code 提示你选择逻辑应用工作区的项目模板。

  6. 对于 Azure 托管的标准逻辑应用工作流,请按照提示选择“.NET Framework”或“.NET 8”。

  7. 按照后续提示提供以下示例值:

    示例值
    .NET 函数项目的函数名称 WeatherForecast
    .NET 函数项目的命名空间名称 Contoso.Enterprise
    工作流模板:
    - 有状态工作流
    - 无状态工作流
    有状态工作流
    工作流名称 MyWorkflow
  8. 选择“在当前窗口中打开”。

    完成此步骤后,Visual Studio Code 会创建工作区,其中默认包括 .NET 函数项目和逻辑应用项目,例如:

    屏幕截图显示 Visual Studio Code 和已创建的工作区。

    节点 说明
    <workspace-name> 包含你的 .NET 函数项目和逻辑应用工作流项目。
    函数 包含 .NET 函数项目的工件。 例如,<function-name>.cs 文件是可在其中创作代码的代码文件。
    逻辑应用 包含你的逻辑应用项目的工件,包括空白工作流。

编写代码

  1. 在工作区中,展开“Functions”节点(如果尚未展开)。

  2. 打开 <function-name>.cs 文件,此示例中名为 WeatherForecast.cs

    默认情况下,此文件包含具有以下代码元素的示例代码,它具有前面提供的示例值(如果适用):

    • 命名空间名称
    • 类名
    • 函数名称
    • 函数参数
    • 返回类型
    • 复杂类型

    以下示例展示了完整的示例代码:

    //------------------------------------------------------------
    // Copyright (c) Microsoft Corporation. All rights reserved.
    //------------------------------------------------------------
    
    namespace Contoso.Enterprise
    {
        using System;
        using System.Collections.Generic;
        using System.Threading.Tasks;
        using Microsoft.Azure.Functions.Extensions.Workflows;
        using Microsoft.Azure.WebJobs;
        using Microsoft.Extensions.Logging;
    
        /// <summary>
        /// Represents the WeatherForecast flow invoked function.
        /// </summary>
        public class WeatherForecast
        {
    
            private readonly ILogger<WeatherForecast> logger;
    
            public WeatherForecast(ILoggerFactory loggerFactory)
            {
                logger = loggerFactory.CreateLogger<WeatherForecast>();
            }
    
            /// <summary>
            /// Executes the logic app workflow.
            /// </summary>
            /// <param name="zipCode">The zip code.</param>
            /// <param name="temperatureScale">The temperature scale (e.g., Celsius or Fahrenheit).</param>
            [FunctionName("WeatherForecast")]
            public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
            {
    
                this.logger.LogInformation("Starting WeatherForecast with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
    
                // Generate random temperature within a range based on the temperature scale
                Random rnd = new Random();
                var currentTemp = temperatureScale == "Celsius" ? rnd.Next(1, 30) : rnd.Next(40, 90);
                var lowTemp = currentTemp - 10;
                var highTemp = currentTemp + 10;
    
                // Create a Weather object with the temperature information
                var weather = new Weather()
                {
                    ZipCode = zipCode,
                    CurrentWeather = $"The current weather is {currentTemp} {temperatureScale}",
                    DayLow = $"The low for the day is {lowTemp} {temperatureScale}",
                    DayHigh = $"The high for the day is {highTemp} {temperatureScale}"
                };
    
                return Task.FromResult(weather);
            }
    
            /// <summary>
            /// Represents the weather information for WeatherForecast.
            /// </summary>
            public class Weather
            {
                /// <summary>
                /// Gets or sets the zip code.
                /// </summary>
                public int ZipCode { get; set; }
    
                /// <summary>
                /// Gets or sets the current weather.
                /// </summary>
                public string CurrentWeather { get; set; }
    
                /// <summary>
                /// Gets or sets the low temperature for the day.
                /// </summary>
                public string DayLow { get; set; }
    
                /// <summary>
                /// Gets or sets the high temperature for the day.
                /// </summary>
                public string DayHigh { get; set; }
            }
        }
    }
    

    函数定义包括可用于入门的默认 Run 方法。 此示例 Run 方法演示了自定义函数特性提供的一些功能,例如传递不同的输入和输出,包括复杂的 .NET 类型。

    <function-name>.cs 文件还包括 ILogger 接口,该接口支持将事件记录到 Application Insights 资源。 可以将跟踪信息发送到 Application Insights,并将该信息与工作流中的跟踪信息一起存储,例如:

    private readonly ILogger<WeatherForecast> logger;
    
    public WeatherForecast(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger<WeatherForecast>();
    }
    
    [FunctionName("WeatherForecast")]
    public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
    {
    
        this.logger.LogInformation("Starting WeatherForecast with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
    
        <...>
    
    }
    
  3. 将示例函数代码替换为你自己的代码,并为你自己的方案编辑默认 Run 方法。 或者,可以复制该函数(包括 [FunctionName("<*function-name*>")] 声明),然后使用唯一名称重命名该函数。 然后,可以编辑重命名后的函数以满足你的需求。

此示例会继续执行示例代码,不进行任何更改。

编译和生成代码

编写完代码后,进行编译以确保不存在生成错误。 你的 .NET 函数项目会自动包含生成任务,这些任务会编译代码,然后将其添加到逻辑应用项目中的 lib\custom 文件夹中,其中工作流会查找要运行的自定义函数。 这些任务会根据 .NET 版本将程序集置于 lib\custom\net472 或 lib\custom\net8 文件夹中。

  1. 在 Visual Studio Code 的“终端”菜单中,选择“新建终端”。

  2. 在显示的工作目录列表中,选择“Functions”作为新终端的当前工作目录。

    屏幕截图显示了 Visual Studio Code、当前工作目录的提示以及选定的 Functions 目录。

    Visual Studio Code 会打开终端窗口,其中包含一个命令提示符。

  3. 终端窗口的命令提示符处,输入 dotnet restore

    Visual Studio Code 会分析你的项目并确定它们是否是最新的。

    屏幕截图显示了 Visual Studio Code、终端窗口和已完成的 dotnet restore 命令。

  4. 命令提示符重新出现后,输入 dotnet build。 或者,在“终端”菜单中,选择“运行任务”。 从任务列表中,选择“生成 (Functions)”。

    如果你的生成成功,则终端窗口将报告生成成功

  5. 确认你的逻辑应用项目中存在以下项:

    • 在工作区中,根据 .NET 版本展开以下文件夹:LogicApp>lib\custom>net472 或 net8。 确认分别名为 net472 或 net8 的子文件夹包含运行代码所需的程序集 (DLL) 文件,包括名为 <function-name>.dll 的文件。

    • 在工作区中,展开以下文件夹:LogicApp>lib\custom><函数名称>。 确认名为 <function-name> 的子文件夹包含 function.json 文件,该文件包含有关你编写的函数代码的元数据。 工作流设计器使用此文件来确定调用你的代码时所需的输入和输出。

    以下示例显示了逻辑应用项目中示例生成的程序集和其他文件:

    屏幕截图显示了 Visual Studio Code 和逻辑应用工作区,包括 .NET 函数项目和逻辑应用项目,现在生成了程序集和其他所需文件。

从工作流调用你的代码

确认你的代码已编译且逻辑应用项目包含代码运行所需的文件后,打开逻辑应用项目中包含的默认工作流。

  1. 在工作区的“LogicApp”下,展开 <workflow-name> 节点,打开 workflow.json 的快捷菜单,然后选择“打开设计器”。

    在打开的工作流设计器上,你的逻辑应用项目包含的默认工作流会随以下触发器和操作一起显示:

  2. 选择名为“在此逻辑应用中调用本地函数”的操作。

    该操作的信息窗格会在右侧打开。

    屏幕截图显示了 Visual Studio Code、工作流设计器和默认的工作流,其中包括触发器和操作。

  3. 查看并确认 Function Name 参数值已设为要运行的函数。 查看或更改函数使用的任何其他参数值。

调试代码和工作流

  1. 重复以下步骤以启动 Azurite 存储模拟器次:为以下每个 Azure 存储服务启动一次:

    • Azure Blob 服务
    • Azure 队列服务
    • Azure 表服务
    1. 在 Visual Studio Code 的“视图”菜单中选择“命令面板”。

    2. 在出现的提示中,找到并选择“Azurite:启动 Blob 服务”。

    3. 在显示的工作目录列表中,选择 LogicApp

    4. Azurite:启动队列服务Azurite:启动表服务重复这些步骤。

    当屏幕底部的 Visual Studio Code 任务栏显示三个存储服务正在运行时,你就成功了,例如:

    屏幕截图显示了 Visual Studio Code 任务栏和正在运行的 Azure Blob 服务、Azure 队列服务和 Azure 表服务。

  2. 按照以下步骤将调试器附加到你的逻辑应用项目:

    1. 在 Visual Studio Code 活动栏上,选择“运行并调试”。 (键盘:Ctrl+Shift+D)

      屏幕截图显示了 Visual Studio Code 活动栏和所选的“运行并调试”。

    2. 从“运行并调试”列表中,选择“附加到逻辑应用 (LogicApp)”(如果尚未选择),然后选择“运行”(绿色箭头)。

      屏幕截图显示了“运行并调试”列表,选择了“附加到逻辑应用”和“运行”按钮。

      终端”窗口随即打开,并显示已开始的调试过程。 然后,将显示“调试控制台”窗口,并显示调试状态。 在 Visual Studio Code 底部,任务栏变为橙色,表示 .NET 调试程序已加载。

  3. 根据代码执行以下步骤,将调试器附加到你的 .NET 函数项目:

    .NET 8 项目

    1. 在 Visual Studio Code 的“视图”菜单中选择“命令面板”。

    2. 在命令面板中,找到并选择“调试:附加到 .NET 5+ 或 .NET Core 进程”。

      屏幕截图显示了“运行并调试”列表,选择了“附加到 NET Functions”和“运行”按钮。

    3. 从列表中找到并选择 dotnet.exe 进程。 如果存在多个 dotnet.exe 进程,请选择具有以下路径的进程:

      <drive-name>:\Users<user-name>.azure-functions-core-tools\Functions\ExtensionBundles\Microsoft.Azure.Functions.ExtensionBundle.Workflows<extension-bundle-version>\CustomCodeNetFxWorker\net8\Microsoft.Azure.Workflows.Functions.CustomCodeNetFxWorker.dll

    .NET Framework 项目

    从“运行并调试”列表中,选择“附加到 .NET 函数 (Functions)”(如果尚未选择),然后选择“运行”(绿色箭头)。

    屏幕截图显示了“运行并调试”列表,选择了“附加到 .NET 函数 (Functions)”和“运行”按钮。

  4. 若要设置任何断点,在你的函数定义 (<function-name>.cs) 或工作流定义 (workflow.json) 中,找到需要断点的行号,然后选择左侧的列,例如:

    屏幕截图显示了 Visual Studio Code 和打开的函数代码文件,其中为代码中的某一行设置了断点。

  5. 若要在工作流中手动运行请求触发器,请打开工作流的“概述”页。

    1. 在逻辑应用项目中,打开 workflow.json 文件的快捷菜单,然后选择“概述”。

      在工作流的“概述”页上,当你想要手动启动工作流时,可以使用“运行触发器”按钮。 在“工作流属性”下,“回叫 URL”值是你的工作流中请求触发器创建的可调用终结点的 URL。 可以向此 URL 发送请求,以从其他应用(包括其他逻辑应用工作流)触发工作流。

      屏幕截图显示了打开的 Visual Studio Code 和工作流的概述页面。

  6. 在“概述”页工具栏中,选择“运行触发器”。

    工作流开始运行后,调试器会激活你的第一个断点。

  7. 在“运行”菜单或调试程序工具栏中,选择“调试操作”。

    工作流运行完成后,“概述”页会显示已完成的运行以及有关该运行的基本详情。

  8. 要查看有关工作流运行的详细信息,请选择已完成的运行。 或者,在“持续时间”列旁边的列表中,选择“显示运行”。

    屏幕截图显示了打开的 Visual Studio Code 和已完成的工作流运行。

部署你的代码

可以通过部署逻辑应用项目相同的方式部署自定义函数。 无论是从 Visual Studio Code 部署还是使用 CI/CD DevOps 过程进行部署,请确保在部署之前生成代码,并且所有依赖程序集都存在于以下逻辑应用项目文件夹中:

  • .NET 4.7.2:lib/custom/net472 文件夹

  • .NET 8:lib/custom/net8 文件夹

有关详细信息,请参阅将标准工作流从 Visual Studio Code 部署到 Azure

排查问题

操作信息窗格错误

在工作流设计器中,当你选择名为“调用此逻辑应用中的本地函数”的内置操作时,该操作的信息窗格会显示以下消息:

Failed to retrieve dynamic inputs. Error details:

在此方案中,检查你的逻辑应用项目以确定 LogicApp\lib\custom 文件夹是否为空。 如果为空,请在“终端”菜单中选择“运行任务>生成 Functions”。

当前没有具有指定名称的进程在运行

如果在运行工作流时收到此错误消息,则很可能是因为你将调试程序进程附加到了 .NET Functions,而不是你的逻辑应用。

要解决此问题,请在“运行并调试”列表中,选择“附加到逻辑应用 (LogicApp)”,然后选择“运行”(绿色三角形)。

包未正确导入

如果“输出”窗口显示了类似于以下消息的错误,请确保你已至少安装 .NET 6.0。 如果已安装此版本,请尝试卸载,然后重新安装。

C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.targets(83,5): warning : The ExtensionsMetadataGenerator package was not imported correctly. Are you missing 'C:\Users\yourUserName\.nuget\packages\microsoft.azure.webjobs.script.extensionsmetadatagenerator\4.0.1\build\Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator.targets' or 'C:\Users\yourUserName\.nuget\packages\microsoft.azure.webjobs.script.extensionsmetadatagenerator\4.0.1\build\Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator.props'? [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] WeatherForecast -> C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\\bin\Debug\net472\WeatherForecast.dll C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : It was not possible to find any compatible framework version [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : The specified framework 'Microsoft.NETCore.App', version '6.0.0' was not found. [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : - Check application dependencies and target a framework version installed at: [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj]

生成失败

如果你的函数不包含变量,并且生成了代码,则“输出”窗口可能会显示以下错误消息:

C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1031: Type expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]
C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1001: Identifier expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]

Build FAILED.

C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1031: Type expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]
C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1001: Identifier expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]

0 Warning(s)
2 Error(s)

要解决此问题,请在代码的 Run 方法中追加以下参数:

string parameter1 = null

以下示例显示了 Run 方法签名的显示方式:

public static Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale, string parameter1 = null)

后续步骤

使用 Visual Studio Code 创建标准工作流