11

A coding pitfall in implementing dependency injection in .NET azure functions

 1 year ago
source link: https://techcommunity.microsoft.com/t5/apps-on-azure-blog/a-coding-pitfall-in-implementing-dependency-injection-in-net/ba-p/3411110?WT_mc_id=DOP-MVP-4025064
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A coding pitfall in implementing dependency injection in .NET azure functions

A coding pitfall in implementing dependency injection in .NET azure functions

Published May 22 2022 02:08 PM 994 Views

.NET Azure Functions supports the dependency injection (DI) software design pattern to achieve Inversion of Control (IoC) between classes and their dependencies.  The official article explains how to implement dependency injection in Azure .NET Functions.



2 key points are summarized as below:

1.  Create a custom Startup.cs that inherits from FunctionsStartup class from the Microsoft.Azure.Functions.Extensions NuGet package.  This startup class is meant for setup and dependency registration only but not for using any of the registered services during startup process.   The startup class runs once and only once to build up the ServiceCollection when the function host starts up.

2.  Service lifetimes:

This blog will show you a coding pitfall in implementing dependency injection in .NET azure functions to help you better understand the registered service lifetime, which is one of the most important parts in the DI world.



Let us review the code first:





















The Problem:

The fruit delivery service hosted on the Azure App Service is operating well for some time,  then it would be broken with consistent 400 errors "The size of the request headers is too long" or equivalent messages.  The problem can be mitigated by a function app restart which typically means it is an application layer issue.



Root Cause Analysis:



Fixes:



Take-aways:



You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.

