Dependency injection using Azure WebJobs SDK?
Azure WebJobs SDK now supports instance methods. Combining this with a custom IJobActivator allows you to use DI.
First, create the custom IJobActivator that can resolve a job type using your favourite DI container:
public class MyActivator : IJobActivator
{
private readonly IUnityContainer _container;
public MyActivator(IUnityContainer container)
{
_container = container;
}
public T CreateInstance<T>()
{
return _container.Resolve<T>();
}
}
You need to register this class using a custom JobHostConfiguration:
var config = new JobHostConfiguration
{
JobActivator = new MyActivator(myContainer)
};
var host = new JobHost(config);
Then, you can use a simple class with instance methods for your jobs (here I'm using Unity's constructor injection feature):
public class MyFunctions
{
private readonly ISomeDependency _dependency;
public MyFunctions(ISomeDependency dependency)
{
_dependency = dependency;
}
public Task DoStuffAsync([QueueTrigger("queue")] string message)
{
Console.WriteLine("Injected dependency: {0}", _dependency);
return Task.FromResult(true);
}
}
ASP.Net core Azure Webjob (SDK 3.0.10) DI not resolving
With your current design the host is unaware of the service registrations that were done in main.
Here is a simplification of the current setup based on what was provided in the original example:
class Program {
static void Main(string[] args) {
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
var azureQueueOptions = configuration.GetSection("AzureQueueOptions").Get<AzureQueueOptions>();
azureQueueOptions.StorageConnectionString = configuration.GetConnectionString("Storage");
var builder = new HostBuilder()
.ConfigureWebJobs(webJobConfiguration => {
webJobConfiguration.AddAzureStorageCoreServices();
webJobConfiguration.AddAzureStorage(c => c.BatchSize = azureQueueOptions.BatchSize);
})
.ConfigureServices(services => {
services.AddTransient<Functions>();
services.AddTransient<ICloudStorageService, AzureBlobStorageService>();
})
.ConfigureLogging((context, config) => {
config.AddConsole();
});
var host = builder.Build();
host.Run();
}
}
That or create an actual Startup
and have the builder use it:
var builder = new HostBuilder()
.UseStartup<Startup>()
//...omitted for brevity
Dependency Injection in Azure WebJobs SDK when using NoAutomaticTrigger?
The WebJobs SDK now supports instance methods to be triggered instead of only static methods. The NoAutomaticTrigger method don't need to be static anymore. Update the SDK, use the custom IJobActivator approach you found and remember to register you Functions class and of course your other dependencies.
Example Program class:
class Program
{
static void Main()
{
var container = new UnityContainer();
//the instance to be injected
var systemClient = new JobSystemClient
{
UserName = "admin",
PassWord = "admin1234"
};
container.RegisterInstance<ISystemClient>(systemClient);
//Registration of the Functions class
container.RegisterType<Functions>();
var activator = new UnityJobActivator(container);
var config = new JobHostConfiguration();
config.JobActivator = activator;
var host = new JobHost(config);
// The following code will invoke a function called ManualTrigger and
// pass in data (value in this case) to the function
host.Call(typeof(Functions).GetMethod("ManualTrigger"), new { value = 20 });
host.RunAndBlock();
}
}
sample Function class:
public class Functions
{
private readonly ISystemClient _systemClient;
public Functions(ISystemClient systemClient)
{
_systemClient = systemClient;
}
//Not static anymore
[NoAutomaticTrigger]
public void ManualTrigger(TextWriter log, int value, [Queue("queue")] out string message)
{
log.WriteLine("Function is invoked with value={0}", value);
message = value.ToString();
log.WriteLine("username:{0} and password:{1}", _systemClient.UserName, _systemClient.PassWord);
}
}
and here is the output:
Found the following functions:
TestWebJob.Functions.ManualTrigger
Executing 'Functions.ManualTrigger' (Reason='This function was programmatically called via the host APIs.', Id=bf9aedc0-89d1-4ba0-a33e-9b23e0d7b8a2)
Function is invoked with value=20
Following message will be written on the Queue=20
username:admin and password:admin1234
Executed 'Functions.ManualTrigger' (Succeeded, Id=bf9aedc0-89d1-4ba0-a33e-9b23e0d7b8a2)
Job host started
Webjobs Method Indexing - Dependency Injection Exception IConfiguration
Instead of using static functions and parameter injection, you can use a full class and constructor injection.
services.AddScoped<Functions>()
services.AddSingleton(context.Configuration);
...
public class Functions
{
private readonly IConfiguration _Configuration;
public Functions(IConfiguration configuration)
{
_Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public void ProcessQueueMessage([QueueTrigger("queuename")] CloudQueueMessage queueMessage,
TextWriter log)
{
//
}
}
Azure Triggered Webjobs Scope for Dependency Injection
I've opened an request Add IDependencyScope to handle scoping to the Azure Webjob team.
I've create a small library to gather classes around Azure Webjobs and SimpleInjector :
- Nuget download
- GitHub project
For QueueTrigger and ServiceBustrigger, I've come accross these solutions :
ServiceBusTrigger (from this answer: https://stackoverflow.com/a/33759649/4167200):
public sealed class ScopedMessagingProvider : MessagingProvider
{
private readonly ServiceBusConfiguration _config;
private readonly Container _container;
public ScopedMessagingProvider(ServiceBusConfiguration config, Container container)
: base(config)
{
_config = config;
_container = container;
}
public override MessageProcessor CreateMessageProcessor(string entityPath)
{
return new ScopedMessageProcessor(_config.MessageOptions, _container);
}
private class ScopedMessageProcessor : MessageProcessor
{
private readonly Container _container;
public ScopedMessageProcessor(OnMessageOptions messageOptions, Container container)
: base(messageOptions)
{
_container = container;
}
public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
{
_container.BeginExecutionContextScope();
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
{
_container.GetCurrentExecutionContextScope()?.Dispose();
return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
}
}
}You can use your custom MessagingProvider in your JobHostConfiguration like
var serviceBusConfig = new ServiceBusConfiguration
{
ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);QueueTrigger:
public sealed class ScopedQueueProcessorFactory : IQueueProcessorFactory
{
private readonly Container _container;
public ScopedQueueProcessorFactory(Container container)
{
_container = container;
}
public QueueProcessor Create(QueueProcessorFactoryContext context)
{
return new ScopedQueueProcessor(context, _container);
}
private class ScopedQueueProcessor : QueueProcessor
{
private readonly Container _container;
public ScopedQueueProcessor(QueueProcessorFactoryContext context, Container container)
: base(context)
{
_container = container;
}
public override Task<bool> BeginProcessingMessageAsync(CloudQueueMessage message, CancellationToken cancellationToken)
{
_container.BeginExecutionContextScope();
return base.BeginProcessingMessageAsync(message, cancellationToken);
}
public override Task CompleteProcessingMessageAsync(CloudQueueMessage message, FunctionResult result,
CancellationToken cancellationToken)
{
_container.GetCurrentExecutionContextScope()?.Dispose();
return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
}
}
}You can use your custom IQueueProcessorFactory in your JobHostConfiguration like this:
var config = new JobHostConfiguration();
config.Queues.QueueProcessorFactory = new ScopedQueueProcessorFactory(container);
Azure WebJob and wiring up IServiceCollecton from Microsoft.Extensions.DependencyInjection
Still wasn't able to find much after searching around last night.
But after a bit of fiddling, I managed to get something working with the following:
EDIT: I've added a more complete solution with Entity Framework.
I should note that my ASP.Net Core webapp is built upon 4.6.2 instead of pure core.
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.ServiceBus;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
namespace Settlements.WebJob
{
public class ServiceJobActivator : IJobActivator
{
IServiceProvider _serviceProvider;
public ServiceJobActivator(IServiceCollection serviceCollection) : base()
{
_serviceProvider = serviceCollection.BuildServiceProvider();
}
public T CreateInstance<T>()
{
return _serviceProvider.GetRequiredService<T>();
}
}
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
var dbConnectionString = Properties.Settings.Default.DefaultConnection;
var serviceCollection = new ServiceCollection();
// wire up your services
serviceCollection.AddTransient<IThing, Thing>();
// important! wire up your actual jobs, too
serviceCollection.AddTransient<ServiceBusJobListener>();
// added example to connect EF
serviceCollection.AddDbContext<DbContext>(options =>
options.UseSqlServer(dbConnectionString ));
// add it to a JobHostConfiguration
config.JobActivator = new ServiceJobActivator(serviceCollection);
var host = new JobHost(config);
host.RunAndBlock();
}
}
}
Related Topics
Windows 7 and Vista Uac - Programmatically Requesting Elevation in C#
No Console Output When Using Allocconsole and Target Architecture X86
.Net 4.0 and the Dreaded Onuserpreferencechanged Hang
How to Execute Task in the Wpf Background While Able to Provide Report and Allow Cancellation
How to Change the Background Color of All Other Forms from One Form
Why Func<T,Bool> Instead of Predicate<T>
Creating a Very Simple Linked List
Detecting Network Connection Speed and Bandwidth Usage in C#
Check If the Current User Is Administrator
What Is the Fastest Way to Combine Two Xml Files into One
How to Ignore Null Values for All Source Members During Mapping in Automapper 6
How to Delete a File Which Is Locked by Another Process in C#
Graphics.Drawstring VS Textrenderer.Drawtextwhich Can Deliver Better Quality
What Is Imex Within Oledb Connection Strings