Compartir a través de

有关在隔离的工作进程模型中运行 C# Azure Functions 的指南

本文介绍如何使用隔离工作进程模型在 .NET 中使用 Azure Functions。 此模型允许项目独立于其他运行时组件面向 .NET 版本。 有关支持的特定 .NET 版本的信息,请参阅支持的版本

使用以下链接立即开始生成 .NET 隔离工作进程模型函数。

入门 概念 示例

若要了解如何将隔离的工作进程模型项目部署到 Azure,请参阅部署到 Azure Functions

隔离工作进程模型的优点

有两种运行 .NET 类库函数的模式:在与 Functions 主机运行时相同的进程中进程内)或在隔离的工作进程中。 当 .NET 函数在独立工作进程中运行时,你可以利用以下优势:

  • 减少冲突:由于函数在独立的进程中运行,因此应用中使用的程序集不会与主机进程所用的相同程序集的不同版本发生冲突。
  • 全面控制进程:可以控制应用的启动,这意味着你可管理所用的配置和启动的中间件。
  • 标准依赖项注入:由于可以全面控制进程,因此可以使用当前的 .NET 依赖项注入行为,并将中间件整合到函数应用中。
  • .NET 版本灵活性:在主机进程外部运行意味着函数可以在 Functions 运行时(包括 .NET Framework)本机不支持的 .NET 版本上运行。

如果有一个在进程内运行的现有 C# 函数应用,则需要迁移应用以利用这些优势。 有关详细信息,请参阅将 .NET 应用从进程内模型迁移到隔离工作进程模型

有关这两种模式之间的全面比较,请参阅进程内与隔离工作进程 .NET Azure Functions 之间的差异

支持的版本

Functions 运行时版本使用特定版本的 .NET。 若要详细了解 Functions 版本,请参阅 Azure Functions 运行时版本概述。 版本支持取决于 Functions 是在进程内运行还是在独立工作进程中运行。

注意

若要了解如何更改函数应用使用的 Functions 运行时版本,请参阅查看和更新当前运行时版本

下表显示了可与特定版本的 Functions 配合使用的 .NET Core 或 .NET Framework 的最高级别。

Functions 运行时版本 进程内
.NET 类库
独立工作进程
.NET 独立
Functions 4.x .NET 6.0 .NET 6.0
.NET 7.0 (GA)1
.NET Framework 4.8 (GA)1
Functions 1.x .NET Framework 4.8 不适用

1 生成过程还需要使用 .NET 6 SDK

有关 Azure Functions 版本的最新消息,包括删除较旧的特定次要版本,请关注 Azure 应用服务公告

项目结构

Azure Functions 的 .NET 项目采用了独立工作器模型,其本质上是一个 .NET 控制台应用程序项目,而且面向受支持的 .NET 运行时。 下面是任何 .NET 隔离项目中所需的基本文件:

  • C# 项目文件 (.csproj),用于定义项目和依赖项。
  • Program.cs 文件,应用的入口点。
  • 任何用于定义函数的代码文件。
  • 定义你的项目中函数共享的配置的 host.json 文件。
  • 定义你的项目在计算机上本地运行时使用的环境变量的 local.settings.json 文件。

有关完整示例,请参阅 .NET 8 示例项目.NET Framework 4.8 示例项目

包引用

Azure Functions 的项目 .NET 项目使用了独立工作器模型,而且对核心功能和绑定扩展都使用一组唯一的包。

核心包

需要使用以下包在独立工作进程中运行 .NET 函数:

版本 2.x(预览版)

核心包的 2.x 版本会更改支持的框架,并支持这些更高版本中的新 .NET API。 面向 .NET 9(预览版)或更高版本时,应用需要引用这两个包的 2.0.0-preview1 版本或更高版本。

初始预览版本与针对版本 1.x 编写的代码兼容。 但是,在预览期间,较新版本可能会引入可能影响你编写的代码的行为更改。

更新到 2.x 版本时,请注意以下更改:

  • 从版本 2.0.0-preview2 开始,Microsoft.Azure.Functions.Worker.SdkSDK 容器生成添加了默认配置。
  • Microsoft.Azure.Functions.Worker 版本 2.0.0-preview2 开始:
    • 此版本添加了对 IHostApplicationBuilder 的支持。 本指南中的一些示例包括使用 IHostApplicationBuilder 来显示替代项的选项卡。 这些示例需要 2.x 版本。
    • 如果在开发环境中运行,则默认包含服务提供商范围验证。 此行为与 ASP.NET Core 匹配。
    • 默认情况下会启用 EnableUserCodeException 选项。 此属性现在已标记为过时。
    • 默认情况下会启用 IncludeEmptyEntriesInMessagePayload 选项。 启用此选项后,表示集合的触发器有效负载始终包含空条目。 例如,如果发送的消息没有正文,则触发器数据的 string[] 中仍存在空条目。 包含空条目有助于与函数可能引用的元数据数组进行交叉引用。 可以通过在 WorkerOptions 服务配置中将 IncludeEmptyEntriesInMessagePayload 设置为 false 来禁用此行为。
    • ILoggerExtensions 类已重命名为 FunctionsLoggerExtensions。 重命名可防止在 ILogger 实例上使用 LogMetric() 时出现不明确的调用错误。

扩展包

由于 .NET 独立工作进程函数使用不同的绑定类型,因此它们需要一组唯一的绑定扩展包。

可以在 Microsoft.Azure.Functions.Worker.Extensions 下找到这些扩展包。

启动和配置

使用隔离工作器函数时,可以访问函数应用的启动代码(通常在 Program.cs 中)。 你需要负责创建并启动自己的主机实例。 因此,你还可以直接访问应用的配置管道。 使用 .NET Functions 独立工作进程时,可以更轻松地添加配置、注入依赖项并运行自己的中间件。