%3CLINGO-SUB%20id%3D%22lingo-sub-3411110%22%20slang%3D%22en-US%22%3EA%20coding%20pitfall%20in%20implementing%20dependency%20injection%20in%20.NET%20azure%20functions%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-3411110%22%20slang%3D%22en-US%22%3E%3CP%3E.NET%20Azure%20Functions%20supports%20the%20dependency%20injection%20(DI)%20software%20design%20pattern%20to%20achieve%20Inversion%20of%20Control%20(IoC)%20between%20classes%20and%20their%20dependencies.%26nbsp%3B%20The%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fazure-functions%2Ffunctions-dotnet-dependency-injection%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%22%3Eofficial%20article%3C%2FA%3E%20explains%20how%20to%20implement%20dependency%20injection%20in%20Azure%20.NET%20Functions.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E2%20key%20points%20are%20summarized%20as%20below%3A%3C%2FP%3E%0A%3CP%3E1.%26nbsp%3B%20Create%20a%20custom%20Startup.cs%20that%20inherits%20from%20FunctionsStartup%20class%20from%20the%20Microsoft.Azure.Functions.Extensions%20NuGet%20package.%26nbsp%3B%20This%20startup%20class%20is%20meant%20for%20setup%20and%20dependency%20registration%20only%20but%20not%20for%20using%20any%20of%20the%20registered%20services%20during%20startup%20process.%26nbsp%3B%20%26nbsp%3BThe%20startup%20class%20runs%20once%20and%20only%20once%20to%20build%20up%20the%20ServiceCollection%20when%20the%20function%20host%20starts%20up.%3C%2FP%3E%0A%3CP%3E2.%26nbsp%3B%20Service%20lifetimes%3A%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3ETransient%3A%20Transient%20services%20are%20created%20upon%20each%20resolution%20of%20the%20service.%3C%2FLI%3E%0A%3CLI%3EScoped%3A%20The%20scoped%20service%20lifetime%20matches%20a%20function%20execution%20lifetime.%20Scoped%20services%20are%20created%20once%20per%20function%20execution.%20Later%20requests%20for%20that%20service%20during%20the%20execution%20reuse%20the%20existing%20service%20instance.%3C%2FLI%3E%0A%3CLI%3ESingleton%3A%20The%20singleton%20service%20lifetime%20matches%20the%20host%20lifetime%20and%20is%20reused%20across%20function%20executions%20on%20that%20instance.%20Singleton%20lifetime%20services%20are%20recommended%20for%20connections%20and%20clients%2C%20for%20example%20DocumentClient%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3Eor%20HttpClient%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3Einstances.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3EThis%20blog%20will%20show%20you%20a%20coding%20pitfall%20in%20implementing%20dependency%20injection%20in%20.NET%20azure%20functions%20to%20help%20you%20better%20understand%20the%20registered%20service%20lifetime%2C%20which%20is%20one%20of%20the%20most%20important%20parts%20in%20the%20DI%20world.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CEM%3E%3CSTRONG%3ELet%20us%20review%20the%20code%20first%3A%3C%2FSTRONG%3E%3C%2FEM%3E%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EStartup.cs%3A%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Epublic%20class%20Startup%20%3A%20FunctionsStartup%0A%7B%0A%20%20%20public%20override%20void%20Configure(IFunctionsHostBuilder%20builder)%0A%20%20%20%7B%0A%20%20%20%20%20%20%20%20new%20ConfigurationBuilder()%0A%20%20%20%20%20%20%20%20%20%20%20%20.SetBasePath(Environment.CurrentDirectory)%0A%20%20%20%20%20%20%20%20%20%20%20%20.AddJsonFile(%22local.settings.json%22%2C%20true%2C%20true)%0A%20%20%20%20%20%20%20%20%20%20%20%20.AddEnvironmentVariables()%0A%20%20%20%20%20%20%20%20%20%20%20%20.Build()%3B%0A%20%20%20%20%20%20%20%20builder.Services.AddLogging()%3B%0A%20%20%20%20%20%20%20%20AppleDeliveryService%20appleDeliveryService%20%3D%20new%20AppleDeliveryService(new%20HttpClient())%3B%0A%20%20%20%20%20%20%20%20BananaDeliveryService%20bananaDeliveryService%20%3D%20new%20BananaDeliveryService(new%20HttpClient())%3B%0A%20%20%20%20%20%20%20%20builder.Services.AddTransient%3CIFRUITDELIVERYCOORDINATOR%3E(cls%20%3D%26gt%3B%20new%20FruitDeliveryCoordinator(bananaDeliveryService%2C%20appleDeliveryService))%3B%0A%20%20%20%20%7D%0A%7D%E2%80%8B%3C%2FIFRUITDELIVERYCOORDINATOR%3E%3C%2FCODE%3E%3C%2FPRE%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EDependency%20services%20to%20be%20registered%3A%26nbsp%3B%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3E%20%20%20%20public%20interface%20IBananaDeliveryService%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20void%20DeliverBanana(string%20address%2C%20ILogger%20log)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20interface%20IAppleDeliveryService%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20void%20DeliverApple(string%20address%2C%20ILogger%20log)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20interface%20IFruitDeliveryCoordinator%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20void%20DeliverFruit(string%20address%2C%20ILogger%20log)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20class%20BananaDeliveryService%20%3A%20IBananaDeliveryService%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20readonly%20HttpClient%20client%3B%0A%20%20%20%20%20%20%20%20public%20string%20Random%20%7B%20get%3B%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20BananaDeliveryService(HttpClient%20client)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.client%20%3D%20client%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Random%20%3D%20Guid.NewGuid().ToString()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20void%20DeliverBanana(string%20address%2C%20ILogger%20log)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20log.LogInformation(%22Deliver%20banana%20by%20guid%20-%20%7BRandom%7D%22%2C%20Random)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20this.client.DefaultRequestHeaders.Add(%22Banana-Delivery-Key%22%2C%20%22Banana-Delivery-Value%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20HttpContent%20httpContent%20%3D%20new%20StringContent(JsonConvert.SerializeObject(requestBody)%2C%20Encoding.UTF8%2C%20%22application%2Fjson%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20HttpResponseMessage%20response%20%3D%20await%20_client.PostAsync(url%2C%20httpContent)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20string%20responseJson%20%3D%20await%20response.Content.ReadAsStringAsync()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20class%20AppleDeliveryService%20%3A%20IAppleDeliveryService%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20readonly%20HttpClient%20client%3B%0A%20%20%20%20%20%20%20%20public%20string%20Random%20%7B%20get%3B%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20AppleDeliveryService(HttpClient%20client)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.client%20%3D%20client%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Random%20%3D%20Guid.NewGuid().ToString()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20void%20DeliverApple(string%20address%2C%20ILogger%20log)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20log.LogInformation(%22Deliver%20apple%20by%20guid%20-%20%7BRandom%7D%22%2C%20Random)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20this.client.DefaultRequestHeaders.Add(%22Apple-Delivery-Key%22%2C%20%22Apple-Delivery-Value%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20HttpContent%20httpContent%20%3D%20new%20StringContent(JsonConvert.SerializeObject(requestBody)%2C%20Encoding.UTF8%2C%20%22application%2Fjson%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20HttpResponseMessage%20response%20%3D%20await%20_client.PostAsync(url%2C%20httpContent)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20string%20responseJson%20%3D%20await%20response.Content.ReadAsStringAsync()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20public%20class%20FruitDeliveryCoordinator%20%3A%20IFruitDeliveryCoordinator%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20readonly%20IBananaDeliveryService%20bananaDeliveryService%3B%0A%20%20%20%20%20%20%20%20private%20readonly%20IAppleDeliveryService%20appleDeliveryService%3B%0A%0A%20%20%20%20%20%20%20%20public%20string%20Random%20%7B%20get%3B%20%7D%0A%0A%20%20%20%20%20%20%20%20public%20FruitDeliveryCoordinator(IBananaDeliveryService%20bananaDeliveryService%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20IAppleDeliveryService%20appleDeliveryService)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.bananaDeliveryService%20%3D%20bananaDeliveryService%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.appleDeliveryService%20%3D%20appleDeliveryService%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Random%20%3D%20Guid.NewGuid().ToString()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%0A%20%20%20%20%20%20%20%20public%20void%20DeliverFruit(string%20address%2C%20ILogger%20log)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20log.LogInformation(%22Fruit%20Coordinator%20by%20guid%20-%20%7BRandom%7D%22%2C%20Random)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20bananaDeliveryService.DeliverBanana(address%2C%20log)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20appleDeliveryService.DeliverApple(address%2C%20log)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%7D%E2%80%8B%3C%2FCODE%3E%3C%2FPRE%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EA%20simple%20http%20trigger%20function%3A%26nbsp%3B%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3E%20%20%20%20public%20class%20DeliveryFunction%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20private%20readonly%20ILogger%3CDELIVERYFUNCTION%3E%20log%3B%0A%20%20%20%20%20%20%20%20private%20readonly%20IFruitDeliveryCoordinator%20fruitDeliveryCoordinator%3B%0A%0A%20%20%20%20%20%20%20%20public%20DeliveryFunction(IFruitDeliveryCoordinator%20fruitDeliveryCoordinator%2C%20ILogger%3CDELIVERYFUNCTION%3E%20log)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.fruitDeliveryCoordinator%20%3D%20fruitDeliveryCoordinator%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.log%20%3D%20log%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%5BFunctionName(%22FruitDeliveryFunction%22)%5D%0A%20%20%20%20%20%20%20%20public%20async%20Task%3CIACTIONRESULT%3E%20Run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5BHttpTrigger(AuthorizationLevel.Anonymous%2C%20%22get%22%2C%20%22post%22%2C%20Route%20%3D%20null)%5D%20HttpRequest%20req)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20log.LogInformation(%22C%23%20HTTP%20trigger%20function%20processed%20a%20request.%22)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20fruitDeliveryCoordinator.DeliverFruit(%22Gru%20address%22%2C%20log)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20string%20name%20%3D%20req.Query%5B%22name%22%5D%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20string%20requestBody%20%3D%20await%20new%20StreamReader(req.Body).ReadToEndAsync()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20dynamic%20data%20%3D%20JsonConvert.DeserializeObject(requestBody)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%3D%20name%20%3F%3F%20data%3F.name%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20string%20responseMessage%20%3D%20string.IsNullOrEmpty(name)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3F%20%22This%20HTTP%20triggered%20function%20executed%20successfully.%20Pass%20a%20name%20in%20the%20query%20string%20or%20in%20the%20request%20body%20for%20a%20personalized%20response.%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3A%20%24%22Hello%2C%20%7Bname%7D.%20This%20HTTP%20triggered%20function%20executed%20successfully.%22%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20OkObjectResult(responseMessage)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%E2%80%8B%3C%2FIACTIONRESULT%3E%3C%2FDELIVERYFUNCTION%3E%3C%2FDELIVERYFUNCTION%3E%3C%2FCODE%3E%3C%2FPRE%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CEM%3E%3CSTRONG%3EThe%20Problem%3A%3C%2FSTRONG%3E%3C%2FEM%3E%3C%2FP%3E%0A%3CP%3EThe%20fruit%20delivery%20service%20hosted%20on%20the%20Azure%20App%20Service%20is%20operating%20well%20for%20some%20time%2C%26nbsp%3B%20then%20it%20would%20be%20broken%20with%20consistent%20400%20errors%20%22The%20size%20of%20the%20request%20headers%20is%20too%20long%22%20or%20equivalent%20messages.%26nbsp%3B%20The%20problem%20can%20be%20mitigated%20by%20a%20function%20app%20restart%20which%20typically%20means%20it%20is%20an%20application%20layer%20issue.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CEM%3E%3CSTRONG%3ERoot%20Cause%20Analysis%3A%3C%2FSTRONG%3E%3C%2FEM%3E%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EIn%20the%20Startup.cs%20where%20we%20register%20the%20dependency%20services%2C%26nbsp%3B%20the%26nbsp%3BAppleDeliveryService%20and%20BananaDeliveryService%20are%20instantiated%20using%26nbsp%3B%3CCODE%3Enew%3C%2FCODE%3Ekeyword%20where%20the%26nbsp%3BFruitDeliveryCoordinator%20was%26nbsp%3Binjected%20as%20a%20transient%20service.%26nbsp%3B%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Ebuilder.Services.AddLogging()%3B%0A%0AAppleDeliveryService%20appleDeliveryService%20%3D%20new%20AppleDeliveryService(new%20HttpClient())%3B%0ABananaDeliveryService%20bananaDeliveryService%20%3D%20new%20BananaDeliveryService(new%20HttpClient())%3B%0A%0Abuilder.Services.AddTransient%3CIFRUITDELIVERYCOORDINATOR%3E(cls%20%3D%26gt%3B%20new%20FruitDeliveryCoordinator(bananaDeliveryService%2C%20appleDeliveryService))%3B%E2%80%8B%3C%2FIFRUITDELIVERYCOORDINATOR%3E%3C%2FCODE%3E%3C%2FPRE%3E%3C%2FLI%3E%0A%3CLI%3EConsequently%2C%26nbsp%3B%20as%20a%20transient%20registration%2C%20any%20time%20we%20request%20for%20the%20FruitDeliveryCoordinator%20class%20by%20a%20function%20invocation%2C%20DI%20will%20manage%20to%20create%20a%20new%20instance.%26nbsp%3BOn%20the%20other%20hand%2C%20the%20AppleDeliveryService%20and%20BananaDeliveryService%20are%20NOT%20managed%20by%20DI%20and%20they're%26nbsp%3Binstantiated%20once%20and%20only%20once%20when%20the%20function%20host%20was%20started%20up.%3C%2FLI%3E%0A%3CLI%3EWe%20can%20verify%20the%20described%20behavior%20by%20generating%20a%20random%20GUID%20in%20the%20constructors%20of%20the%20dependency%20services%20and%20ingesting%20the%20logging%20dependencies%20in%20the%20startup%2C%26nbsp%3B%20then%20we%20can%20tell%20how%20many%20times%20the%20constructors%20are%20called%20by%20comparing%20the%20content%20of%20the%20generated%20random%20GUID.%26nbsp%3B%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20image-alt%3D%22DI_Pitfall2.png%22%20style%3D%22width%3A%20999px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F374005i8B62255A27B3238D%2Fimage-size%2Flarge%3Fv%3Dv2%26amp%3Bpx%3D999%22%20role%3D%22button%22%20title%3D%22DI_Pitfall2.png%22%20alt%3D%22DI_Pitfall2.png%22%20%2F%3E%3C%2FSPAN%3E%3CSPAN%3EFrom%20the%20red%20box%20in%20the%20above%20screenshot%2C%26nbsp%3B%20each%20time%20it%20creates%20a%20new%20FruitDeliveryCoordinator%20instance%20with%20a%20different%20GUID%20--%20it%20is%20managed%20by%20DI%20because%20of%20the%20transient%20service%20lifetimes%2C%26nbsp%3B%20while%20it%20is%20referencing%20the%20same%20AppleDeliveryService%20and%20BananaDeliveryService%20instance%20with%20the%20same%20GUID%20--%20no%20DI%20happening%20for%20those%20services%20and%20they%20are%26nbsp%3Binstantiated%20during%20startup.%3C%2FSPAN%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CUL%3E%0A%3CLI%3ESame%20thing%20happens%20to%20the%20HttpClient%2C%26nbsp%3B%20take%20AppleDeliveryService%20as%20an%20example%2C%26nbsp%3B%20it%20is%20using%3CCODE%3Enew%3C%2FCODE%3E%26nbsp%3Bkeyword%20as%20a%20part%20of%20the%20AppleDeliveryService%20instantiation%2C%20so%20a%20single%20instance%20is%20created%20when%20the%20function%20host%20is%20started%20up%2C%20hence%20all%20of%20the%20transient%20AppleDeliveryService%20are%20referencing%20the%20same%20instance%20of%20httpclient.%26nbsp%3B%20If%20one%20call%20many%20times%20of%20%E2%80%9Cclient.DefaultRequestHeaders.Add()%E2%80%9D%2C%20it%20will%20have%20the%20header%20get%20longer%20and%20longer%20as%20it%20appends%20the%20new%20value%20instead%20of%20replacing%20it%2C%26nbsp%3B%20eventually%20hit%20the%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Ftroubleshoot%2Fdeveloper%2Fwebapps%2Fiis%2Fiisadmin-service-inetinfo%2Fhttpsys-registry-windows%23registry-keys%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%22%3EmaxRequestBytes%3C%2FA%3E%20restricted%20by%20IIS%20and%20the%20web%20server%20will%20return%20400%20Bad%20Request.%3CP%3E%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20image-alt%3D%22DI_Pitfall1.png%22%20style%3D%22width%3A%20999px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F373924iAAB26735604612C1%2Fimage-size%2Flarge%3Fv%3Dv2%26amp%3Bpx%3D999%22%20role%3D%22button%22%20title%3D%22DI_Pitfall1.png%22%20alt%3D%22DI_Pitfall1.png%22%20%2F%3E%3C%2FSPAN%3E%3C%2FP%3E%0A%3C%2FLI%3E%0A%3CLI%3EWe%20can%20verify%20the%20described%20behavior%20by%20attaching%20remote%20debugger%20or%20using%20logging%20output.%20However%2C%20capturing%20network%20trace%20won't%20help%20as%20all%20of%20the%20outbound%20traffic%20are%20ssl%20encrypted.%26nbsp%3B%20From%20the%20screenshot%20below%2C%26nbsp%3B%20there%20are%20many%20duplicated%20records%20in%20the%20DefaultRequestHeaders%20of%20the%20single%20httpclient%20instance.%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20image-alt%3D%22DI_Pitfall3.png%22%20style%3D%22width%3A%20999px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F374007iD8EEACBDE61A6A6B%2Fimage-size%2Flarge%3Fv%3Dv2%26amp%3Bpx%3D999%22%20role%3D%22button%22%20title%3D%22DI_Pitfall3.png%22%20alt%3D%22DI_Pitfall3.png%22%20%2F%3E%3C%2FSPAN%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CEM%3E%3CSTRONG%3EFixes%3A%3C%2FSTRONG%3E%3C%2FEM%3E%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EThe%20easiest%20way%20to%20fix%20the%20400%20errors%20is%20to%20move%20the%26nbsp%3BAppleDeliveryService%20and%20BananaDeliveryService%26nbsp%3Binstantiation%20into%20the%20transient%20service%20registration%20of%20FruitDeliveryCoordinator%2C%26nbsp%3B%20as%20the%20screenshot%20shown%20below.%26nbsp%3B%20So%20each%20time%20the%20DI%20creates%20a%20FruitDeliveryCoordinator%20instance%2C%26nbsp%3B%20it%20will%20indirectly%20create%20an%20AppleDeliveryService%2C%20BananaDeliveryService%20and%20httpclient%20instance%20on%20the%20fly.%26nbsp%3B%20The%20request%20header%20accumulation%20won't%20happen%20since%20it%20is%20no%20longer%20to%20be%20a%20single%20httpclient%20anymore.%3CBR%20%2F%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Ebuilder.Services.AddTransient%3CIFRUITDELIVERYCOORDINATOR%3E(cls%20%3D%26gt%3B%20new%20FruitDeliveryCoordinator(new%20BananaDeliveryService(new%20HttpClient())%2C%20new%20AppleDeliveryService(new%20HttpClient())))%3B%E2%80%8B%3C%2FIFRUITDELIVERYCOORDINATOR%3E%3C%2FCODE%3E%3C%2FPRE%3EOr%2C%26nbsp%3B%20an%20equivalent%20fix%20is%20to%20ingest%20all%20of%20the%20dependencies%20as%20transient%20services%2C%26nbsp%3B%20%26nbsp%3Bthe%20request%20header%20accumulation%20won't%20happen%20as%20well%20since%20the%20httpclient%20instance%20is%20no%20longer%20to%20be%20a%20single%20instance.%26nbsp%3B%26nbsp%3B%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Ebuilder.Services.AddTransient%3CIBANANADELIVERYSERVICE%3E()%3B%0Abuilder.Services.AddTransient%3CIAPPLEDELIVERYSERVICE%3E()%3B%0Abuilder.Services.AddTransient%3CIFRUITDELIVERYCOORDINATOR%3E()%3B%E2%80%8B%3C%2FIFRUITDELIVERYCOORDINATOR%3E%3C%2FIAPPLEDELIVERYSERVICE%3E%3C%2FIBANANADELIVERYSERVICE%3E%3C%2FCODE%3E%3C%2FPRE%3EHowever%2C%26nbsp%3B%20both%20of%20the%20fixes%20smell%20%3CSTRONG%3EBAD%3C%2FSTRONG%3E%2C%26nbsp%3B%20because%20it%20violates%20the%20guidelines%20of%20developing%20an%20azure%20function%20that%20%3CI%3ENOT%3C%2FI%3E%3CSPAN%3E%26nbsp%3Bto%26nbsp%3B%3C%2FSPAN%3Ecreate%20a%20new%20client%20with%20every%20function%20invocation%2C%26nbsp%3B%20for%20a%20function%20app%20hosted%20on%20the%20Azure%20platform%2C%26nbsp%3B%20it%20would%26nbsp%3B%3CSPAN%3Ehold%20more%20connections%20than%20necessary%20and%20eventually%20lead%20to%20the%20SNAT%20port%20exhaustion%20issue%3C%2FSPAN%3E%3CSPAN%3E%2C%26nbsp%3B%20more%20explanations%20about%20the%20SNAT%20port%20exhaustion%20can%20be%20found%20at%20the%20%3CA%20href%3D%22https%3A%2F%2F4lowtherabbit.github.io%2Fblogs%2F2019%2F10%2FSNAT%2F%22%20target%3D%22_self%22%20rel%3D%22nofollow%20noopener%20noreferrer%22%3ESNAT%20official%20blog%3C%2FA%3E.%26nbsp%3B%3C%2FSPAN%3E%3C%2FLI%3E%0A%3CLI%3E%3CSPAN%3E%3CSPAN%3EAn%20acceptable%20fix%20is%20to%20r%3C%2FSPAN%3E%3C%2FSPAN%3E%3CSPAN%3E%3CSPAN%3Eegister%20the%20AppleDeliveryService%20and%20BananaDeliveryService%20dependencies%20with%20singleton%20service%20lifetimes%2C%26nbsp%3B%20this%20is%20an%20optional%20step%20because%20it%20is%20somehow%20equivalent%20to%20the%20original%26nbsp%3B%3CCODE%3Enew%3C%2FCODE%3Einstantiation%20approach%20but%20it%20is%20more%20elegant.%3C%2FSPAN%3E%3C%2FSPAN%3E%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Ebuilder.Services.AddSingleton%3CIBANANADELIVERYSERVICE%3E()%3B%0Abuilder.Services.AddSingleton%3CIAPPLEDELIVERYSERVICE%3E()%3B%0Abuilder.Services.AddTransient%3CIFRUITDELIVERYCOORDINATOR%3E()%3B%3C%2FIFRUITDELIVERYCOORDINATOR%3E%3C%2FIAPPLEDELIVERYSERVICE%3E%3C%2FIBANANADELIVERYSERVICE%3E%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3EThen%20add%20an%20if%20clause%20to%20check%20if%20the%20same%20request%20header%20already%20exists%20in%20the%20DefaultRequestHeaders%20collection%20of%20the%20httpclient.%26nbsp%3B%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-csharp%22%3E%3CCODE%3Eif%20(!this.client.DefaultRequestHeaders.Contains(%22Banana-Delivery-Key%22))%0A%7B%0A%20%20%20this.client.DefaultRequestHeaders.Add(%22Banana-Delivery-Key%22%2C%20%22Banana-Delivery-Value%22)%3B%0A%7D%E2%80%8B%3C%2FCODE%3E%3C%2FPRE%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CEM%3E%3CSTRONG%3ETake-aways%3A%3C%2FSTRONG%3E%3C%2FEM%3E%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EIt%20is%20recommended%20to%20use%20DI%20all%20the%20way%20down%20for%20all%20of%20the%20registered%20services%2C%26nbsp%3B%20otherwise%2C%26nbsp%3B%20you%20need%20to%20be%20careful%20when%20mixing%20the%3CCODE%3Enew%3C%2FCODE%3Einstantiation%20with%20the%20DI%20approach.%26nbsp%3B%20DI%20only%20works%20for%20classes%20created%20by%20DI.%26nbsp%3B%20If%20you%20create%20classes%20with%3CCODE%3Enew%3C%2FCODE%3Ekeyword%2C%20there%20is%20no%20DI%20happening.%3C%2FLI%3E%0A%3CLI%3ESingleton%20lifetime%20services%20are%20highly%20recommended%20for%20connections%20and%20clients%2C%20for%20example%20DocumentClient%20or%20HttpClient%20instances.%20In%20fact%2C%20it%20is%26nbsp%3Bamong%20the%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fazure-functions%2Ffunctions-best-practices%3Ftabs%3Dcsharp%23plan-for-connections%22%20target%3D%22_self%22%20rel%3D%22noopener%20noreferrer%22%3Efunction%20app%20best%20practices%3C%2FA%3E%2C%26nbsp%3B%20in%20order%20to%20better%20manage%20the%20connections%20to%20avoid%20SNAT%20issues.%3C%2FLI%3E%0A%3CLI%3EHttpClient.DefaultRequestHeaders%20is%20used%20to%20add%20headers%20for%20ALL%20requests%2C%26nbsp%3B%20it%20is%20typically%20seen%20when%20instantiating%20a%20single%20httpclient%20instance%20and%20apply%20a%20common%20header%20for%20all%20requests.%26nbsp%3B%26nbsp%3BHttpRequestMessage.Headers%20is%20used%20to%20add%20headers%20PER%20request.%3C%2FLI%3E%0A%3CLI%3EThe%20coding%20pitfall%20described%20in%20this%20blog%20can%20be%20easily%20identified%20in%20an%20earlier%20stage%20if%20there%20is%20a%20load%20test%20or%20performance%20test%20within%20the%20development%20cycle%2C%26nbsp%3B%20otherwise%20after%20going%20into%20production%20it%20is%20difficult%20to%20identify%20such%20issues%20without%20affecting%20availability%20because%20all%20of%20the%20outbound%20traffic%20is%20encrypted%20--%20capturing%20network%20packets%20won't%20help.%26nbsp%3B%20%26nbsp%3BSo%20it%20is%20highly%20recommended%20to%20involve%20load%20test%20as%20a%20part%20of%20the%20dev%20sprint%20in%20order%20to%20identify%20problems%20as%20early%20as%20possible%2C%26nbsp%3B%20usually%20such%20kind%20of%20problems%20will%20lead%20to%20a%20major%20code%20refactoring%20work%20and%20even%20application%20re-architecture%2C%26nbsp%3B%20either%20of%20them%20would%20bring%20much%20efforts%20of%20the%20regression%20tests.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-TEASER%20id%3D%22lingo-teaser-3411110%22%20slang%3D%22en-US%22%3E%3CP%3E%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20image-alt%3D%22DI_Pitfall4.png%22%20style%3D%22width%3A%20971px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Ftechcommunity.microsoft.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F374015i2408F89CFEF0BA98%2Fimage-size%2Flarge%3Fv%3Dv2%26amp%3Bpx%3D999%22%20role%3D%22button%22%20title%3D%22DI_Pitfall4.png%22%20alt%3D%22DI_Pitfall4.png%22%20%2F%3E%3C%2FSPAN%3E%3C%2FP%3E%3C%2FLINGO-TEASER%3E%3CLINGO-LABS%20id%3D%22lingo-labs-3411110%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3EAzure%20Functions%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E

Version history
Last update:

‎May 22 2022 06:31 AM

Updated by:
Labels

Share


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK