Azure / azure-functions-host

The host/runtime that powers Azure Functions

Home Page:https://functions.azure.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dependency Injection support for Functions

fabiocav opened this issue · comments

Meta issue to track exposing first class DI support to end-user functions.

High level list of work items (issue links pending):

  • Support scoped services
  • Port changes to enable instance methods
  • Proper extensibility model for Functions users: clear extensibility model that does not rely on IWebJobsBuilder and uses the correct scope.
  • Binding extension to support service injection attribute (similar to FromServicesAttribute]
  • Documentation

Related PR: #2389. I was actually planning on getting to this work soon, because even with the level of DI support we already have, supporting instance methods would allow people to take services on job class construction, would enable instance based function filter implementations, etc.

Scopes services would be particularly useful. We have had some grumbling on one of our channels about the lack of support for that.

@fabiocav 👋 G'day! I noticed that this issue was re-assigned from S38 -> Triaged. Is there any info you/the AF team can say about:

  • the priority of this issue?
  • rough quarter-ETA of this issue? e.g. hopefully proper DI will be in, in Q2 2019? Q1 2019, etc?

@PureKrome Azure Functions Live - Dec 2018 https://youtu.be/Ti_QEeGmRSo?t=1661

As @espray mention... Attribute Binding is good way to inject services at Functions scope... IMHO a DI maybe make Functions too "weight"... I see Azure Functions a "light-weight" way to make APIs, Serverless and Microservices apps...

Azure Functions Live - Dec 2018 https://youtu.be/Ti_QEeGmRSo?t=1661

OK - at the 28:15m mark they start talking about DI (and even reference this GH Issue!)
TL;DR;

Q: what's the update on this DI stuff?
A: This is still extremely high. We addressed this issue on our last webcast. Unfortunately that ambious goal in the commitment ... we're not going to hit this and the end of this year. Looking at early next year ... i'm hesitant to say ... the first few months of next year we should have DI for c# developers. It was in this sprint ... it's not just blocked by my docs (documentation) absolutely something that's important and a priority.

Attribute Binding is good way to inject services at Functions scope...

That's where I tend to disagree with (at the current time). I've always looked at Attribute Injection as a BadThing ™️ / code smell / hack. Here's some more info to help explain why (for some people like me) cringe at them.

I just felt that there are better ways to do this and those ways should be explored/promoted.

That's where I tend to disagree with (at the current time). I've always looked at Attribute Injection as a BadThing ™️ / code smell / hack. Here's some more info to help explain why (for some people like me) cringe at them.

@PureKrome the blog post have good points but need some updates and tests... Last interaction on content (not comments) is from 2015...

I just felt that there are better ways to do this and those ways should be explored/promoted

I'm totally agree and I don't see Attribute Binding at Azure Functions context only just a DI hack...

As I said, I think put DI at Function is extra weight in a light-weight service...

Inject using Atribute is "sucks" and need more tests... And better ways can be explored/promoted... Maybe DI is the best...

I know that this isn't quite ready yet, but since Instance methods are now supported with the recent deployment I figured I'd give it a try. From what I can tell it seems to be working quite well already, except for scoped services.

In my project I added a Startup class, that implements IWebJobsStartup and I added the WebJobsStartup assembly level attribute. I also had to switch the target framework to netstandard2.0 to make that work due to #3731, but after that I was able to register services using builder.Services.AddSingleton(), etc.

I also added some scoped services and I added an IServiceScopeFactory parameter to the constructor of the class holding my Azure functions. I then used the CreateScope method to build a scope and then request services from it. However, it seems that the scoped services somehow conflict with how the Azure Functions host is working, since I seem to be getting new instances every time, even within the same scope. Again, I know this isn't quite finished, but this wasn't quite what I was expecting to happen.

Using [Inject] in Functions makes tests dependent on Injection code.
As you have to setup the function before calling it from test.

Currently, I'm using HostBuilder:

In Func App:

    var host = new HostConfigurator()
            .Inject<ServiceToInject>(context);

In Extensions, adding that service:

        public static IHost Setup<T>(this HostConfigurator di, ExecutionContext context)
            where T : class
        {
            var builder = di.BuildHost(context);

            var host = builder.ConfigureServices((hostContext, services) =>
                {
                    services.AddScoped<T>();
                })
                .ConfigureData()
                .Build();

            return host;
        }

Configuring all dependencies, Startup-like, common place:


public static class HostBuilderExtensions
    {
        public static IHostBuilder ConfigureData(this IHostBuilder builder)
        {
            builder.ConfigureServices((hostContext, services) =>
            {
                // here to setup all deps: efcore, options, etc;
            });

            return builder;
        }
    }

And Host Start, run some validation before start:

      public static T StartService<T>(this IHost host)
        {
            // Init code before Start, e.g. AutoMapper validation

            host.Start();
            var service = host.Services.GetService<T>();
            return service;
         }

Definitely, some kind of Startup will save the day, but placing Injection into arguments of the function might not be the best solution.

This one was written before, https://github.com/BorisWilhelms/azure-function-dependency-injection

It

Any updates on this issue? I've been trying to get ASP.NET Core Health Checks to work within an Azure Function app so that I can provide some visibility into whether my app is working correctly. That sort of kinda works, except when you want to implement a custom health check that relies on other services that are registered with DI.