以下代码显示了 HostBuilder 管道的示例:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //     Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults()
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddApplicationInsightsTelemetryWorkerService();
                    s.ConfigureFunctionsApplicationInsights();
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                    s.Configure<LoggerFilterOptions>(options =>
                    {
                        // The Application Insights SDK adds a default logging filter that instructs ILogger to capture only Warning and more severe logs. Application Insights requires an explicit override.
                        // Log levels can also be configured using appsettings.json. For more information, see /azure-monitor/app/worker-service#ilogger-logs
                        LoggerFilterRule toRemove = options.Rules.FirstOrDefault(rule => rule.ProviderName
                            == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");

                        if (toRemove is not null)
                        {
                            options.Rules.Remove(toRemove);
                        }
                    });
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

此代码需要 using Microsoft.Extensions.DependencyInjection;

IHostBuilder 上调用 Build() 之前,应:

  • 如果使用 ASP.NET Core 集成,则调用 ConfigureFunctionsWebApplication(),否则调用 ConfigureFunctionsWorkerDefaults()。 有关这些选项的详细信息,请参阅 HTTP 触发器
    如果要使用 F# 编写应用程序,则某些触发器和绑定扩展需要额外的配置。 当你计划在 F# 应用中使用这些扩展时,请参阅 Blob 扩展表扩展Cosmos DB 扩展的设置文档。
  • 配置项目所需的任何服务或应用配置。 有关详细信息,请参阅配置
    如果你计划使用 Application Insights,则需要在 ConfigureServices() 委托中调用 AddApplicationInsightsTelemetryWorkerService()ConfigureFunctionsApplicationInsights()。 有关详细信息,请参阅 Application Insights

如果项目面向 .NET Framework 4.8,则还需要在创建 HostBuilder 之前添加 FunctionsDebugger.Enable();。 它应该是方法 Main() 的第一行。 有关详细信息,请参阅以 .NET Framework 为目标时进行调试

HostBuilder 用于生成并返回完全初始化的 IHost 实例,你以异步方式运行该实例以启动函数应用。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //     Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults()
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddApplicationInsightsTelemetryWorkerService();
                    s.ConfigureFunctionsApplicationInsights();
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                    s.Configure<LoggerFilterOptions>(options =>
                    {
                        // The Application Insights SDK adds a default logging filter that instructs ILogger to capture only Warning and more severe logs. Application Insights requires an explicit override.
                        // Log levels can also be configured using appsettings.json. For more information, see /azure-monitor/app/worker-service#ilogger-logs
                        LoggerFilterRule toRemove = options.Rules.FirstOrDefault(rule => rule.ProviderName
                            == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");

                        if (toRemove is not null)
                        {
                            options.Rules.Remove(toRemove);
                        }
                    });
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

配置

使用的生成器类型决定了应用程序的配置方法。

ConfigureFunctionsWorkerDefaults 方法用于添加运行函数应用所需的设置。 该方法包括以下功能:

  • 转换器的默认设置。
  • 设置默认 JsonSerializerOptions 以忽略属性名称大小写。
  • 与 Azure Functions 日志记录集成。
  • 输出绑定中间件和功能。
  • 函数执行中间件。
  • 默认的 gRPC 支持。
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //     Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults()
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddApplicationInsightsTelemetryWorkerService();
                    s.ConfigureFunctionsApplicationInsights();
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                    s.Configure<LoggerFilterOptions>(options =>
                    {
                        // The Application Insights SDK adds a default logging filter that instructs ILogger to capture only Warning and more severe logs. Application Insights requires an explicit override.
                        // Log levels can also be configured using appsettings.json. For more information, see /azure-monitor/app/worker-service#ilogger-logs
                        LoggerFilterRule toRemove = options.Rules.FirstOrDefault(rule => rule.ProviderName
                            == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");

                        if (toRemove is not null)
                        {
                            options.Rules.Remove(toRemove);
                        }
                    });
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

能够访问主机生成器管道意味着还可以在初始化期间设置任何特定于应用的配置。 可以一次或多次调用 HostBuilder 上的 ConfigureAppConfiguration 方法,以添加代码所需的任何配置源。 若要详细了解应用配置,请参阅 ASP.NET Core 中的配置

这些配置仅适用于你创作的工作器代码,不会直接影响 Functions 主机或触发器和绑定的配置。 若要更改 Functions 主机或触发器以及绑定配置,则仍需使用 host.json 文件

注意

自定义配置源不可用于配置触发器和绑定。 触发器和绑定配置必须可供 Functions 平台(而不仅仅是应用程序代码)使用。 可以通过应用程序设置密钥保管库引用应用程序配置引用功能提供此配置。

依赖关系注入

隔离的工作器模型使用标准 .NET 机制来注入服务。

使用 HostBuilder 时,请在主机生成器上调用 ConfigureServices,并使用 IServiceCollection 上的扩展方法来注入特定服务。 以下示例注入单一实例服务依赖项:

.ConfigureServices(services =>
{
    services.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
})

此代码需要 using Microsoft.Extensions.DependencyInjection;。 有关详细信息,请参阅 ASP.NET Core 中的依赖项注入

注册 Azure 客户端

可通过依赖项注入来与其他 Azure 服务交互。 可以使用 Microsoft.Extensions.Azure 包从 Azure SDK for .NET 注入客户端。 安装包后,通过在 Program.cs 中的服务集合上调用 AddAzureClients()注册客户端。 以下示例会为 Azure Blob 配置一个命名客户端

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices((hostContext, services) =>
    {
        services.AddAzureClients(clientBuilder =>
        {
            clientBuilder.AddBlobServiceClient(hostContext.Configuration.GetSection("MyStorageConnection"))
                .WithName("copierOutputBlob");
        });
    })
    .Build();

host.Run();

以下示例演示如何使用此注册和 SDK 类型,通过注入的客户端将 Blob 内容作为流从一个容器复制到另一个容器:

using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Logging;

namespace MyFunctionApp
{
    public class BlobCopier
    {
        private readonly ILogger<BlobCopier> _logger;
        private readonly BlobContainerClient _copyContainerClient;

        public BlobCopier(ILogger<BlobCopier> logger, IAzureClientFactory<BlobServiceClient> blobClientFactory)
        {
            _logger = logger;
            _copyContainerClient = blobClientFactory.CreateClient("copierOutputBlob").GetBlobContainerClient("samples-workitems-copy");
            _copyContainerClient.CreateIfNotExists();
        }

        [Function("BlobCopier")]
        public async Task Run([BlobTrigger("samples-workitems/{name}", Connection = "MyStorageConnection")] Stream myBlob, string name)
        {
            await _copyContainerClient.UploadBlobAsync(name, myBlob);
            _logger.LogInformation($"Blob {name} copied!");
        }

    }
}

此示例中的 ILogger<T> 也是通过依赖项注入获取的,因此它会自动注册。 若要详细了解日志记录的配置选项,请参阅日志记录

提示

该示例在 Program.cs 和函数中使用文本字符串作为客户端的名称。 请考虑改用在函数类上定义的共享常量字符串。 例如,可以添加 public const string CopyStorageClientName = nameof(_copyContainerClient);,然后在这两个位置引用 BlobCopier.CopyStorageClientName。 同样,可以使用函数定义配置节名称,而不是在 Program.cs 中定义。

中间件

独立的工作器模型还支持中间件注册,方法同样是使用与 ASP.NET 中存在的模型相似的模型。 使用此模型可以在执行函数之前和之后将逻辑注入到调用管道中。

ConfigureFunctionsWorkerDefaults 扩展方法具有一个重载,可让你注册自己的中间件,如以下示例中所示。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using Microsoft.Extensions.Hosting;

namespace CustomMiddleware
{
    public class Program
    {
        public static void Main()
        {
            //<docsnippet_middleware_register>
            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults(workerApplication =>
                {
                    // Register our custom middlewares with the worker

                    workerApplication.UseMiddleware<ExceptionHandlingMiddleware>();

                    workerApplication.UseMiddleware<MyCustomMiddleware>();

                    workerApplication.UseWhen<StampHttpHeaderMiddleware>((context) =>
                    {
                        // We want to use this middleware only for http trigger invocations.
                        return context.FunctionDefinition.InputBindings.Values
                                      .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
                    });
                })
                .Build();
            //</docsnippet_middleware_register>
            host.Run();
        }
    }
}

UseWhen 扩展方法可用于注册按条件执行的中间件。 必须将一个返回布尔值的谓词传递给此方法,如果该谓词的返回值为 true,则中间件将参与调用处理管道。

利用 FunctionContext 上的以下扩展方法可在独立模型中更轻松地使用中间件。

方法 说明
GetHttpRequestDataAsync 在被 HTTP 触发器调用时获取 HttpRequestData 实例。 此方法返回 ValueTask<HttpRequestData?> 的实例,当你要读取消息数据(例如请求标头和 Cookie)时,该实例很有用。
GetHttpResponseData 在被 HTTP 触发器调用时获取 HttpResponseData 实例。
GetInvocationResult 获取 InvocationResult 的实例,该实例表示当前函数执行的结果。 根据需要使用 Value 属性获取或设置值。
GetOutputBindings 获取当前函数执行的输出绑定条目。 此方法结果中的每个条目的类型都是 OutputBindingData。 可以根据需要使用 Value 属性获取或设置值。
BindInputAsync 为请求的 BindingMetadata 实例绑定一个输入绑定项。 例如,当你的函数具有需要由中间件使用的 BlobInput 输入绑定时,可以使用此方法。

这是一个中间件实现的示例,它在函数执行期间读取 HttpRequestData 实例并更新 HttpResponseData 实例:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;

namespace CustomMiddleware
{
    /// <summary>
    /// Middleware to stamp a response header on the result of http trigger invocation.
    /// </summary>
    //<docsnippet_middleware_example_stampheader>
    internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware
    {
        public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
        {
            var requestData = await context.GetHttpRequestDataAsync();

            string correlationId;
            if (requestData!.Headers.TryGetValues("x-correlationId", out var values))
            {
                correlationId = values.First();
            }
            else
            {
                correlationId = Guid.NewGuid().ToString();
            }

            await next(context);

            context.GetHttpResponseData()?.Headers.Add("x-correlationId", correlationId);
        }
    }
    //</docsnippet_middleware_example_stampheader>
}

此中间件检查是否存在特定请求标头 (x-correlationId),如果存在,则此中间件将使用该标头值标记响应标头。 否则,它会生成一个新的 GUID 值,并使用该值来标记响应标头。 有关在函数应用中使用自定义中间件的更完整示例,请参阅自定义中间件参考示例

自定义 JSON 序列化

隔离的工作进程模型默认使用 System.Text.Json。 可以通过将服务配置为 Program.cs 文件的一部分来自定义序列化程序的行为。 本部分介绍常规用途序列化,不会影响 HTTP 触发具有 ASP.NET Core 集成的 JSON 序列化,后者必须单独配置。

以下示例使用 ConfigureFunctionsWebApplication 演示了此操作,但它也适用于 ConfigureFunctionsWorkerDefaults

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication((IFunctionsWorkerApplicationBuilder builder) =>
    {
        builder.Services.Configure<JsonSerializerOptions>(jsonSerializerOptions =>
        {
            jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            jsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;

            // override the default value
            jsonSerializerOptions.PropertyNameCaseInsensitive = false;
        });
    })
    .Build();

host.Run();

你可能想要改用 JSON.NET (Newtonsoft.Json) 进行序列化。 为此需要安装 Microsoft.Azure.Core.NewtonsoftJson 包。 然后,在服务注册中,重新分配 WorkerOptions 配置中的 Serializer 属性。 以下示例使用 ConfigureFunctionsWebApplication 演示了此操作,但它也适用于 ConfigureFunctionsWorkerDefaults

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication((IFunctionsWorkerApplicationBuilder builder) =>
    {
        builder.Services.Configure<WorkerOptions>(workerOptions =>
        {
            var settings = NewtonsoftJsonObjectSerializer.CreateJsonSerializerSettings();
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            settings.NullValueHandling = NullValueHandling.Ignore;

            workerOptions.Serializer = new NewtonsoftJsonObjectSerializer(settings);
        });
    })
    .Build();

host.Run();

识别为函数的方法

函数方法是公共类的公共方法,其中包含一个应用于该方法的 Function 属性以及一个应用于输入参数的触发器属性,如以下示例所示:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using Azure.Storage.Queues.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public class QueueFunction
    {
        private readonly ILogger<QueueFunction> _logger;

        public QueueFunction(ILogger<QueueFunction> logger)
        {
            _logger = logger;
        }

        //<docsnippet_queue_output_binding>
        //<docsnippet_queue_trigger>
        [Function(nameof(QueueFunction))]
        [QueueOutput("output-queue")]
        public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context)
        //</docsnippet_queue_trigger>
        {
            // Use a string array to return more than one message.
            string[] messages = {
                $"Album name = {myQueueItem.Name}",
                $"Album songs = {myQueueItem.Songs.ToString()}"};

            _logger.LogInformation("{msg1},{msg2}", messages[0], messages[1]);

            // Queue Output messages
            return messages;
        }
        //</docsnippet_queue_output_binding>

        /// <summary>
        /// This function demonstrates binding to a single <see cref="QueueMessage"/>.
        /// </summary>
        [Function(nameof(QueueMessageFunction))]
        public void QueueMessageFunction([QueueTrigger("input-queue")] QueueMessage message)
        {
            _logger.LogInformation(message.MessageText);
        }

        /// <summary>
        /// This function demonstrates binding to a single <see cref="BinaryData"/>.
        /// </summary>
        [Function(nameof(QueueBinaryDataFunction))]
        public void QueueBinaryDataFunction([QueueTrigger("input-queue")] BinaryData message)
        {
            _logger.LogInformation(message.ToString());
        }
    }

    public class Album
    {
        public string Id { get; set; }

        public string Name { get; set; }

        public List<string> Songs { get; set; }
    }
}

触发器属性指定触发器类型并将输入数据绑定到一个方法参数。 以上示例函数将由一条队列消息触发,队列消息将传递给方法中的 myQueueItem 参数。

Function 属性将该方法标记为函数入口点。 该名称在项目中必须是唯一的,以字母开头,并且只包含字母、数字、_-,长度不得超过 127 个字符。 项目模板通常创建一个名为 Run 的方法,但方法名称可以是任何有效的 C# 方法名称。 该方法必须是公共类的公共成员。 它通常应该是一个实例方法,以便可以通过依赖项注入传入服务。

函数参数

下面是可以作为函数方法签名的一部分包含的一些参数:

  • 绑定,通过将参数修饰为属性进行标记。 函数必须恰好包含一个触发器参数。
  • 执行上下文对象,提供有关当前调用的信息。
  • 取消令牌,用于正常关闭。

执行上下文

.NET 隔离进程将 FunctionContext 对象传递给函数方法。 使用此对象可以通过调用 GetLogger 方法并提供 categoryName 字符串,来获取要写入到日志的 ILogger 实例。 可以使用此上下文来获取 ILogger,而无需使用依赖项注入。 有关详细信息,请参阅日志记录

取消令牌

函数可以接受 CancellationToken 参数,以使操作系统能够在函数即将终止时通知代码。 可以使用此通知来确保该函数不会意外终止,导致数据处于不一致状态。

取消令牌在独立工作进程中运行时,受 .NET 函数支持。 以下示例在收到取消请求时引发异常:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Net7Worker
{
    public class EventHubCancellationToken
    {
        private readonly ILogger _logger;

        public EventHubCancellationToken(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<EventHubCancellationToken>();
        }

        // Sample showing how to handle a cancellation token being received
        // In this example, the function invocation status will be "Cancelled"
        //<docsnippet_cancellation_token_throw>
        [Function(nameof(ThrowOnCancellation))]
        public async Task ThrowOnCancellation(
            [EventHubTrigger("sample-workitem-1", Connection = "EventHubConnection")] string[] messages,
            FunctionContext context,
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(ThrowOnCancellation));

            foreach (var message in messages)
            {
                cancellationToken.ThrowIfCancellationRequested();
                await Task.Delay(6000); // task delay to simulate message processing
                _logger.LogInformation("Message '{msg}' was processed.", message);
            }
        }
        //</docsnippet_cancellation_token_throw>

        // Sample showing how to take precautionary/clean up actions if a cancellation token is received
        // In this example, the function invocation status will be "Successful"
        //<docsnippet_cancellation_token_cleanup>
        [Function(nameof(HandleCancellationCleanup))]
        public async Task HandleCancellationCleanup(
            [EventHubTrigger("sample-workitem-2", Connection = "EventHubConnection")] string[] messages,
            FunctionContext context,
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(HandleCancellationCleanup));

            foreach (var message in messages)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    _logger.LogInformation("A cancellation token was received, taking precautionary actions.");
                    // Take precautions like noting how far along you are with processing the batch
                    _logger.LogInformation("Precautionary activities complete.");
                    break;
                }

                await Task.Delay(6000); // task delay to simulate message processing
                _logger.LogInformation("Message '{msg}' was processed.", message);
            }
        }
        //</docsnippet_cancellation_token_cleanup>
    }
}

