Dependency injection in Azure Function v3
In this article, I am going to show how DI (Dependency Injection) can easily be achieved within an Azure Function project and explain its benefits. This article is a follow-on from the previous article where I set up a wireframe project and I will be using the same wireframe in this article.
Firstly, we must look at Dependency Injection. Dependency Injection (which I shall refer to as DI) is a software design pattern that allows us to develop loosely coupled code. The purpose of DI is to make code maintainable and reduce tight coupling between components.
The biggest advantages of DI are as follows:
DI is extremely easy to set up and greatly increases the reusability of code. In the example to come, we are going to create the following components and consume them in the application on run time.
Viewing the file structure as is shown below, right-click "Services", hover "Add" and click "Add a new class..." and name it "TestService.cs".
Once you have added the class, paste the following code into it, making sure to replace the entire class but not the namespace:
public class TestService
? ? {
? ? ? ? public string SayHello(string name)
? ? ? ? {
? ? ? ? ? ? string retVal = !string.IsNullOrEmpty(name) ? "Hello " + name + " from Injected Service" : "Oops, you did not provide a name!";
? ? ? ? ? ? return retVal;
? ? ? ? }
? ? }
Now, right-click on the folder called "Interfaces", hover "Add" and click "New Item...".
Next, in the search bar on the top right, search for "Interface" and click on the option that remains. Name the file "ITestService.cs" and click "Add".
Once you have added the interface, paste the following code into it, making sure to replace the entire interface but not the namespace:
public interface ITestService
? ? {
? ? ? ? public string SayHello(string name);
? ? }
Now, return to the "TestService.cs" class and tell it to inherit from the interface as follows:
public class TestService : ITestService
This is the only line we will need to modify in this file. Now that we have our interface and service set up, we now need to register them within our "Startup.cs" so that we can inject them wherever we need them! Once you have navigated to "Startup.cs", we will add the following code inside the "Configure" method:
builder.Services.AddSingleton<ITestService, TestService>();
Next step is to add our using statements as follows:
领英推荐
using Microsoft.Extensions.DependencyInjection;
using V3.Practice.Application.Interfaces;
using V3.Practice.Application.Services;
This will allow us to add a Singleton, Scoped or Transient to our application as well as allow us to reference our Interface and Service to allow us to register them. You may need to adjust the usings for the Interface and Service as your project may be named differently.
The "Startup.cs" should resemble the following now that we have added what we need:
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using V3.Practice.Application.Interfaces;
using V3.Practice.Application.Services;
[assembly: FunctionsStartup(typeof(V3.Practice.Startup))]
namespace V3.Practice
{
? ? public class Startup : FunctionsStartup
? ? {
? ? ? ? public override void Configure(IFunctionsHostBuilder builder)
? ? ? ? {
? ? ? ? ? ? builder.Services.AddSingleton<ITestService, TestService>();
? ? ? ? }
? ? }
}
The next step to achieving DI is to now use Constructor Injection which will allow us to use it anywhere in the class as the dependency is supplied through the class's constructor when creating the instance of the class. Navigate to the file named "TestFunction.DI.cs", we will be adding the dependency to the class constructor as follows:
First, we need to create a private variable for the service so we can reference it and consume it in our "TestFunction.cs" so above the constructor we place the following:
private ITestService _testService;
Now we need to modify the constructor so we can assign the Interface on class initialization, we will do as follows:
Modify the constructor as follows:
public TestFunction(ITestService testService)
? ? ? ? {
? ? ? ? ? ? _testService = testService;
? ? ? ? }
What we do in the code above, is we inject the dependency and assign it to our private variable so we can use it. Now in the "TestFunction.cs" we can call "_testService" anywhere in that page. Below is the default code for a function that I will be using to modify to call the Injected Service:
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
namespace V3.Practice.Functions.PracticeFunction
{
? ? public partial class TestFunction
? ? {
? ? ? ? [FunctionName("TestFunction")]
? ? ? ? [OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
? ? ? ? [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
? ? ? ? [OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
? ? ? ? [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
? ? ? ? public static async Task<IActionResult> Run(
? ? ? ? ? ? [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
? ? ? ? ? ? ILogger log)
? ? ? ? {
? ? ? ? ? ? log.LogInformation("C# HTTP trigger function processed a request.");
? ? ? ? ? ? string name = req.Query["name"];
? ? ? ? ? ? string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
? ? ? ? ? ? dynamic data = JsonConvert.DeserializeObject(requestBody);
? ? ? ? ? ? name = name ?? data?.name;
? ? ? ? ? ? string responseMessage = string.IsNullOrEmpty(name)
? ? ? ? ? ? ? ? ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
? ? ? ? ? ? ? ? : $"Hello, {name}. This HTTP triggered function executed successfully.";
? ? ? ? ? ? return new OkObjectResult(responseMessage);
? ? ? ? }
? ? }
}
The first thing we will need to do is to make the method non-static otherwise we will not be able to use our Injected Service. Then we will need to make a small modification after the "name" has been assigned from the requestBody and that modification is as follows:
Delete the variable and assignment of "responseMessage" and replace it with the following:
string responseMessage = _testService.SayHello(name);
Now when we run the Function and call it using either the Swagger UI or a Postman call, we will see the following on a call providing a name as a parameter:
And the following on a call without providing the name as a parameter:
And there we have it! We have no successfully Injected a Service using constructor injection and best practice! Please do leave feedback and a reaction if you found this useful or enjoyable! In my next article, I will look to showing the wireframe from the first article in a microservice pattern.
Until next time!
Declan
Senior Software Developer
3 年Very clear follow up for the first article. I would like to see also some unit tests!? What is your favourite framework for testing?