This seems to stem from the fact that the DefaultHealthCheckService from the Microsoft.Extensions.Diagnostics.HealthChecks NuGet package relies on an IServiceScopeFactory and uses it to create a new dependency injection scope. For some reason, the DefaultHealthCheckService is being created with a different implementation of IServiceScopeFactory than the one I'm getting if I inject that service into my Function class directly. I don't really understand why that happens, but the end result is that when it then tries to create my custom health check instance it is unable to resolve the types since it's no longer attached to the root container.

@jmezach we're making some progress on this, but unfortunately, other items have taken precedence. Most of the core pieces to add this support are done. We'll be in a better position to provide an ETA on that once the last SDK items are completed.

@fabiocav

We'll be in a better position to provide an ETA on that once the last SDK items are completed.

Is there an official place we (the public) can view what the last SDK items are? Is it a single/few GH milestones?

I can't explain how important this is for so many of us (that is, people who I talk to, about AF). It's literally a deal blocker for many of us. I know that sounds lame/weird :(

So having any insight into work items, milestones and dates can give us some hope and transparency - as well help us plan expectations and time frames.

We all do really appreciate the work from you and the team. 🎉 🍰

Can anyone explain how the upcoming DI functionality will very from using a IWebJobsStartup class implementation and just having it register dependencies on the IWebJobsBuilder's Services (IServiceCollection) property? I'm currently able to register my dependencies this way, and then make my functions' classes non-static, and accept dependencies through their constructors without any extra attributes or anything to specify that the parameters should be injected. Does using IWebJobsStartup class create a dependency on WebJobs stuff that makes the function not a stand-alone serverless function?

@PureKrome I'll keep this issue updated as we make progress on those items, so this would be the issue to watch. We're resuming work on that, so you should be more updates here soon.

@kemmis you're already benefiting from some of the work that has been done to complete the feature end-to-end. We've broken this down into smaller items and have been lighting them up as they're completed, non-static classes is one of those items. If what you have is addressing your needs, that's great! That won't change and there's no real downsides to your approach. Eventually, what you'll see is a friendlier (Azure Functions focused) API in addition to additional missing features like scoped services support.

@fabiocav Hi again - got another question re: this ->

  • Proper extensibility model for Functions users: clear extensibility model that does not rely on IWebJobsBuilder and uses the correct scope.

Can we get/do logging in this new model?

Will this new "clear extensible model that does not reply on IWebJobsBuilder enable us to be able to:-

  • get an ILogger instance
  • setup any dependencies (which also might require a logging instance!)
  • log.Info("all custom dependencies are setup").

Currently, when I can't seem to do any logging in the IWebJobsStartup Configure method.

Repo to replicate this is here.

@PureKrome IWebJobsBuilder exposes an API and functionality that targets WebJobs and extension author scenarios. The goal of this task is to expose a model that is appropriate for Azure Functions scenarios. That does include logging scenarios as well.

We're about to merge some of the SDK changes that will unblock that work (e.g. support for scoped services) and will start the work on that API soon. I'll try to keep this issue updated with the details of what that will look like as we move forward so we can make sure it addresses the scenarios we need, get some feedback and iterate on it.

Adding @brettsam since he'll be heavily involved in that process as well.

Thanks @fabiocav (and @brettsam for future work 🍰 ) - My gut feeling here is to wait a wee bit longer for stuff to drop in. I'll go back to a simple background task (yes, this is not a "consumption" solution) as the host and when vNext stuff comes online, I can switch out the hosts and keep my logic (and then hopefully become a "consumption" based host/solution)

Cheers team!

Thanks for the awesome working happening in this repo.

I've bumped into something quite odd this morning, I've got two function apps deployed in Azure with exactly the same code base.

One is running run-time version: 2.0.12342.0, the other is running run-time version: 2.0.12353.0. DI is working in the former, but broken in the latter, resulting in an InvalidOperationException - Unable to bind service for type.

Both function apps have worked previously with DI, so the run-time version has clearly updated itself on one of the function apps.

@fabiocav has something regressed? Is there a way I can explicitly set the run-time version in the second version to the one in the first function so DI works again?

EDIT: I've set the FUNCTIONS_EXTENSION_VERSION setting in the Function application settings to 2.0.12342.0 and DI is now working again.

I have the same issue:

  • Runtime version: 2.0.12353.0 (~2) → DI is not working
  • Runtime version: 2.0.12342.0 (~2) → DI works

Function code is here: https://github.com/Azure/iotedge-lorawan-starterkit/tree/dev/LoRaEngine/LoraKeysManagerFacade

Folks! Can anyone share a sample on how to use the "instance" functions with DI?

I can't find any official doc/sample on how to use it. Basically trying to use Microsoft.Extensions.Logging for logs and our own services.

I appreciate any help. Thanks!

Folks! Can anyone share a sample on how to use the "instance" functions with DI?

I can't find any official doc/sample on how to use it. Basically trying to use Microsoft.Extensions.Logging for logs and our own services.

I appreciate any help. Thanks!

Add an IWebJobStartup class to your project:

// WHY IS THIS FILE HERE?
// https://github.com/Azure/Azure-Functions/issues/972

[assembly: WebJobsStartup(typeof(AzureFunctionsAppStartup))]
namespace Example.AzureFunctionsApp
{
    public class AzureFunctionsAppStartup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            // register your dependencies via builder.Services
        }
    }
}