以下示例在收到取消请求时执行清理操作:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Net7Worker
{
    public class EventHubCancellationToken
    {
        private readonly ILogger _logger;

        public EventHubCancellationToken(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<EventHubCancellationToken>();
        }

        // Sample showing how to handle a cancellation token being received
        // In this example, the function invocation status will be "Cancelled"
        //<docsnippet_cancellation_token_throw>
        [Function(nameof(ThrowOnCancellation))]
        public async Task ThrowOnCancellation(
            [EventHubTrigger("sample-workitem-1", Connection = "EventHubConnection")] string[] messages,
            FunctionContext context,
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(ThrowOnCancellation));

            foreach (var message in messages)
            {
                cancellationToken.ThrowIfCancellationRequested();
                await Task.Delay(6000); // task delay to simulate message processing
                _logger.LogInformation("Message '{msg}' was processed.", message);
            }
        }
        //</docsnippet_cancellation_token_throw>

        // Sample showing how to take precautionary/clean up actions if a cancellation token is received
        // In this example, the function invocation status will be "Successful"
        //<docsnippet_cancellation_token_cleanup>
        [Function(nameof(HandleCancellationCleanup))]
        public async Task HandleCancellationCleanup(
            [EventHubTrigger("sample-workitem-2", Connection = "EventHubConnection")] string[] messages,
            FunctionContext context,
            CancellationToken cancellationToken)
        {
            _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(HandleCancellationCleanup));

            foreach (var message in messages)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    _logger.LogInformation("A cancellation token was received, taking precautionary actions.");
                    // Take precautions like noting how far along you are with processing the batch
                    _logger.LogInformation("Precautionary activities complete.");
                    break;
                }

                await Task.Delay(6000); // task delay to simulate message processing
                _logger.LogInformation("Message '{msg}' was processed.", message);
            }
        }
        //</docsnippet_cancellation_token_cleanup>
    }
}

