Azure Functions should allow use of 3rd party IoC containers, e.g. Castle.Windsor.
Could this be implemented via a virtual method in Microsoft.Azure.Functions.Extensions.DependencyInjection.FunctionsStartup which returns an instance of IServiceProvider? This could be overridden to configure the IoC container. e.g for Castle.Windsor we could do something like this:
```c#
public override IServiceProvider Configure(IFunctionsHostBuilder builder)
{
var container = new WindsorContainer();
// configure container here ...
container.AddServices(builder.Services);
return container.Resolve<IServiceProvider>();
}
```
Closely following this. We have an application which has dotnet core API project and a bunch of v2 functions. API uses a hybrid (Autofac + Microsoft) IServieProvider as we have some simple DI registrations and some complex DI registrations that Microsoft's DI doesn't support (e.g. named instances). All the functions app uses a custom [Inject] attribute based hacked DI container that kind of supports reuse of the registrations used by the API.
In order for us to truly switch to Functions' new first class DI support, we need the same support provided by dotnet core web projects added to the Functions project as well.
This is a feature request.
@brettsam / @fabiocav - Do we have plans to support other IoC containers?
This is a feature request.
@brettsam / @fabiocav - Do we have plans to support other IoC containers?
I can't even understand why this can be a question!
This is a feature request.
@brettsam / @fabiocav - Do we have plans to support other IoC containers?I can't even understand why this can be a question!
@janus007 I don't understand your remark. Please can you clarify.
I think I have a solution, using an implementation of IJobActivatorEx based on this thread: https://github.com/Azure/azure-webjobs-sdk/issues/1915.
This example is for Castle.Windsor
The IJobActivatorEx implementation:
```c#
public class CastleWindsorJobActivator : IJobActivatorEx
{
private readonly WindsorContainer container;
public CastleWindsorJobActivator(WindsorContainer container) => this.container = container;
public T CreateInstance<T>(IFunctionInstanceEx functionInstance)
{
var disposer = functionInstance.InstanceServices.GetRequiredService<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
// Ensures a created Castle.Windsor scope is disposed at the end of the request
public sealed class ScopeDisposable : IDisposable
{
public IDisposable Scope { get; set; }
public void Dispose() => this.Scope?.Dispose();
}
public T CreateInstance<T>()
{
var disposer = container.Resolve<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
}
Startup class:
```c#
[assembly: FunctionsStartup(typeof(FunctionApp2.Startup))]
namespace FunctionApp2
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var container = new WindsorContainer();
container.Register(Component.For<IScoped1>().ImplementedBy<Scoped1>().LifestyleScoped())
.Register(Component.For<IScoped2>().ImplementedBy<Scoped2>().LifestyleScoped())
.Register(Component.For<ISingleton1>().ImplementedBy<Singleton1>())
.Register(Component.For<ISingleton2>().ImplementedBy<Singleton2>());
// register function classes in container
var functions = Assembly.GetExecutingAssembly().GetTypes().Where(t =>
t.GetMethods().Any(m => m.GetCustomAttributes(typeof(FunctionNameAttribute), false).Any()));
foreach (var function in functions)
{
container.Register(Component.For(function).LifestyleScoped());
}
builder.Services.AddScoped<CastleWindsorJobActivator.ScopeDisposable>()
.AddSingleton<IJobActivator>(new CastleWindsorJobActivator(container));
container.AddServices(builder.Services);
}
}
}
Example function:
```c#
public class Function1
{
public Function1(IScoped1 scoped1, IScoped2 scoped2, ISingleton1 singleton1, ISingleton2 singleton2)
{
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
```
Hi @DavidJFowler, I am having issues using Microsoft.Extensions.Logging.ILogger in the solution you proposed. I am getting this exception
Can't create component 'Microsoft.Extensions.Logging.ILoggerProvider_30cdb896-2dd1-40ea-a323-bc41a1c63a1d' as it has dependencies to be satisfied.
'Microsoft.Extensions.Logging.ILoggerProvider_30cdb896-2dd1-40ea-a323-bc41a1c63a1d' is waiting for the following dependencies:
- Service 'Microsoft.Azure.WebJobs.Script.IFileLoggingStatusManager' which was not registered.
- Service 'Microsoft.Azure.WebJobs.Script.IPrimaryHostStateProvider' which was not registered.
Will you be able to help please ?
I have created a sample here
Hi @DavidJFowler, I am having issues using Microsoft.Extensions.Logging.ILogger in the solution you proposed. I am getting this exception
Can't create component 'Microsoft.Extensions.Logging.ILoggerProvider_30cdb896-2dd1-40ea-a323-bc41a1c63a1d' as it has dependencies to be satisfied. 'Microsoft.Extensions.Logging.ILoggerProvider_30cdb896-2dd1-40ea-a323-bc41a1c63a1d' is waiting for the following dependencies: - Service 'Microsoft.Azure.WebJobs.Script.IFileLoggingStatusManager' which was not registered. - Service 'Microsoft.Azure.WebJobs.Script.IPrimaryHostStateProvider' which was not registered.Will you be able to help please ?
I have created a sample here
Hi @harmandeol ,
I didn't attempt to inject ILogger<T> into any of my classes. I would suggest that you pass the ILogger instance from the function method into any methods on your injected classes to handle logging, e.g.
```c#
public class Function1
{
IScoped1 _scoped1;
public Function1(IScoped1 scoped1)
{
_scoped1 = scoped1;
}
[FunctionName("Function1")]
public void Run([TimerTrigger("0 */5 * * * *",RunOnStartup = true)]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
_scoped1.MyMethod(log);
}
}
```
@DavidJFowler this usage is part of larger framework already using ILogger. I added this sample to understand if there can be workaround. Looks like untill it is properly supported as you suggested above, I will have to stick with webjob.
This is a feature request.
@brettsam / @fabiocav - Do we have plans to support other IoC containers?I can't even understand why this can be a question!
@janus007 I don't understand your remark. Please can you clarify.
Because every framework that Microsoft develop should have the option to use whatever DI one would prefer, which is the case in 95% of the occurrences.
Trying to make Azure Functions without the possibility to change DI, will kill Azure Function faster than a....
I'm sure everyone will agree on that, and that was why I said "I can't even understand why this can be a question!"
Positive vibes 馃拑
Cheers
@DavidJFowler this usage is part of larger framework already using ILogger. I added this sample to understand if there can be workaround. Looks like untill it is properly supported as you suggested above, I will have to stick with webjob.
Hi @harmandeol ,
I use Autofac in the Azure Functions and instead of the limited ILogger, we use TelemetryClient instead, which has all the trace, customevent, metrics and exceptions that we need :)
@janus007 this is not a feature we intend to support in the hosted Azure Functions service.
The support for Azure Functions as an application framework/component is somewhat limited today, but we do make those packages available and that does give you some more flexibility to modify things like the DI framework used by the app. Do keep in mind that, once you take this route, the hosting options change, as you'd be running your own runtime.
Can you also expand you what you've mentioned above? The types of services you can consume are not tied to the DI framework used, so it would be good to have more clarity on your use case and the blockers you're running into.
@DavidJFowler thank you for the suggestion. This is something we may re-evaluate in the future, but to accurately reflect the current plans, I'll close this for now.
@harmandeol the issue you're running into is not with the out-of-box DI features in Azure Funtions (replacing the IJobActivator is not a supported scenario), but if you feel you need some help with your requirements, please open a separate issue with those details and we can assist.
Thanks again for the suggestions and discussion!
Most helpful comment
Because every framework that Microsoft develop should have the option to use whatever DI one would prefer, which is the case in 95% of the occurrences.
Trying to make Azure Functions without the possibility to change DI, will kill Azure Function faster than a....
I'm sure everyone will agree on that, and that was why I said "I can't even understand why this can be a question!"
Positive vibes 馃拑
Cheers