Then just use constructor injection in your Functions class(s):

namespace Test.AzureFunctionsApp
{
    public class MyFunction
    {
        private readonly IAzureFunctionsAppService _azureFunctionsAppService;

        public MyFunction(IAzureFunctionsAppService azureFunctionsAppService)
        {
            _azureFunctionsAppService = azureFunctionsAppService;
        }
        
        [FunctionName("MyFunction")]
        public async Task RunAsync([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
            await _azureFunctionsAppService.RunAsync();
        }
    }
}

(Here IAzureFunctionsAppService is a custom service I made that contains the actual app functionality.)

commented

@kemmis does that mean it is not required to mark the function class and the run method as static?

@kemmis does that mean it is not required to mark the function class and the run method as static?

That is correct. It currently works, but requires the webjob dependent code, and thus isn't the most desirable method of doing this. That's why they are still working on the DI feature for Functions - because it's not currently independent of webjobs. I imagine that in the (hopefully near) future there will be a different place to register your dependencies from how I do it in my example. Maybe something like a Startup.cs class like you see in asp.net core apps.

commented

@kemmis does that mean it is not required to mark the function class and the run method as static?

That is correct. It currently works, but requires the webjob dependent code, and thus isn't the most desirable method of doing this. That's why they are still working on the DI feature for Functions - because it's not currently independent of webjobs. I imagine that in the (hopefully near) future there will be a different place to register your dependencies from how I do it in my example. Maybe something like a Startup.cs class like you see in asp.net core apps.

I simply named my class Startup.cs and when it comes around, I'll remove the inheritance from IWebJobsStartup! I rather follow DI and deal with the startup changes later! Thanks a lot!

I don't think it is working for me:

image

The ILogger which were being injected on the function method was moved to be created on the ctor.

Look at what is printed on the debug console when it on line 26...

Am I missing anything?

Also, if you inspect the _logger variable you will see that all the log providers registered by the function host are there:

image

@galvesribeiro So, I think this is one of the features we're waiting on. Here are my thoughts based on some assumptions (since I'm not involved in the dev of this stuff). Take this with a grain of salt - it could all be wrong:

When you run/debug a Function locally, it gets run by a "host" app that simulates the environment that it is intended to run on in Azure. In Visual Studio that host is the Azure Functions and Web Jobs Tools extension. I assume there's one that you're using in VS Code as well.
image

Anyway, I assume this host has niceties built in that automatically register ILogger "sinks" which dump your logging into the debug console. I also assume that the tool currently only is programmed to do this for the ILogger that gets injected via the function method, and not the instance constructor.

Thus, at this time, I think you would need to manually register the debug console "sink" within your startup class if you want to see your logging in your console locally. As far as how logging will work within Azure, I'd say there's a 50/50 chance that it might flow into App Insights w/o any custom setup in your startup class. Give it a try. I'm curious to find out.

Hope this helps! Also, if you have any further questions, feel free to email me: rafe AT kemmis DOT info. There's no need for us to clutter up this thread with our troubleshooting of stuff that isn't officially supported yet.

Hey!

I finally got it to work with the logger:

[assembly: WebJobsStartup(typeof(AzureFunctionsAppStartup))]
namespace GP.Profiles
{
    public class AzureFunctionsAppStartup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddLogging(logging =>
            {
                logging.AddFilter(level => true);
                logging.AddConsole();
            });
        }
    }
}

The important thing is the AddFilter(). Without it, it wont print anything that I want to log. Only the ones provided by the host are printed.

Now it works and I can see the logs on the terminal. Look at the selected line:

image

I was having trouble getting this to work but setting FUNCTIONS_EXTENSION_VERSION to 2.0.12342.0 and updating nuget package microsoft.net.sdk.functions from 1.0.24 to 1.0.26 seems to have solved my issues.

This doesn't appear to be working with ServiceLifetime.Scoped. I created a very simple function using the code below:

  1. Scoped object
using System;

namespace TestingDI
{
    public class ScopedContext
    {
        public Guid Id { get; set; } = Guid.NewGuid();
    }
}
  1. Startup.cs
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using TestingDI;

[assembly: WebJobsStartup(typeof(Startup))]
namespace TestingDI
{
    public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddScoped<ScopedContext>();
        }
    }
}
  1. Function
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestingDI
{
    public class TestingScopedContext
    {
        readonly ScopedContext _scopedContext;
        public TestingScopedContext(ScopedContext scopedContext)
        {
            _scopedContext = scopedContext;
        }
        [FunctionName("TestingScoped")]
        public async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]HttpRequestMessage req, TraceWriter log)
        {
            return req.CreateResponse(HttpStatusCode.OK, _scopedContext);
        }
    }
}

First call result:

{
    "Id": "d18a4a44-2cb4-43e3-bf12-bd6551c34280"
}

Second call result:

{
    "Id": "d18a4a44-2cb4-43e3-bf12-bd6551c34280"
}

I don't feel like I am missing anything, but maybe I am. Anyone else run into this?

Idk if scoped services are supported yet. I've only tested transient and singleton.

But I know something isn't working properly on this DI. For example, if you use the new HttpClientFactory, it will not work complaining that the MessageHandlerBuilder is not registered.

If you get the same code and put on any other .Net app but Functions, it just work...