绑定

绑定是通过在方法、参数和返回类型中使用特性定义的。 绑定可提供字符串、数组和可序列化类型的数据,例如普通旧类对象 (POCO)。 对于某些绑定扩展,还可以绑定到服务 SDK 中定义的特定于服务的类型

对于 HTTP 触发器,请参阅 HTTP 触发器部分。

有关使用触发器和具有隔离工作进程函数的绑定的完整参考示例集,请参阅绑定扩展参考示例

输入绑定

一个函数可以有零个或多个可向函数传递数据的输入绑定。 与触发器一样,输入绑定是通过向输入参数应用绑定特性来定义的。 执行函数时,运行时将尝试获取绑定中指定的数据。 请求的数据通常依赖于触发器使用绑定参数提供的信息。

输出绑定

若要写入到输出绑定,必须将输出绑定属性应用到函数方法,该方法定义了如何写入到绑定服务。 该方法返回的值将写入到输出绑定。 例如,以下示例使用输出绑定将一个字符串值写入到名为 output-queue 的消息队列:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using Azure.Storage.Queues.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public class QueueFunction
    {
        private readonly ILogger<QueueFunction> _logger;

        public QueueFunction(ILogger<QueueFunction> logger)
        {
            _logger = logger;
        }

        //<docsnippet_queue_output_binding>
        //<docsnippet_queue_trigger>
        [Function(nameof(QueueFunction))]
        [QueueOutput("output-queue")]
        public string[] Run([QueueTrigger("input-queue")] Album myQueueItem, FunctionContext context)
        //</docsnippet_queue_trigger>
        {
            // Use a string array to return more than one message.
            string[] messages = {
                $"Album name = {myQueueItem.Name}",
                $"Album songs = {myQueueItem.Songs.ToString()}"};

            _logger.LogInformation("{msg1},{msg2}", messages[0], messages[1]);

            // Queue Output messages
            return messages;
        }
        //</docsnippet_queue_output_binding>

        /// <summary>
        /// This function demonstrates binding to a single <see cref="QueueMessage"/>.
        /// </summary>
        [Function(nameof(QueueMessageFunction))]
        public void QueueMessageFunction([QueueTrigger("input-queue")] QueueMessage message)
        {
            _logger.LogInformation(message.MessageText);
        }

        /// <summary>
        /// This function demonstrates binding to a single <see cref="BinaryData"/>.
        /// </summary>
        [Function(nameof(QueueBinaryDataFunction))]
        public void QueueBinaryDataFunction([QueueTrigger("input-queue")] BinaryData message)
        {
            _logger.LogInformation(message.ToString());
        }
    }

    public class Album
    {
        public string Id { get; set; }

        public string Name { get; set; }

        public List<string> Songs { get; set; }
    }
}

多个输出绑定

写入到输出绑定的数据始终是函数的返回值。 如果需要写入到多个输出绑定,必须创建自定义返回类型。 在此返回类型中,必须已将输出绑定特性应用到类的一个或多个属性。 以下示例是一个 HTTP 触发的函数,它使用 ASP.NET Core 集成写入 HTTP 响应和队列输出绑定:

public class MultipleOutputBindings
{
    private readonly ILogger<MultipleOutputBindings> _logger;

    public MultipleOutputBindings(ILogger<MultipleOutputBindings> logger)
    {
        _logger = logger;
    }

    [Function("MultipleOutputBindings")]
    public MyOutputType Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
    {
        _logger.LogInformation("C# HTTP trigger function processed a request.");
        var myObject = new MyOutputType
        {
            Result = new OkObjectResult("C# HTTP trigger function processed a request."),
            MessageText = "some output"
        };
        return myObject;
    }

    public class MyOutputType
    {
        [HttpResult]
        public IActionResult Result { get; set; }

        [QueueOutput("myQueue")]
        public string MessageText { get; set; }
    }
}

对具有 ASP.NET Core 集成的多个输出绑定使用自定义返回类型时,必须将 [HttpResult] 属性添加到提供结果的属性。 将 SDK 1.17.3-preview2 或更高版本HTTP 扩展版本 3.2.0 或更高版本以及 ASP.NET Core 扩展版本 1.3.0 或更高版本结合使用时,可使用 HttpResult 属性。

SDK 类型

对于某些特定于服务的绑定类型,可以使用服务 SDK 和框架中的类型提供绑定数据。 除了序列化字符串或普通旧 CLR 对象 (POCO) 可以提供的功能之外,它们还提供了更多功能。 要使用较新的类型,需要更新项目来使用较新版本的核心依赖项。

依赖项 版本要求
Microsoft.Azure.Functions.Worker 1.18.0 或更高版本
Microsoft.Azure.Functions.Worker.Sdk 1.13.0 或更高版本

在计算机上本地测试 SDK 类型时,还需要使用 Azure Functions Core Tools 版本 4.0.5000 或更高版本。 可以使用 func version 命令检查当前版本。

每个触发器和绑定扩展也有自己的最低版本要求,详见扩展参考文章。 以下特定于服务的绑定提供 SDK 类型:

服务 触发器 输入绑定 输出绑定
Azure Blobs 正式发布版 正式发布版 不建议使用 SDK 类型。1
Azure 队列 正式发布版 输入绑定不存在 不建议使用 SDK 类型。1
Azure 服务总线 正式发布版 输入绑定不存在 不建议使用 SDK 类型。1
Azure 事件中心 正式发布版 输入绑定不存在 不建议使用 SDK 类型。1
Azure Cosmos DB 未使用的 SDK 类型2 正式发布版 不建议使用 SDK 类型。1
Azure 表 触发器不存在 正式发布版 不建议使用 SDK 类型。1
Azure 事件网格 正式发布版 输入绑定不存在 不建议使用 SDK 类型。1

1 对于使用 SDK 类型的输出方案,应直接创建和使用 SDK 客户端,而不是使用输出绑定。 如需依赖项注入示例,请参阅注册 Azure 客户端

2 Cosmos DB 触发器使用 Azure Cosmos DB 更改源,并将更改源项公开为 JSON 可序列化类型。 缺少 SDK 类型是面向此方案的有意设计。

注意

使用依赖于触发器数据的绑定表达式时,不能使用触发器本身的 SDK 类型。

HTTP 触发器

