You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让C#编写的Azure Function App更符合DRY原则?

解决方案

一、用元编程(Source Generator)自动生成重复类

如果想保留编译时的Function注册逻辑,可以通过C# Source Generator自动生成多个重复的Function类,避免手动编写冗余代码:

  1. 定义标记属性(用于指定要生成的配置名称):
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
public class GenerateTaskEventGeneratorAttribute : Attribute
{
    public string ConfigName { get; }
    public GenerateTaskEventGeneratorAttribute(string configName)
    {
        ConfigName = configName;
    }
}
  1. 实现Source Generator,扫描配置名称并生成对应类:
[Generator]
public class TaskEventGeneratorSourceGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
        // 这里可以从常量类或配置文件读取所有需要生成的配置名称
        var configNames = new[] { "InitialUpload", "DailySync", "HourlyScan" };

        foreach (var configName in configNames)
        {
            var className = $"{configName}NewOrModifiedFileTaskEventGenerator";
            var functionName = $"GenerateFor{configName}";

            var sourceCode = $$"""
            using System;
            using System.Threading;
            using System.Threading.Tasks;
            using Microsoft.Azure.WebJobs;
            using YourProjectNamespace;

            public class {{className}}
            {
                private readonly TaskEventGeneratorConfig _taskEventGeneratorConfig;
                private readonly NewOrModifiedFileTaskEventGeneratorHelper _taskEventGeneratorHelper;

                private const string _configName = "{{configName}}";

                public {{className}}(
                        ITaskEventGeneratorConfigFactory taskEventGeneratorConfigFactory,
                        NewOrModifiedFileTaskEventGeneratorHelper taskEventGeneratorHelper
                    )
                {
                    _taskEventGeneratorConfig = Guard.Against.Null(taskEventGeneratorConfigFactory)
                        .GetConfig(_configName);
                    _taskEventGeneratorHelper = Guard.Against.Null(taskEventGeneratorHelper);
                }

                [FunctionName("{{functionName}}")]
                [SuppressMessage("Roslynator", "RCS1163:Unused parameter.", Justification = "Used for Azure Function")]
                public async Task Generate(
                    [TimerTrigger($"%{{_configName}}:DirectoryScanFrequencyCron%")] TimerInfo timer,
                    CancellationToken cancellationToken
                ) =>
                    await _taskEventGeneratorHelper.Generate(_taskEventGeneratorConfig, cancellationToken);
            }
            """;

            context.AddSource($"{className}.g.cs", sourceCode);
        }
    }
}

编译时会自动生成所有对应配置名称的Function类,无需手动编写重复代码。

二、进阶:动态注册定时器函数,彻底摆脱硬编码

如果想完全通过配置驱动,不用硬编码任何配置名称,可以借助Azure Functions的动态作业注册功能:

  1. 修改appsettings.json为数组配置:
{
  "TaskEventGenerators": [
    {
      "ConfigName": "InitialUpload",
      "DirectoryScanFrequencyCron": "0 */5 * * * *",
      // 其他配置字段
    },
    {
      "ConfigName": "DailySync",
      "DirectoryScanFrequencyCron": "0 0 0 * * *",
      // 其他配置字段
    }
  ]
}
  1. 创建配置模型:
public class TaskEventGeneratorConfigItem
{
    public string ConfigName { get; set; }
    public string DirectoryScanFrequencyCron { get; set; }
    // 补充其他需要的配置属性
}
  1. 在Program.cs中动态注册定时器:
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices((context, services) =>
    {
        // 绑定配置数组
        var configItems = context.Configuration.GetSection("TaskEventGenerators")
            .Get<List<TaskEventGeneratorConfigItem>>();
        
        services.AddSingleton(configItems);
        services.AddSingleton<NewOrModifiedFileTaskEventGeneratorHelper>();
        services.AddSingleton<ITaskEventGeneratorConfigFactory, TaskEventGeneratorConfigFactory>();
    })
    .Build();

// 遍历配置,动态注册每个定时器函数
var configItems = host.Services.GetRequiredService<List<TaskEventGeneratorConfigItem>>();
var helper = host.Services.GetRequiredService<NewOrModifiedFileTaskEventGeneratorHelper>();
var configFactory = host.Services.GetRequiredService<ITaskEventGeneratorConfigFactory>();

foreach (var item in configItems)
{
    var config = configFactory.GetConfig(item.ConfigName);
    
    host.RegisterTimerFunction(
        functionName: $"GenerateFor{item.ConfigName}",
        cronExpression: item.DirectoryScanFrequencyCron,
        executeAsync: async (timerInfo, cancellationToken) =>
        {
            await helper.Generate(config, cancellationToken);
        }
    );
}

await host.RunAsync();

注:RegisterTimerFunction是Azure Functions Worker SDK的扩展方法,需确保使用最新版本的Microsoft.Azure.Functions.Worker包。

这种方式下,新增配置只需修改appsettings.json,无需改动任何代码,完全实现配置驱动。

两种方案对比

  • Source Generator方案:保留编译时Function注册,适合需要静态检查Function存在性的场景;
  • 动态注册方案:灵活性更高,适合配置频繁变动的场景,彻底消除硬编码。

内容的提问来源于stack exchange,提问作者Brian Kessler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:22:08