Idk if scoped services are supported yet. I've only tested transient and singleton.

While it does construct the object, it appears to register it like a singleton. Like you said, the transient does work, but it won't help if I have more than one class that needs the same instance for the request/action. I am hoping @fabiocav has some guidance and I am just missing some magic code ;)

@galvesribeiro I ran into the same problem with our azure function. If you are making use of a typed httpClient, I managed to fix it in our situation by adding the following configuration command to the startup class:

builder.Services.Configure<HttpClientFactoryOptions>(nameof(DynamicsHttpClient), options => options.SuppressHandlerScope = true);

(replace "DynamicsHttpClient" by the typed httpClient you are using, this requires microsoft.extensions.http v2.2)

@tekking thanks, but it doesn't work for me :(

[3/20/19 1:26:36 PM] An unhandled host error has occurred.
[3/20/19 1:26:36 PM] Microsoft.Extensions.DependencyInjection.Abstractions: No service for type 'Microsoft.Extensions.Http.HttpMessageHandlerBuilder' has been registered.

I got this working with 3 Azure Functions earlier this week. But now it seems broken. Just now deleted everything and redeployed function 1 to see where this goes sideways, but still can't get it to work.

When testing the function from the test pane in the Azure portal, it complains about not having the AutoMapper dependency that should have been added to DI in Startup.cs. My function constructor is taking an instance of IMapper.
This used to work smoothly at some point when I updated to Microsoft.NET.Sdk.Functions 1.0.26. Locally in the emulator it's working fine, when I deploy to my resource group it booms on me.
Any ideas?