使用 HTTP 触发器,可以通过 HTTP 请求调用函数。 可使用两种不同的方法:

  • ASP.NET Core 集成模型,它使用 ASP.NET Core 开发人员熟悉的概念
  • 内置模型,不需要额外的依赖项,并将自定义类型用于 HTTP 请求和响应。 此方法保持与以前的 .NET 隔离工作进程应用的向后兼容性。

ASP.NET Core 集成

本部分介绍如何使用 ASP.NET Core 提供的类型(包括 HttpRequestHttpResponseIActionResult)处理基础 HTTP 请求和响应对象。 此模型不适用于面向 .NET Framework 的应用,应改用内置模型

注意

此模型并不具备 ASP.NET Core 的所有功能。 具体而言,ASP.NET Core 中间件管道和路由功能不可用。 ASP.NET Core 集成要求使用更新的包。

若要为 HTTP 启用 ASP.NET Core 集成:

  1. 在项目中添加对 Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore 包 1.0.0 或更高版本的引用。

  2. 更新项目以使用这些特定的包版本:

  3. Program.cs 文件中,更新主机生成器配置来调用 ConfigureFunctionsWebApplication()。 如果以其他方式使用此方法,这会替换 ConfigureFunctionsWorkerDefaults()。 以下示例演示了一个没有其他自定义项的极简设置:

    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Extensions.Hosting;
    
    var host = new HostBuilder()
        .ConfigureFunctionsWebApplication()
        .Build();
    
    host.Run();
    
  4. 更新任何现有的 HTTP 触发函数以使用 ASP.NET Core 类型。 此示例显示了用于简单的“hello, world”函数的标准 HttpRequestIActionResult

    [Function("HttpFunction")]
    public IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequest req)
    {
        return new OkObjectResult($"Welcome to Azure Functions, {req.Query["name"]}!");
    }
    

具有 ASP.NET Core 集成的 JSON 序列化

ASP.NET Core 有其自己的序列化层,并且不受自定义常规序列化配置的影响。 要自定义用于 HTTP 触发器的序列化行为,需要在服务注册过程中包括 .AddMvc() 调用。 返回的 IMvcBuilder 可用于修改 ASP.NET Core 的 JSON 序列化设置。 以下示例演示如何使用此方法配置 JSON.NET (Newtonsoft.Json) 进行序列化:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        services.AddMvc().AddNewtonsoftJson();
    })
    .Build();
host.Run();

内置 HTTP 模型

在内置模型中,系统将传入的 HTTP 请求消息转换为要传递给函数的 HttpRequestData 对象。 此对象提供请求中的数据,包括 HeadersCookiesIdentitiesURL 和可选消息 Body。 此对象是 HTTP 请求的表示形式,但未直接连接到基础 HTTP 侦听器或收到的消息。

同样,函数返回一个 HttpReponseData 对象,该对象提供用于创建 HTTP 响应的数据,包括消息 StatusCodeHeaders 和可选消息 Body

以下示例演示了 HttpRequestDataHttpResponseData 的用法:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public static class HttpFunction
    {
        //<docsnippet_http_trigger>
        [Function(nameof(HttpFunction))]
        public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
            FunctionContext executionContext)
        {
            //<docsnippet_logging>
            var logger = executionContext.GetLogger(nameof(HttpFunction));
            logger.LogInformation("message logged");
            //</docsnippet_logging>

            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            response.WriteString("Welcome to .NET isolated worker !!");

            return response;
        }
        //</docsnippet_http_trigger>
    }
}

Logging

可使用 ILogger<T>ILogger 实例来写入到日志。 可通过 ILogger<T>ILoggerFactory依赖项注入来获取记录器:

public class MyFunction {
    
    private readonly ILogger<MyFunction> _logger;
    
    public MyFunction(ILogger<MyFunction> logger) {
        _logger = logger;
    }
    
    [Function(nameof(MyFunction))]
    public void Run([BlobTrigger("samples-workitems/{name}", Connection = "")] string myBlob, string name)
    {
        _logger.LogInformation($"C# Blob trigger function Processed blob\n Name: {name} \n Data: {myBlob}");
    }

}

也可以从传递给函数的 FunctionContext 对象获取记录器。 调用 GetLogger<T>GetLogger 方法,并传递一个字符串值,该值是在其中写入日志的类别的名称。 该类别通常是从中写入日志的特定函数的名称。 若要详细了解类别,请参阅有关监视的文章

使用 ILogger<T>ILogger 的方法写入各种日志级别,例如 LogWarningLogError。 若要详细了解日志级别,请参阅有关监视的文章。 可通过注册筛选器,自定义添加到代码中的组件的日志级别:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // Registers IHttpClientFactory.
        // By default this sends a lot of Information-level logs.
        services.AddHttpClient();
    })
    .ConfigureLogging(logging =>
    {
        // Disable IHttpClientFactory Informational logs.
        // Note -- you can also remove the handler that does the logging: https://github.com/aspnet/HttpClientFactory/issues/196#issuecomment-432755765 
        logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning);
    })
    .Build();

作为在 Program.cs 中配置应用的一部分,还可以定义错误如何显示在日志中的行为。 默认行为由所使用的生成器类型决定。

当你使用 RpcException 时,默认情况下,代码引发的异常最终可能会打包在 HostBuilder 中。 要移除此额外层,请将 EnableUserCodeException 属性设置为“true”,作为配置生成器的一部分:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder => {}, options =>
    {
        options.EnableUserCodeException = true;
    })
    .Build();

host.Run();

Application Insights

可配置独立的进程应用程序,使其将日志直接发送到 Application Insights。 此行为取代了通过主机中继日志的默认行为,建议这样做,因为它可让你控制这些日志的发出方式。

安装包

若要直接从代码将日志写入 Application Insights,请在项目中添加对这些包的引用:

可以运行以下命令,将这些引用添加到项目:

dotnet add package Microsoft.ApplicationInsights.WorkerService
dotnet add package Microsoft.Azure.Functions.Worker.ApplicationInsights

配置启动

安装包后,必须在服务配置期间在 Program.cs 文件中调用 AddApplicationInsightsTelemetryWorkerService()ConfigureFunctionsApplicationInsights(),如下例所示:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
    
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
    })
    .Build();

host.Run();

