Azure WebJobs SDK 3, trying to add a BlobTrigger a

2019-05-20 17:56发布

问题:

The old way of doing things looked as so:

        var jobConfig = new JobHostConfiguration(cfg.DocumentDatabase.BlobStorageServer)
        {
            NameResolver = new Support.BlobNameResolver(_env)
        };
        jobConfig.Queues.MaxPollingInterval = TimeSpan.FromSeconds(_pollSeconds);

        _wjHost = new JobHost(jobConfig);

I am trying to translate this to the new way in 3.0, and this is how far I have come:

        _wjHost = new HostBuilder().ConfigureWebJobs(b =>
        {
            b.AddAzureStorage(x =>
            {
                x.MaxPollingInterval = TimeSpan.FromSeconds(_pollSeconds);
            });
        }).ConfigureServices(s =>
        {
            s.AddSingleton<INameResolver, Support.BlobNameResolver>(_ => new Support.BlobNameResolver(_env));
            s.Configure<QueuesOptions>(o =>
            {
                o.MaxPollingInterval = TimeSpan.FromSeconds(_pollSeconds);
            });
        }).Build();

Firstly, i don't know which MaxPollingInterval is the right one to use... so i set both. I assume i shouldn't be using the one in AddBlobStorage

Secondly, and more importantly, where do I specify the blob storage connection string? In the case above, it's the setting stored in cfg.DocumentDatabase.BlobStorageServer

Thanks

回答1:

Official sample available at WebJob Github

In your Functions, you can pass the connection string key name used in appsettings.json

ex:

public void ProcessBlob([BlobTrigger("blobPath", Connection = "AzureWebJobsBlobConnection")] string blob)

the "AzureWebJobsBlobConnection" is configured in appsettings.json as follows: { "Logging": { ... }, "AzureWebJobsBlobConnection": "...", }

And do not forget to add the configuration in program.cs:

var builder = new HostBuilder()
            .ConfigureAppConfiguration((builderContext, cb) =>
            {
                IHostingEnvironment env = builderContext.HostingEnvironment;

                cb.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
            })
            .ConfigureWebJobs(b =>
            {
                b.AddAzureStorage(o => 
                {
                    o.MaxDequeueCount = 1;
                })
                .AddServiceBus(c =>
                {
                    c.MessageHandlerOptions.MaxConcurrentCalls = 1;
                });
            })
            .ConfigureLogging((webHostBuilder, loggingBuilder) =>
            {
                loggingBuilder.AddConsole();
                loggingBuilder.AddDebug();
            })
            .ConfigureServices((hb, sc) =>
            {
                string connectionString = hb.Configuration.GetConnectionString("DefaultConnection");

                sc.AddScoped<Functions, Functions>();
                ...
            });

        builder.RunConsoleAsync().GetAwaiter().GetResult();


回答2:

So, after staring at the source code for the webjob SDK, I found a kludge. Well, I think it's a kludge. It works and I can now use the new 3.0 SDK.

I am posting this here, mainly because I fear there is no other way to do this using my own configuration files.

If this is wrong, please just let me know and I will delete this answer.

So my code now looks like this:

    _wjHost = new HostBuilder().ConfigureWebJobs(b =>
    {
        b.AddAzureStorage(x =>
        {
            x.MaxPollingInterval = TimeSpan.FromSeconds(_pollSeconds);
        });
    }).ConfigureServices(s =>
    {
        s.AddSingleton(new StorageAccountProvider(new BlobStorageConfiguration(cfg.DocumentDatabase.BlobStorageServer)));
        s.AddSingleton<INameResolver, Support.BlobNameResolver>(_ => new Support.BlobNameResolver(_env));
        s.Configure<QueuesOptions>(o =>
        {
            o.MaxPollingInterval = TimeSpan.FromSeconds(_pollSeconds);
        });
    }).Build();

The line I added was s.AddSingleton(new StorageAccountProvider(new BlobStorageConfiguration(cfg.DocumentDatabase.BlobStorageServer)));

The webjobs SDK is specifically looking for a key named Storage. So I had to implement IConfiguration and kludge this in as so:

private sealed class BlobStorageConfiguration : IConfiguration
{
    private readonly string _bsConnString;
    public BlobStorageConfiguration(string connString)
    {
        _bsConnString = connString;
    }

