Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency Injection support for Functions #3736

Open
fabiocav opened this Issue Nov 5, 2018 · 95 comments

Comments

Projects
None yet
@fabiocav
Copy link
Member

fabiocav commented Nov 5, 2018

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
@mathewc

This comment has been minimized.

Copy link
Contributor

mathewc commented Nov 6, 2018

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.

@fabiocav fabiocav self-assigned this Nov 28, 2018

@fabiocav fabiocav added this to the Functions Sprint 38 milestone Nov 28, 2018

@mwadams

This comment has been minimized.

Copy link

mwadams commented Dec 10, 2018

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

@espray

This comment has been minimized.

Copy link

espray commented Dec 10, 2018

@PureKrome

This comment has been minimized.

Copy link

PureKrome commented Dec 19, 2018

@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?
@espray

This comment has been minimized.

Copy link

espray commented Dec 19, 2018

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

@balivo

This comment has been minimized.

Copy link

balivo commented Dec 19, 2018

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...

@PureKrome

This comment has been minimized.

Copy link

PureKrome commented Dec 19, 2018

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.

@balivo

This comment has been minimized.

Copy link

balivo commented Dec 20, 2018

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...

@jmezach

This comment has been minimized.

Copy link

jmezach commented Jan 28, 2019

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.

@vitalybibikov

This comment has been minimized.

Copy link

vitalybibikov commented Jan 28, 2019

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

@jmezach

This comment has been minimized.

Copy link

jmezach commented Feb 12, 2019

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.

@fabiocav

This comment has been minimized.

Copy link
Member Author

fabiocav commented Feb 12, 2019

@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.

@PureKrome

This comment has been minimized.

Copy link

PureKrome commented Feb 21, 2019

@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. 🎉 🍰

@kemmis

This comment has been minimized.

Copy link

kemmis commented Feb 22, 2019

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?

@fabiocav

This comment has been minimized.

Copy link
Member Author

fabiocav commented Feb 22, 2019

@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.

@rifuller

This comment has been minimized.

Copy link

rifuller commented Feb 22, 2019

@DavidJFowler

This comment has been minimized.

Copy link

DavidJFowler commented Apr 4, 2019

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,

@aaronhudon

This comment has been minimized.

Copy link

aaronhudon commented Apr 4, 2019

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

@ngruson

This comment has been minimized.

Copy link

ngruson commented Apr 4, 2019

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"));
        }
@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 4, 2019

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.

@aaronhudon

This comment has been minimized.

Copy link

aaronhudon commented Apr 4, 2019

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

@ngruson

This comment has been minimized.

Copy link

ngruson commented Apr 4, 2019

@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

@mattosaurus

This comment has been minimized.

Copy link

mattosaurus commented Apr 5, 2019

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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 5, 2019

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

@mattosaurus

This comment has been minimized.

Copy link

mattosaurus commented Apr 5, 2019

@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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 5, 2019

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! 😄

@anarsen

This comment has been minimized.

Copy link

anarsen commented Apr 7, 2019

@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.

@aaronhudon

This comment has been minimized.

Copy link

aaronhudon commented Apr 7, 2019

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

@hiraldesai

This comment has been minimized.

Copy link

hiraldesai commented Apr 9, 2019

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?

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

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

@german95n

This comment has been minimized.

Copy link

german95n commented Apr 9, 2019

@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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

@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.

@german95n

This comment has been minimized.

Copy link

german95n commented Apr 9, 2019

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).

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

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.

@ielcoro

This comment has been minimized.

Copy link

ielcoro commented Apr 9, 2019

@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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

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...

@ielcoro

This comment has been minimized.

Copy link

ielcoro commented Apr 9, 2019

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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

@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...

@hiraldesai

This comment has been minimized.

Copy link

hiraldesai commented Apr 9, 2019

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. 😄

@fabiocav

This comment has been minimized.

Copy link
Member Author

fabiocav commented Apr 9, 2019

@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.

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 9, 2019

Awesome as usual, @fabiocav 😄

@ielcoro

This comment has been minimized.

Copy link

ielcoro commented Apr 9, 2019

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

Thanks for explaining the direction and looking into this.

@PureKrome

This comment has been minimized.

Copy link

PureKrome commented Apr 10, 2019

@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 :)

@galvesribeiro

This comment has been minimized.

Copy link

galvesribeiro commented Apr 10, 2019

@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

@mayoatte

This comment has been minimized.

Copy link

mayoatte commented Apr 19, 2019

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.