ConfigureFunctionsApplicationInsights() 的调用会添加 ITelemetryModule,它会侦听 Functions 定义的 ActivitySource。 这会创建支持分布式跟踪所需的依赖项遥测数据。 要了解有关 AddApplicationInsightsTelemetryWorkerService() 的更多信息以及如何使用它,请参阅辅助角色服务应用程序的 Application Insights

管理日志级别

重要

Functions 主机和隔离的进程辅助角色对日志级别等有单独的配置。host.json 中的任何 Application Insights 配置都不会影响辅助角色的日志记录,同样,辅助角色代码中的配置也不会影响主机的日志记录。 如果方案需要在两个层进行自定义,则需要在这两个位置应用更改。

应用程序的其余部分继续使用 ILoggerILogger<T>。 但在默认情况下,Application Insights SDK 会添加日志记录筛选器,指示记录器只捕获警告和更严重的日志。 如果要禁用此行为,请将筛选规则作为服务配置的一部分移除:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services => {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
    })
    .ConfigureLogging(logging =>
    {
        logging.Services.Configure<LoggerFilterOptions>(options =>
        {
            LoggerFilterRule defaultRule = options.Rules.FirstOrDefault(rule => rule.ProviderName
                == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider");
            if (defaultRule is not null)
            {
                options.Rules.Remove(defaultRule);
            }
        });
    })
    .Build();

host.Run();

性能优化

本部分概述了可提高冷启动性能的选项。

通常,应用应使用其核心依赖项的最新版本。 至少应按如下所示更新项目:

  1. Microsoft.Azure.Functions.Worker 升级到版本 1.19.0 或更高版本。
  2. Microsoft.Azure.Functions.Worker.Sdk 升级到版本 1.16.4 或更高版本。
  3. 添加对 Microsoft.AspNetCore.App 的框架引用,除非你的应用是面向 .NET Framework。

以下代码片段在项目文件的上下文中显示此配置:

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.4" />
  </ItemGroup>

占位符

占位符是一种平台功能,可改进面向 .NET6 或更高版本的应用程序的冷启动。 若要使用此优化,必须使用以下步骤显式启用占位符:

  1. 更新项目配置以使用最新的依赖项版本,如上一部分所述。

  2. WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED 应用程序设置设为 1,可以使用这个 az functionapp config appsettings set 命令执行此操作:

    az functionapp config appsettings set -g <groupName> -n <appName> --settings 'WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED=1'
    

    在此示例中,将 <groupName> 替换为资源组的名称,将 <appName> 替换为函数应用的名称。

  3. 确保函数应用的 netFrameworkVersion 属性与你的项目的目标框架(必须是 .NET 6 或更高版本)匹配。 可以使用这个 az functionapp config set 命令执行此操作:

    az functionapp config set -g <groupName> -n <appName> --net-framework-version <framework>
    

    在本例中,根据你的目标 .NET 版本,将 <framework> 替换为适当的版本字符串,例如 v8.0

  4. 确保你的函数应用配置为使用 64 位进程,可以使用这个 az functionapp config set 命令执行此操作:

    az functionapp config set -g <groupName> -n <appName> --use-32bit-worker-process false
    

重要

WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED 设置为 1 时,必须正确设置所有其他函数应用配置。 否则,函数应用可能无法启动。

优化的执行程序

函数执行程序是平台的一个组件,它会导致调用运行。 从 SDK 版本 1.16.2 开始,默认启用此组件的优化版本。 无需其他配置。

ReadyToRun

可以将函数应用编译为 ReadyToRun 二进制文件。 ReadyToRun 是一种预先编译形式,可以提高启动性能,帮助降低在消耗计划中运行时的冷启动的影响。 ReadyToRun 在 .NET 6 中提供,并且需要 Azure Functions 运行时版本 4.0 或更高版本

ReadyToRun 要求你根据托管应用的运行时体系结构生成项目。 如果它们不一致,则你的应用将在启动时遇到错误。 从此表中选择运行时标识符:

操作系统 应用为 32 位1 运行时标识符
Windows True win-x86
Windows False win-x64
Linux True N/A(不支持)
Linux False linux-x64

1 只有 64 位应用程序才有资格进行其他一些性能优化。

若要检查你的 Windows 应用是 32 位还是 64 位,可以运行以下 CLI 命令,用你的资源组名称替换 <group_name>,并用你的应用程序名称替换 <app_name>。 输出“true”表示应用为 32 位,“false”表示其为 64 位。

 az functionapp config show -g <group_name> -n <app_name> --query "use32BitWorkerProcess"

可以使用以下命令将应用程序更改为 64 位,并使用相同的替换:

az functionapp config set -g <group_name> -n <app_name> --use-32bit-worker-process false`

若要将项目编译为 ReadyToRun,请通过添加 <PublishReadyToRun><RuntimeIdentifier> 元素来更新项目文件。 以下示例显示的配置与发布到 Windows 64 位函数应用有关。

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

如果不想将 <RuntimeIdentifier> 设置为项目文件的一部分,则也可以将其配置为发布过程本身的一部分。 例如,对于 Windows 64 位函数应用,.NET CLI 命令将为:

dotnet publish --runtime win-x64

在 Visual Studio 中,发布配置文件中的“目标运行时”选项应设置为正确的运行时标识符。 当设置为默认值“可移植”时,不使用 ReadyToRun。

部署到 Azure Functions

将函数代码项目部署到 Azure 时,该项目必须在函数应用或 Linux 容器中运行。 在部署代码之前,函数应用和其他必需的 Azure 资源必须存在。

还可以在 Linux 容器中部署函数应用。 有关详细信息,请参阅使用容器和 Azure Functions

创建 Azure 资源

可以使用以下方法之一在 Azure 中创建函数应用和其他所需资源:

  • Visual Studio:Visual Studio 可以在代码发布过程中为你创建资源。
  • Visual Studio Code:Visual Studio Code 可以连接到你的订阅,创建你的应用所需的资源,然后发布代码。
  • Azure CLI:可以使用 Azure CLI 在 Azure 中创建所需的资源。
  • Azure PowerShell:可以使用 Azure PowerShell 在 Azure 中创建所需的资源。
  • 部署模板:可以使用 ARM 模板和 Bicep 文件自动将所需资源部署到 Azure。 请确保模板包含任何必需的设置
  • Azure 门户:可以在 Azure 门户中创建所需的资源。

发布应用程序

在 Azure 中创建函数应用和其他所需资源后,可以使用以下方法之一将代码项目部署到 Azure:

有关详细信息,请参阅 Azure Functions 中的部署技术

部署有效负载

许多部署方法都使用 zip 存档。 如果要自行创建 zip 存档,必须遵循本部分中概述的结构。 如果没有,应用可能会在启动时遇到错误。

部署有效负载应与 dotnet publish 命令的输出匹配,但不包含封闭父文件夹。 zip 存档应来自以下文件:

  • .azurefunctions/
  • extensions.json
  • functions.metadata
  • host.json
  • worker.config.json
  • 项目可执行文件(控制台应用)
  • 与该可执行文件对等的其他支持文件和目录

这些文件由生成过程生成,不应直接编辑这些文件。

准备 zip 存档进行部署时,应仅压缩输出目录的内容,而不是封闭目录本身。 将存档提取到当前工作目录中时,需要立即看到上面列出的文件。

部署要求

在 Azure 的隔离工作进程模型中运行 .NET 函数有几个要求,具体取决于操作系统:

使用上一部分中的方法在 Azure 中创建函数应用时,系统会为你添加这些必需的设置。 使用 ARM 模板或 Bicep 文件自动化创建这些资源时,必须确保在模板中设置它们。

调试

使用 Visual Studio 或 Visual Studio Code 在本地运行时,可以正常调试 .NET 隔离工作进程项目。 但是,有两种调试方案无法按预期工作。

使用 Visual Studio 进行远程调试

由于独立工作进程应用在 Functions 运行时外部运行,因此你需要将远程调试器附加到单独的进程。 要详细了解如何使用 Visual Studio 进行调试,请参阅远程调试

面向 .NET Framework 时的调试

如果独立项目面向 .NET Framework 4.8,则当前预览版范围需要你执行手动步骤才能启用调试。 如果使用其他目标框架,则不需要执行这些步骤。

应用应首先调用 FunctionsDebugger.Enable(); 作为其第一个操作。 在 Main() 方法中初始化 HostBuilder 之前会发生此操作。 你的 Program.cs 文件应类似于:

using System;
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Functions.Worker;
using NetFxWorker;

namespace MyDotnetFrameworkProject
{
    internal class Program
    {
        static void Main(string[] args)
        {
            FunctionsDebugger.Enable();

            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults()
                .Build();

            host.Run();
        }
    }
}

接下来,需要使用 .NET Framework 调试程序手动附加到进程。 Visual Studio 目前不会自动对独立工作进程 .NET Framework 应用执行此操作,因此应避免“开始调试”操作。

在项目目录(或其生成输出目录)中,运行:

func host start --dotnet-isolated-debug

这会启动工作进程,进程会停止并显示以下消息:

Azure Functions .NET Worker (PID: <process id>) initialized in debug mode. Waiting for debugger to attach...

其中 <process id> 是你的工作进程的 ID。 现在可以使用 Visual Studio 手动附加到该进程。 有关此操作的说明,请参阅如何附加到正在运行的进程

附加调试器后,进程执行将会恢复,你将能够开始调试。

预览 .NET 版本

在正式发布之前,.NET 版本可能会在预览版Go-live 状态下发布。 有关这些状态的详细信息,请参阅 .NET 官方支持策略

虽然可以面向本地 Functions 项目中的给定版本,但 Azure 中托管的函数应用可能没有可用的版本。 Azure Functions 只能用于本部分中所述的预览版或 Go-live 版本。

Azure Functions 当前可用于以下“Preview”或“Go-live”.NET 版本:

操作系统 .NET 预览版
Windows .NET 9 预览版 61, 2
Linux .NET 9 RC21, 3

1 若要成功面向 .NET 9,项目需要引用核心包的 2.x 版本。 如果使用的是 Visual Studio,NET 9 需要版本 17.12 或更高版本。

2 在预览版期间,一些客户端中可能没有对 Windows 的支持。

3 Flex 消耗 SKU 尚不支持 .NET 9。

有关你可以使用的正式发布版本的列表,请参阅支持的版本

使用预览版 .NET SDK

要将 Azure Functions 与 .NET 预览版配合使用,需要通过以下方式更新项目:

  1. 在开发中安装相关的 .NET SDK 版本
  2. 更改 .csproj 文件中的 TargetFramework 设置

部署到 Azure 中的函数应用时,还需要确保该框架可供应用使用。 在预览期间,某些工具和体验可能无法将新的预览版本显示为选项。 例如,如果在 Azure 门户中没有看到预览版本,可以使用 REST API、Bicep 模板或 Azure CLI 手动配置该版本。

对于在 Windows 上托管的应用,请使用以下 Azure CLI 命令。 将 <groupName> 替换为资源组的名称,将 <appName> 替换为函数应用的名称。 将 <framework> 替换为相应的版本字符串,例如 v8.0

az functionapp config set -g <groupName> -n <appName> --net-framework-version <framework>

使用 .NET 预览版的注意事项

将 Functions 与 .NET 预览版配合使用时,请记住以下注意事项:

  • 在 Visual Studio 中创作函数时,必须使用 Visual Studio Preview,它支持使用 .NET 预览版 SDK 生成 Azure Functions 项目。

  • 请确保你拥有最新的 Functions 工具和模板。 若要更新工具:

    1. 导航到“工具”>“选项”,在“项目和解决方案”下选择“Azure Functions”。
    2. 选择“检查更新”并按提示安装更新。
  • 在预览期间,你的开发环境可能具有版本比托管服务更新的 .NET 预览版。 这可能会导致函数应用在部署时失败。 若要解决此问题,可以指定要在 global.json 中使用的 SDK 版本。

    1. 运行 dotnet --list-sdks 命令并记下你当前在本地开发期间使用的预览版本。
    2. 运行 dotnet new globaljson --sdk-version <SDK_VERSION> --force 命令,其中 <SDK_VERSION> 是你本地使用的版本。 例如,生成项目时,dotnet new globaljson --sdk-version dotnet-sdk-8.0.100-preview.7.23376.3 --force 会导致系统使用 .NET 8 预览版 7 SDK。

注意

由于预览框架的即时加载,与早期 GA 版本相比,在 Windows 上运行的函数应用经历的冷启动时间可能会增加。

后续步骤