Azure / azure-functions-host

The host/runtime that powers Azure Functions
https://functions.azure.com
MIT License
1.93k stars 440 forks source link

Dependency Injection support for Functions #3736

Closed fabiocav closed 5 years ago

fabiocav commented 5 years ago

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

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

galvesribeiro commented 5 years ago

@nguyenquyhy look at my comment here https://github.com/Azure/azure-functions-host/issues/3736#issuecomment-473299060

It just work.

saeid-p commented 5 years ago

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

galvesribeiro commented 5 years ago

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?

saeid-p commented 5 years ago

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

mushthaque-pk commented 5 years ago

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?

chris-rogala commented 5 years ago

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

galvesribeiro commented 5 years ago

@mushthaque-pk totally possible.

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

balivo commented 5 years ago

Have samples of best practice for DI in Functions?

mushthaque-pk commented 5 years ago

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

DavidJFowler commented 5 years ago

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

ngruson commented 5 years ago

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>();
funcfan commented 5 years ago

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

galvesribeiro commented 5 years ago

Look at my comment here: https://github.com/Azure/azure-functions-host/issues/3736#issuecomment-473299060

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

mushthaque-pk commented 5 years ago

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

ngruson commented 5 years ago

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

mushthaque-pk commented 5 years ago

@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

DavidJFowler commented 5 years ago

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 commented 5 years ago

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

ngruson commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

ngruson commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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

hiraldesai commented 5 years ago

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 commented 5 years ago

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

germancasares commented 5 years ago

@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 commented 5 years ago

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

germancasares commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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, (https://github.com/Azure/azure-functions-host/blob/5b66919f92fafdeb274c83cab6e455f66f2e3132/src/WebJobs.Script.WebHost/DependencyInjection/JobHostServiceProvider.cs#L9) 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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

Awesome as usual, @fabiocav 😄

ielcoro commented 5 years ago

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

Thanks for explaining the direction and looking into this.

PureKrome commented 5 years ago

@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 commented 5 years ago

@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

oluatte commented 5 years ago

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

cmenzi commented 5 years ago

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?

fabiocav commented 5 years ago

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

fabiocav commented 5 years ago

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>();
        }
    }
}
PureKrome commented 5 years ago

@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

espray commented 5 years ago

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