DI in Azure Functions

后端 未结 11 1040
庸人自扰
庸人自扰 2020-12-25 10:43

I have some class libraries that I use in my ASP.NET Web API app that handle all my backend stuff e.g. CRUD operations to multiple databases like Azure SQL Database, Cosmos

相关标签:
11条回答
  • 2020-12-25 11:19

    As stated above, it was just announced at Build 2019. It can now be setup almost exactly like you would in an ASP .Net Core app.

    Microsoft Documentation

    Short Blog I Wrote

    0 讨论(0)
  • 2020-12-25 11:22

    There is an open feature request on the GitHub pages for Azure Functions concerning this matter.

    However, the way I'm approaching this is using some kind of 'wrapper' entry point, resolve this using the service locator and and start the function from there.

    This looks a bit like this (simplified)

    var builder = new ContainerBuilder();
    //register my types
    
    var container = builder.Build();
    
    using(var scope = container.BeginLifetimeScope())
    {
      var functionLogic = scope.Resolve<IMyFunctionLogic>();
    
      functionLogic.Execute();
    }
    

    This is a bit hacky of course, but it's the best there is until there is at the moment (to my knowledge).

    0 讨论(0)
  • 2020-12-25 11:25

    Azure Functions Depdendency Injection was announced at MSBuild 2019. Here's an example on how to do it:

    [assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
    
    namespace MyNamespace
    {
        public class Startup : FunctionsStartup
        {
            public override void Configure(IFunctionsHostBuilder builder)
            {
                builder.Services.AddHttpClient();
                builder.Services.AddSingleton((s) => {
                    return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
                });
                builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
            }
        }
    }
    
    • GitHub Example
    • Documentation
    0 讨论(0)
  • 2020-12-25 11:26

    I have been using SimpleInjector perfectly fine in Azure Functions. Just create a class (let's call it IoCConfig) that has the registrations and make a static instance of that class in function class so that each instance will use the existing instance.

    public interface IIoCConfig
    {
        T GetInstance<T>() where T : class;
    }
    
    public class IoCConfig : IIoCConfig
    {
        internal Container Container;
    
        public IoCConfig(ExecutionContext executionContext, ILogger logger)
        {
            var configurationRoot = new ConfigurationBuilder()
                .SetBasePath(executionContext.FunctionAppDirectory)
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();
    
            Container = new Container();
            Configure(configurationRoot, logger);
        }
    
        public IoCConfig(IConfigurationRoot configurationRoot, ILogger logger)
        {
            Container = new Container();
            Configure(configurationRoot, logger);
        }
    
        private void Configure(IConfigurationRoot configurationRoot, ILogger logger)
        {
            Container.RegisterInstance(typeof(IConfigurationRoot), configurationRoot);
            Container.Register<ISomeType, SomeType>();
        }
    
        public T GetInstance<T>() where T : class
        {
            return Container.GetInstance<T>();
        }
    }
    

    Then in root:

       public static class SomeFunction
    {
        public static IIoCConfig IoCConfig;
    
        [FunctionName("SomeFunction")]
        public static async Task Run(
            [ServiceBusTrigger("some-topic", "%SUBSCRIPTION_NAME%", Connection = "AZURE_SERVICEBUS_CONNECTIONSTRING")]
            SomeEvent msg,
            ILogger log,
            ExecutionContext executionContext)
        {
            Ensure.That(msg).IsNotNull();
    
            if (IoCConfig == null)
            {
                IoCConfig = new IoCConfig(executionContext, log);
            }
    
            var someType = IoCConfig.GetInstance<ISomeType>();
            await someType.Handle(msg);
        }
    }
    
    0 讨论(0)
  • 2020-12-25 11:27

    I've seen the willie-zone blog mentioned a lot when it comes to this topic, but you don't need to go that route to use DI with Azure functions.

    If you are using Version2 you can make your Azure functions non-static. Then you can add a public constructor for injecting your dependencies. The next step is to add an IWebJobsStartup class. In your startup class you will be able to register your services like you would for any other .Net Core project.

    I have a public repo that is using this approach here: https://github.com/jedi91/MovieSearch/tree/master/MovieSearch

    Here is a direct link to the startup class: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Startup.cs

    And here is the function: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Functions/Search.cs

    Hope this approach helps. If you are wanting to keep your Azure Functions static then the willie-zone approach should work, but I really like this approach and it doesn't require any third party libraries.

    One thing to note is the Directory.Build.target file. This file will copy your extensions over in the host file so that DI will work once the function is deployed to Azure. Running the function locally does not require this file.

    0 讨论(0)
  • 2020-12-25 11:29

    Actually there is a much nicer and simpler way provided out of the box by Microsoft. It is a bit hard to find though. You simply create a start up class and add all required services here, and then you can use constructor injection like in regular web apps and web apis.

    This is all you need to do.

    First I create my start up class, I call mine Startup.cs to be consistent with Razor web apps, although this is for Azure Functions, but still it's the Microsoft way.

    using System;
    using com.paypal;
    using dk.commentor.bl.command;
    using dk.commentor.logger;
    using dk.commentor.sl;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using org.openerp;
    
    [assembly:Microsoft.Azure.WebJobs.Hosting.WebJobsStartup(typeof(dk.commentor.starterproject.api.Startup))]
    namespace dk.commentor.starterproject.api
    {
    
        public class Startup : IWebJobsStartup
        {
    
            public void Configure(IWebJobsBuilder builder)
            {
                builder.Services.AddSingleton<ILogger, CommentorLogger>();
                builder.Services.AddSingleton<IPaymentService, PayPalService>();
                builder.Services.AddSingleton<IOrderService, OpenERPService>();
                builder.Services.AddSingleton<ProcessOrderCommand>();
                Console.WriteLine("Host started!");
            }
        }
    }
    

    Next I change the method call in the function from static to non-static, and I add a constructor to the class (which is now also non-static). In this constructor I simply add the services I require as constructor parameters.

    using System;
    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    using dk.commentor.bl.command;
    
    namespace dk.commentor.starterproject.api
    {
        public class ProcessOrder
        {
            private ProcessOrderCommand processOrderCommand;
    
            public ProcessOrder(ProcessOrderCommand processOrderCommand) {
                this.processOrderCommand = processOrderCommand;
            }
    
            [FunctionName("ProcessOrder")]
            public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
            {
                log.LogInformation("C# HTTP trigger ProcessOrder called!");
                log.LogInformation(System.Environment.StackTrace);
    
                string jsonRequestData = await new StreamReader(req.Body).ReadToEndAsync();
                dynamic requestData = JsonConvert.DeserializeObject(jsonRequestData);
    
                if(requestData?.orderId != null)
                    return (ActionResult)new OkObjectResult($"Processing order with id {requestData.orderId}");
                else
                    return new BadRequestObjectResult("Please pass an orderId in the request body");
            }
        }
    }
    

    Hopes this helps.

    0 讨论(0)
提交回复
热议问题