    public string this[string key]
    {
        get => key == "Storage" ? _bsConnString : null;
        set { }
    }

    public IEnumerable<IConfigurationSection> GetChildren() => null;
    public IChangeToken GetReloadToken() => null;
    public IConfigurationSection GetSection(string key) => null;
}

and now the trigger is firing just fine. Not pretty. But there is ZERO documentation on the new IHost methods.



回答3:

In the WebSDK 3, the job is configured via the appsettings.json file, the same way it's done for ASP.NET Core.

You can see an example on the official Github repo, but I'll also add a more complete example below.

In appsettings.development.json, configure it to use the local storage, via the ConnectionStrings["AzureWebJobsStorage"] property:

{
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "UseDevelopmentStorage=true",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}

And in appsettings.json, configure it for prod:

{
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=name;AccountKey=key",
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=name;AccountKey=key"
  }
}

My complete Program.cs looks like this (with comments):

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = new HostBuilder();

        // allows us to read the configuration file from current directory
        // (remember to copy those files to the OutputDirectory in VS)
        builder.UseContentRoot(Directory.GetCurrentDirectory());

        // configure things like batch size, service bus, etc..
        builder.ConfigureWebJobs(b =>
        {
            b
            .AddAzureStorageCoreServices()
            .AddAzureStorage(options =>
            {
                options.BatchSize = 1;
                options.MaxDequeueCount = 1;
            })
            ;
        });

        // this step allows the env variable to be read BEFORE the rest of the configuration
        // => this is useful to configure the hosting environment in debug, by setting the 
        // ENVIRONMENT variable in VS
        builder.ConfigureHostConfiguration(config =>
        {
            config.AddEnvironmentVariables();
        });

        // reads the configuration from json file
        builder.ConfigureAppConfiguration((context, config) =>
        {
            var env = context.HostingEnvironment;

            // Adding command line as a configuration source
            config
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);

            config.AddEnvironmentVariables();
            if (args != null)
                config.AddCommandLine(args);
        });

        // configure logging (you can use the config here, via context.Configuration)
        builder.ConfigureLogging((context, loggingBuilder) =>
        {
            loggingBuilder.AddConfiguration(context.Configuration.GetSection("Logging"));
            loggingBuilder.AddConsole();

            // If this key exists in any config, use it to enable App Insights
            var appInsightsKey = context.Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
            if (!string.IsNullOrEmpty(appInsightsKey))
            {
                loggingBuilder.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
            }
        });

        // inject dependencies via DI
        builder.ConfigureServices((context, services) =>
        {
            services.AddSingleton<INameResolver>(new QueueNameResolver("test"));

            services.AddDbContextPool<DbContext>(options =>
                options.UseSqlServer(context.Configuration.GetConnectionString("DbContext"))
            );
        });

        // finalize host config
        builder.UseConsoleLifetime();

        var host = builder.Build();
        using (host)
        {
            await host.RunAsync();
        }
    }
}


回答4:

Just in case someone came to the same situation I had with resolving the queue name when you use QueueTrigger:

You have to use " Microsoft.Extensions.DependencyInjection " package for the below code to work.

static void Main(string[] args)
    {
        var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        Console.WriteLine($"Current Environment : {(string.IsNullOrEmpty(environment) ? "Development" : environment)}");

        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        var builder = new HostBuilder();
        builder.UseEnvironment("Development");
        builder.ConfigureLogging((context, b) =>
        {
            b.AddConsole();
        });

        builder.ConfigureWebJobs(b =>
        {

            b.AddAzureStorageCoreServices();
            b.AddAzureStorage();
            // b.AddTimers();

            b.AddServiceBus(c =>
            {
                c.ConnectionString = "[Your Connection String]";
            });
        }).ConfigureServices((context, services)=>
        {
            services.AddSingleton<INameResolver>(new QueueNameResolver(config));
        });

        var host = builder.Build();


        using (host)
        {
            host.Run();
        }
    }

public class QueueNameResolver : INameResolver
{
    private readonly IConfiguration _configuration;
    public QueueNameResolver(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    public string Resolve(string name)
    {
        return _configuration[$"AppSettings:{name}"];
    }
}