The error:
2019-03-23T09:16:24 Welcome, you are now connected to log-streaming service. 2019-03-23T09:16:30.965 [Error] Executed 'GetProducts' (Failed, Id=051f5abc-ed72-4f12-9ca2-4bb6064ac1d7) System.InvalidOperationException : Unable to resolve service for type 'AutoMapper.IMapper' while attempting to activate 'Webshop.CatalogSystem.AzureFunction.ProductsFunction'. at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp,Type type,Type requiredBy,Boolean isDefaultParameterRequired) at lambda_method(Closure ,IServiceProvider ,Object[] ) at Microsoft.Azure.WebJobs.Host.Executors.DefaultJobActivator.CreateInstance[T](IServiceProvider serviceProvider) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\DefaultJobActivator.cs : 42 at Microsoft.Azure.WebJobs.Host.Executors.DefaultJobActivator.CreateInstance[T](IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\DefaultJobActivator.cs : 32 at Microsoft.Azure.WebJobs.Host.Executors.ActivatorInstanceFactory1.<>c__DisplayClass1_1.<.ctor>b__0(IFunctionInstanceEx i) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\ActivatorInstanceFactory.cs : 20
at Microsoft.Azure.WebJobs.Host.Executors.ActivatorInstanceFactory1.Create(IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\ActivatorInstanceFactory.cs : 26 at Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker2.CreateInstance(IFunctionInstanceEx functionInstance) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionInvoker.cs : 44
at Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ParameterHelper.Initialize() at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 845
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.TryExecuteAsyncCore(IFunctionInstanceEx functionInstance,CancellationToken cancellationToken) at C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs : 116
`

Startup.cs:

[assembly: WebJobsStartup(typeof(Startup))]
namespace Webshop.CatalogSystem.AzureFunction
{
    public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddScoped<ServiceFactory>(p => p.GetService);
            builder.Services.Scan(scan => scan
                .FromAssembliesOf(typeof(IMediator), typeof(GetProductsQueryHandler))
                .AddClasses()
                .AsImplementedInterfaces());
            builder.Services.AddAutoMapper(new Type[] { typeof(MappingProfile) });
            builder.Services.AddDbContext<CatalogDbContext>(c =>
                c.UseInMemoryDatabase("Catalog"));
        }
    }
}

EDIT: Setting FUNCTIONS_EXTENSION_VERSION to 2.0.12342.0 in the application settings does work, so I'm using that workaround for now. Thanks @Jaffacakes82 and @fbeltrao for that.

Im getting the same error as ngruson. When I deploy my code bit not in the emulator :(

Same issue. DI works in the emulator, returns 'Unable to resolve service for type 'XXX'' when published.

@AndeyR see this issue:

Azure/Azure-Functions#972

Thanks for pointing to that rabbit hole)
At the end my issue resolved with downgrading host to 2.0.12342.0
Original issue #4203

Regardless of fixes and outcomes, it still feels like the current situation is a bit of a frustrating experience with lots of issues.

Just looking forward to this issue getting 100% A1 priority, fixed and released.

🤗 hugs to the team! We're just all passionate. Patience is a virtue.....

A follow up... yes, there was a regression introduced with the latest release as part of the change to properly support scoped services, where the underlying SDK changes conflicted with the host configuration and, unfortunately, the test coverage was not sufficient to catch that. The problem has been addressed and is rolling out with the next release.

We've been adding the individual features that make up the broader DI solution independently, which lights up additional scenarios as we go, as we thought that would be nice and enable to unblock more and more scenarios before the full set of changes are done, but have not publicly discussed a lot of those because, until the work is completed, there will be gaps and quirks in the behavior.

The instance class/constructor injection support was one of the things that was discusssed more broadly before everything was baked, and unfortunatly, it was what impacted with the recent changes as we wrap up the remaining work and move towards the finish line. We're taking steps to ensure this won't happen again.

The good news is that with the last set of changes, we've checked of last set of impacful changes needed in the SDK/runtime (reflected in the original issue list). Which brings us much closer to completion.

We appreciate the patience as we work through this. We're trying to complete this as carefully as possible while balancing the work with other key features we're delivering in the coming releases, and
while the progress may seem slow, work has been happening across all the different repos and we're now finally bringing this together. (and thanks @PureKrome for always knowing exactly how to get your point across in a positive way!)

@fabiocav - When is functions host release 2.0.12382 rolling out? Is this required to get the scoped services fix made in the functions sdk release 3.0.5 to work? I am running in 2.0.12342.0 in Azure to fix the other issues reported with 2.0.12353 and I am still having trouble with scoped services. As far as the core tools releases the latest 2.4.498 does not include 2.0.12382, it includes the broken DI release of 2.0.12353. Just wanting some guidance when all of these will be synced up.

A follow up... yes, there was a regression introduced with the latest release as part of the change to properly support scoped services, where the underlying SDK changes conflicted with the host configuration and, unfortunately, the test coverage was not sufficient to catch that. The problem has been addressed and is rolling out with the next release.

Following on from the above comment, the latest release of the Azure Functions Core Tools released two days ago comes packaged with the regressed version of the Functions run-time host and also breaks DI locally - it seems to have auto-updated on my machine somehow. Don't upgrade just yet!

@miantosca If you're still having the issue I did the following to resolve npm install -g azure-functions-core-tools@2.4.419 and ensure your launch settings are executing this host. I had multiple installations of the core tools for some reason.

2.0.12382 is rolling out to Azure and yes, it is a requirement for the scoped services support recently introduced. The rollout is expected to complete by the end of this week.

Once the Azure deployment is completed, the Core Tools matching that version will be released.

@fabiocav and team - thanks so much for resolving this

2.0.12382.0 is now a valid version in Azure and the DI issues have been resolved

commented

Thanks to @fabiocav and Azure functions team.
Now, how to inject the ILogger to the lower services used by the function? BTW, ILogger only gets injected via the run method parameters. The constructor injection doesn't work for ILogger.

Thank you very much for resolving this! Our functions are working properly now.

@nguyenquyhy look at my comment here #3736 (comment)

It just work.

commented

@nguyenquyhy look at my comment here #3736 (comment)

It just work.

The logger for the function works just fine, but it won't work for the services that are trying to inject it. Besides, it doesn't work with the constructor injection.

OFC it works. I'm using it. Both the constructor of the function and on any service injected to it can take a ILoggerFactory and then call ILogger logger = factory.CreateLogger<T>() just like on any Microsoft.Extensions.DependencyInjection-based application...

What is the problem you guys have with it?

commented

My bad. I was trying to inject ILogger directly. ILoggerFactory works like a charm.

Is it possible to inject dependency for DocumentClient object (for accessing Azure Cosmos DB) and ServiceClient object (for cloud-to-device messages)?

I'm trying to add more unit test coverage for HTTP triggered functions. Kindly let me know if it's possible or not, and what is the best practice?

Yes, inject the interface into the constructor and wire it up in the service collection in the startup.

@mushthaque-pk totally possible.

Don't forget to register the type you are injecting (interface or not) on your IWebJobsStartup implementation.

Have samples of best practice for DI in Functions?

@galvesribeiro Do you have sample or steps to configure startup? Right now, I don't have any startup class for functions.

Do you plan to support 3rd party DI frameworks, for example Castle Windsor?

Do you plan to support 3rd party DI frameworks, for example Castle Windsor?

You can use Castle Windsor in combination with the DI framework that is used by Azure Functions and ASP.NET Core. To do this, grab the Castle.Windsor.MsDependencyInjection NuGet package.

IServiceCollection services = new ServiceCollection();
services.AddTransient<MyService>();
 
var windsorContainer = new WindsorContainer();
 
windsorContainer.Register(
    Component.For<MyService>()
);
 
var serviceProvider = WindsorRegistrationHelper.CreateServiceProvider(
    windsorContainer,
    services
);
 
var myService = serviceProvider.GetService<MyService>();

@ngruson - ok, but will it work with constructor injection? We can't return serviceProvider in startup class.

Look at my comment here: #3736 (comment)

You will find a sample Startup.cs. The most important piece is to implement that interface, make sure it is public and the assembly-level attribute on top of the class is targeting that very same class.

With that implemented on your function project, just register your types on that class and then inject it on your function classes. Remember that your functions in this case, are not static classes anymore. They're all instance classes and you will be injecting the services with DI thru its constructor.

I hope it help...

What are the required nuget packages to work dependency injection in Azure Function v2?

I have following package versions installed and tried to inject dependencies:
Microsoft.Azure.WebJobs v3.0.6
Microsoft.Azure.WebJobs.Extensions v3.0.2
Microsoft.NET.Sdk.Functions v1.0.26

I have everything (logic in the startup.cs and constructor injection in functions) are working perfectly but throwing file not found errors. Randomly visual studio throwing file not found errors in debug mode (please see the screenshot).
Container.cs not found
DefaultJobActivator.cs not found
WebJobsBuilderExtensions.cs not found

Capture

What are the required nuget packages to work dependency injection in Azure Function v2?

I have following package versions installed and tried to inject dependencies:
Microsoft.Azure.WebJobs v3.0.6
Microsoft.Azure.WebJobs.Extensions v3.0.2
Microsoft.NET.Sdk.Functions v1.0.26

Did you explicitly add references to Microsoft.Azure.WebJobs and Microsoft.Azure.WebJobs.Extensions?
That shouldn't be necessary, the 3.0.0 versions come with Microsoft.NET.Sdk.Functions v1.0.26.

I have these references on a working solution:

image

@ngruson I just removed the explicitly added references. Thank you for sharing that info.

But still I'm getting the same file not found exception. Is it related to storage emulator?
Capture

Look at my comment here: #3736 (comment)

You will find a sample Startup.cs. The most important piece is to implement that interface, make sure it is public and the assembly-level attribute on top of the class is targeting that very same class.

With that implemented on your function project, just register your types on that class and then inject it on your function classes. Remember that your functions in this case, are not static classes anymore. They're all instance classes and you will be injecting the services with DI thru its constructor.

I hope it help...

I'm not sure how that would work with Castle Windsor though. It would still use the service provider generated using the service collection not the Castle Windsor one,

Is anyone using Entity Framework Core and injecting a DbContext ? Would like to know your experience.

Is anyone using Entity Framework Core and injecting a DbContext ? Would like to know your experience.

Yep, doing that, works like a charm afaik.
The constructor of a class that is used by my Function is taking a MyDbContext object. If you want the base DbContext, you can use a second type param: AddDbContext<DbContext, MyDbContext>.

public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            ...
            builder.Services.AddDbContext<MyDbContext>(c =>
                c.UseInMemoryDatabase("MyDevDb"));
        }

Folks, here is the dependencies I have on one of my projects:

  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="3.0.3" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.EventHubs" Version="3.0.3" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.26" />
    <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
  </ItemGroup>

It work pretty fine.

@ngruson Thanks for the reply. Specifically looking for anyone using SQLDatabase connectivity, and their experience with connection pooling.

@ngruson Thanks for the reply. Specifically looking for anyone using SQLDatabase connectivity, and their experience with connection pooling.

Guess you better ask here: https://github.com/aspnet/EntityFrameworkCore/issues

I've got a basic working example here in case anyone needs one.

https://github.com/mattosaurus/JsonPlaceHolderDependencyInjection

The only thing I haven't been able to figure out is how to pass a Serilog.ILogger through to Run() rather than the Microsoft.Extensions.Logging.ILogger expected (if it's even possible) so I'm just using DI for this as well.

The only thing I haven't been able to figure out is how to pass a Serilog.ILogger through to Run() rather than the Microsoft.Extensions.Logging.ILogger expected (if it's even possible) so I'm just using DI for this as well.

Why do you want to keep passing it on Run()? Can't you inject the ILoggerFactory on the ctor and then get an instance of ILogger? The way you are doing it you are not leveraging the infrastructure from Microsoft.Extensions.Logging.Abstraction...

For Serilog you should do this:

public void Configure(IWebJobsBuilder builder)
  {
      builder.Services.AddLogging(loggingBuilder =>
      	loggingBuilder.AddSerilog(dispose: true));
      
      // Other services ...
  }

No need to create a LoggerConfiguration or Log.Logger and inject it as singleton. That AddSerilog() extension method will do the trick for you and manage and its internal registrations along with support the MEL configuration like categories etc...

More info: https://github.com/serilog/serilog-extensions-logging

@galvesribeiro, Thanks for the advice, I was expecting to get ILogger in the Run() method because that's where it's available in the function template but obviously this isn't necessary if I'm using DI.

Yeah, the first thing you should do when you decide to use MEL as your logging infrastructure with the DI on functions (besides register it on Startup.cs) is to make your class non-static along with your function method. After that, just inject anything you want on the ctor and be happy! 😄

@aaronhudon You may need to hold your Azure Function's hand to do that. I tried it on 1.0.23 and it didn't work due to the concept of scoped services wasn't supported.

I'm just about to try it on the latest WebJobs SDK. I'll let you know how it goes.

@anarsen Thanks. I have been. Each function's entrypoint calls a common DbContext creation method.

Circling back on this one. Did the support for some sort of DI get added to Azure Functions V2 recently? I've been following this thread to make sure I didn't miss it but suddenly stumbled upon a question like this on SO.

So, can someone tell me what the current status of DI support for Azure Functions V2 is? Also - can I use Autofac also if I want to?

@hiraldesai do what I said ☝️ here and be happy 😄

@galvesribeiro But is this official? Is documented anywhere or on a release notes?

I mean, one thing is that it works, but have the AF team confirmed that this is not going to break in future releases?

I have been using Autofac, but if possible would like to switch to an already DI in net core.

@german95n the Az Functions team is working on bring native support to Microsoft.Extensions.DependencyInjection.Abstractions.

The first iteration of this work was completed by @fabiocav and is working as my comment say.

There are still some open issues for other features on it like removing the dependency on WebJobs SDK, support for Scoped Services etc. The infrastructure is laid down already. The rest will come.

As of today, they are based on ME.DI.Abstractions, which is a very stable interface used by all .Net ecosystem and I doubt it will change. Autofac has support to it as well as you can find on this repo so you keep using it if that is the container you like.

I think what @fabiocav did was correct. They are releasing partial support to DI so people start using it and at least can plug (some) of the libraries over .Net ecosystem along with having the ability (finally!!!) to unit test functions properly as they are not static anymore.

Thank you! I will give it a try then since this DI adapts more to what I want to accomplish. As you pointed out, Unit test is an important feature.

I had some doubts as I haven't seen anything official (only this thread).

I'm pretty sure their target now is to get it feature full and collect feedback for early adopters. Like I said, as it is based on ME.DI.Abstractions, it should be the same thing as any .Net ecosystem library that everyone use. The only caveat for now, is the use of the Startup class that still depends on WebJobs SDK types, but will be removed later on.

Regardless, I'm pretty sure that they will provide docs on the official site once everything is sorted out.

@galvesribeiro I hoped this work removed the usage of DryIOC that is the source of the differences in behavior between ME.DI that plague the current functons framework version (see #3399), but as of now I see it's a reimplementation of the standard .net DI facades around DryIOC, (

) which looks like a very bad idea that is going to cause a lot more subtle differences when .net ecosystem libraries, like in the issue mentioned before.

I don't know yet, looking at the code, what is the motivation to deviate from standard .net DI implementation, and assume the risk and support costs of behavioral differences between .net core DI implementation and azure functions, maybe @fabiocav can comment with details?

Those behavior differences has been a problem to us when migration asp.net core application in the past and will continue to be if DryIOC (or any other third party IOC implementation) remains at the core of the DI implementation in functions. When DI was not really supported it was more o less okay to discover issues, but now that it is on the road to become a supported feature, most users will use it when migrating an existing application or start using .net ecosystem libraries (like EF, Health, IdentityServer...) and expect to just work.

but as of now I see it's a reimplementation of the standard .net DI facades around DryIOC

Well, I believe it is temporary. Like I said, WebJobs SDK types are supposed to be removed according to @fabiocav comment on some other issues.

I don't know yet, looking at the code, what is the motivation to deviate from standard .net DI implementation, and assume the risk and support costs of behavioral differences between .net core DI implementation and azure functions, maybe @fabiocav can comment with details?

Yeah, I think Fabio can provide more info but I guess it is because of the reasons I've mentioned on other comment. Less breaking changes, support to incremental feature development on this DI story, and the ability to unblock people to use it piece by piece. Like I said, it is very likely that will be removed but I don't think it will be a big breaking change from the developers perspective as that already happened when you switch from static classes with parameters on the Run() method to non-static classes with dependencies injected on the ctor.

Those behavior differences has been a problem to us when migration asp.net core application in the past and will continue to be if DryIOC (or any other third party IOC implementation) remains at the core of the DI implementation in functions. When DI was not really supported it was more o less okay to discover issues, but now that it is on the road to become a supported feature, most users will use it when migrating an existing application or start using .net ecosystem libraries (like EF, Health, IdentityServer...) and expect to just work.

I agree that it is not desirable to have that dependency. But like I said, it is supposed to be removed later on. As long as the extensions rely on IServiceCollection to be registered and you can consume from IServiceProvider, it should be ok specially if you com from the .Net ecosystem libraries that does the same.

Again, I know we are all anxious to have it completed with public docs etc, but we should let the team work and (if possible) consume the dog food that we're getting with the incremental builds of the Az Fun SDK/Runtime...

Thanks @galvesribeiro for your thorough response.

It's good to know that removing the internal dependency is something that is on the roadmap, an I see reasonable to do it in a parallel changes fashion, we all benefit from it.

I know we should let the team work, but I was writing this with the intent to know the what are the plans of the team for this feature, maybe because I felt, specially after the live stream from April where this topic was barely touched, that this topic needed clarification.

Also I wanted to create awareness on the team, that what could look like an innoncent technical choice, it is already having unexpected consequences on we, the users and implementantors.

@ielcoro don't take me bad 😄 I was just saying that we're seen a good progress with several PRs/issues from @fabiocav on that matter and I was with the same feelings about it as you are. Then I started to following all the related issues and came to that conclusion (that it is coming soon! 😄 )

I don't speak for the team nor MSFT (left the company several years ago) but I'm tracking closely the work on this particular feature because it was one of the reasons I've dropped attempts to use Az Functions and stick with kubernetes.

Now that I can at least test my code properly without hackery and leverage the awesome infrastructure with the Microsoft.Extensions.XXX libraries/abstractions, I'm migrating already everything we have to Az Funcs and all other serverless product offerings from Azure.

If I'm wrong on my assumptions, perhaps @fabiocav can clear things up further...

Thank you for your response @galvesribeiro - the reason I asked for "official" docs on this was because of this comment on another issue from @brettsam - I wanted to be 100% sure of the DI support going forward before I undo my [Inject] based workaround I have in place. 😄

@ielcoro to keep this issue on topic, can you please open a separate issue detailing the problems you've encountered? The runtime has requirements that are not met by the built in DI implementation in .NET Core, and are not expected to be, so we don't foresee that dependency removal happening soon (or an alternative that would still need to custom behavior to meet our requirements), but we would like to understant the problems you're encountering as we do have the ability to address them.

@hiraldesai official docs are currently being worked on and will become available when the feature is completed.

For transparency, and to better set expectations, we're currently targeting Sprint 48 (due by May 1st) to complete this.

Awesome as usual, @fabiocav 😄

@fabiocav I did 7 months ago 😉 here it is #3399

Thanks for explaining the direction and looking into this.

@galvesribeiro

I've dropped attempts to use Az Functions and stick with kubernetes.

Funny you said that ... I've been struggling to get Functions + DI working on localhost for a while. I finally got it working a few weeks back.

After that, I then added my functions project (in my solution) to my docker-compose file :) This means I'm using FROM mcr.microsoft.com/azure-functions/dotnet:2.0 to create a docker image of my function-project and later on, when I learn how to get this stuff from Azure Container Registry -> Azure K8's ... one of the docker images will be a functions app :)

@PureKrome Heehhe yeah. I’ve never tried Az Func outside Az itself...

When i said k8s I meant regular .Net Core/Signar/AspNet/Orleans on top of kube containers

@fabiocav thanks to you and the team for all your hard work. We're very much looking forward to this official release (even though we're using the non static instance methods already).

I'm trying to get a sense of the scenarios this feature will unblock (other than basic DI). For example...

We frequently want to add custom dimensions to the request telemetry logged to app insights. Each function knows what custom information it wants to add so it isn't something that can be handled with just a telemetry initializer. It seems like we need some sort of "request scoped" state like (httpcontextaccessor in asp.net core) that we could inject into the function, the function could write new properties to this state and those properties could be used in the telemetry initializer (because the state would be injected there as well).

Will something like this be possible once this feature is completed?

I'm currently working with this appoach:

It works quite well without any extensions, just azure function runtime 💯. But it somehow don't follow MSFT Dependency Injection regarding lifetimes: Usually, when the registering a singleton which implements IDisposable, it should get disposed when container gets disposed, means when function host or the appdomain shutdown, but it never gets called.

Is there some special handling implemented which overrides this expected behavior?
Is the behavior different on local console host? -> This is where I've tried it.

Another option I've tried is to proxy the IHostLifetime.

public class Startup : IWebJobsStartup
{
    public void Configure(IWebJobsBuilder builder)
    {
        builder.Services.AddSingleton<IMyDependency, MyDependency>();
        builder.Services.Replace(ServiceDescriptor.Singleton<IHostLifetime>(sp =>  new CustomJobHostHostLifetime(sp.GetRequiredService<IHostLifetime>())));
    }

    internal class CustomJobHostHostLifetime : IHostLifetime
    {
        private readonly IHostLifetime _innerHostLifeTime;

        public CustomJobHostHostLifetime(IHostLifetime innerHostLifeTime)
        {
            _innerHostLifeTime = innerHostLifeTime;
        }

        public async Task WaitForStartAsync(CancellationToken cancellationToken)
        {
            await _innerHostLifeTime.WaitForStartAsync(cancellationToken);
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            await _innerHostLifeTime.StopAsync(cancellationToken);
        }
    }
}

public class MyDependency : IMyDependency, IDisposable
{
    private readonly ILogger<MyDependency> _myLogger;

    public MyDependency(ILogger<MyDependency> myLogger)
    {
        _myLogger = myLogger;            
    }

    public void Foo()
    {
        _myLogger.LogInformation("Foo");
    }

    public void Dispose()
    {
        _myLogger.LogInformation("Dispose");
    }
}

public interface IMyDependency
{
    void Foo();
}

But it actually never gets called. To summary a bit better my needs:

It there a way to detect a shutdown of the host, where I can do some startup AND cleanup with and async interface like IHostLifetime?

@cmenzi Dispose should be called in those scenarios. How are you shutting the host down in your tests? It might be helpful to start a separate issue so we can iterate on this.

An update on this.

A package with the set of extensions/APIs that tie into the rest of the DI feature in the host and the build has just been published. You can start trying this out with: https://www.nuget.org/packages/Microsoft.Azure.Functions.Extensions/

Please make sure you're using the latest version of the Microsoft.NET.Sdk.Functions package when using this feature.

Here's an example of a startup with the new APIs:
https://gist.github.com/fabiocav/996811f9351026686930f0456c74007f

using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Microsoft.Azure.Functions.Samples.DependencyInjectionScopes.SampleStartup))]

namespace Microsoft.Azure.Functions.Samples.DependencyInjectionScopes
{
    public class SampleStartup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {

            // Register MyServiceA as transient.
            // A new instance will be returned every
            // time a service request is made
            builder.Services.AddTransient<MyServiceA>();

            // Register MyServiceB as scoped.
            // The same instance will be returned
            // within the scope of a function invocation
            builder.Services.AddScoped<MyServiceB>();

            // Register ICommonIdProvider as scoped.
            // The same instance will be returned
            // within the scope of a function invocation
            builder.Services.AddScoped<ICommonIdProvider, CommonIdProvider>();


            // Register IGlobalIdProvider as singleton.
            // A single instance will be created and reused
            // with every service request
            builder.Services.AddSingleton<IGlobalIdProvider, CommonIdProvider>();
        }
    }
}

@fabiocav this new NuGet library the team has just published, does it also include an overide for ConfigureServices? so it's like we at parity with the generic hosts, etc?

I tried to look for the class FunctionsStartup in a repo on GH but couldn't find it.

image

@fabiocav @jeffhollan so.... any Az Func announcements for MSFT Build next week? 😉