DISCLAIMER: This post is purely a personal opinion, not representing or affiliating my employer’s.
UPDATE (May 9, 2019): During the //Build event, Jeff Hollan officially announced this dependency injection. Here's the official document: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection.
UPDATE (Marth 28, 2019): As of the current runtime version,
2.0.12353.0
, this constructor injection method has been pulled off for stabilisation purpose. Here's the conversation between myself and Jeff Hollan from Azure Functions Team.
OH NOOOOO @AzureFunctions has pulled off the constructor injection feature... or changed the way of doing. I should figure that out.
— ~/☁️/🥑/justinyoo/.dotnet/azure (@justinchronicle) March 28, 2019
I think it broke and we are fixing - know we are making last changes and improvements. Not quite sure on what you’re seeing. It’s not quite publically documented yet but in the final stretch
— Jeff Hollan (@jeffhollan) March 28, 2019
Err OK. I should wait until it’s publicly announced. I was too early... 😅 Can I expect it at the //Build event?
— ~/☁️/🥑/justinyoo/.dotnet/azure (@justinchronicle) March 28, 2019
It may not be. I know there were some bugs that were mentioned earlier but possible were caught before rolling out. In regards to us putting final touches on DI. Also plan to expose function specific builder interfaces
— Jeff Hollan (@jeffhollan) March 28, 2019
In January 2019, Azure Functions Team has released a new version of its runtime, 2.0.12265
.
I wasn't able to believe what that meant at the first place.
Does that mean we now can get rid of the infamous
static
modifier from both classes and methods?
In fact, Fabio from Azure Functions Team showed a demo at Ignite 2018, so I wasn't that surprised at all but thought it would be a matter of time. Rather, I was more excited at the new beginning of Azure Functions runtime.
Maybe it was just me who didn't pay too much attention on this. But, throughout this post, I'm going to walk through how we can chuck out the static
modifier from the Functions code, and use a constructor for dependency injection.
The sample code used in this post can be found at here.
Writing Instance Method
In C#, classes, fields, properties or methods can have the static
modifiers. If we put this, regardless the class is instantiated or not, we can directly access to those fields, properties or methods. On the other hand, without the static
modifier, we can access to them only after the class is instantiated. We call the method of the instance as Instance Method
. Constructors can only be useful when we define a class without the static
modifier, with regards to dependency injections.
The problem that Azure Functions has been keeping so far is that Function classes always comes with the static
modifier. That has forced each method in the class to have the same static
modifier as well. This brought about a lot of headaches when considering dependency injections and, in order to sort out this issue, either property injections using a service locator or method injections using custom binding extensions was introduced and these were, in general, not very recommended unless necessary.
Now, the new Azure Functions runtime enables to get rid of the static
modifier. Let's have a look at the code below:
public class SampleHttpTrigger | |
{ | |
[FunctionName(nameof(GetSamples))] | |
public async Task<IActionResult> GetSamples( | |
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "samples")] HttpRequest req, | |
ILogger log) | |
{ | |
var name = req.Query["name"]; | |
return new OkObjectResult($"Hello {name}"); | |
} | |
} |
Does it look different? Maybe not. But, when you closely look at the class definition – between public
and class
– and method definition – between public async
and Task<IActionResult>
, there's no static
modifier any more. Wow, how does this even work? Let's run the Function app. If it runs properly, it should stop at the debugging break point:
And its result will look like Hello [NAME]
.
It really is the instance method! This brings up massive implication that we can inject dependencies through constructors!
Injecting Dependencies via Property
If you use the library, Aliencube.AzureFunctions.Extensions.DependencyInjection, property injection can be used. I wrote a blog post around this property injection a while ago.
public static class SampleHttpTrigger1 | |
{ | |
public static IFunctionFactory Factory { get; set; } = new FunctionFactory(new AppModule()); | |
[FunctionName(nameof(GetSamples1))] | |
public static async Task<IActionResult> GetSamples1( | |
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "samples1")] HttpRequest req, | |
ILogger log) | |
{ | |
var options = new GetSamplesFunctionOptions("sample 1"); | |
var result = await Factory.Create<IGetSamplesFunction, ILogger>(log) | |
.InvokeAsync<HttpRequest, IActionResult>(req, options) | |
.ConfigureAwait(false); | |
return result; | |
} | |
} |
The static
property implements the FunctionFactory
class through the IFunctionFactory
interface and it accepts AppModule
instance as its dependency, which registers all dependencies. The AppModule
class actually looks like this:
public class AppModule : Module | |
{ | |
public override void Load(IServiceCollection services) | |
{ | |
services.AddSingleton<AppSettings>(); | |
services.AddTransient<IGetSamplesFunction, GetSamplesFunction>(); | |
} | |
} |
Throughout AppModule
, the GetSamplesFunction
instance is registered as IGetSamplesFunction
and the function method invokes it. Now, let's keep the same structure but just remove the static
modifier.
Injecting Dependencies via Constructor
The approach that new Azure Functions runtime takes is to use StartUp
class, which is similar to what ASP.NET Core app does. There's no longer AppModule
necessary. Let's have a look at the code below:
// Registger assembly | |
[assembly: WebJobsStartup(typeof(StartUp))] | |
namespace Sample.FunctionApp | |
{ | |
// Implement IWebJobStartup interface. | |
public class StartUp : IWebJobsStartup | |
{ | |
public void Configure(IWebJobsBuilder builder) | |
{ | |
builder.Services.AddSingleton<AppSettings>(); | |
builder.Services.AddTransient<IGetSamplesFunction, GetSamplesFunction>(); | |
} | |
} | |
} |
The StartUp
class implements the IWebJobStartup
interface, which only defines one method, Configure(IWebjobBuilder builder)
. In addition to that, it uses the decorator, WebJobsStartupAttribute
targeting AttributeTargets.Assembly
, which registers dependencies as a part of starting up the host runtime. Let's see how dependencies are registered through the constructor.
public class SampleHttpTrigger2 | |
{ | |
private readonly IGetSamplesFunction _function; | |
public SampleHttpTrigger2(IGetSamplesFunction function) | |
{ | |
this._function = function ?? throw new ArgumentNullException(nameof(function)); | |
} | |
[FunctionName(nameof(GetSamples2))] | |
public async Task<IActionResult> GetSamples2( | |
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "samples2")] HttpRequest req, | |
ILogger log) | |
{ | |
this._function.Log = log; | |
var options = new GetSamplesFunctionOptions("sample 2"); | |
var result = await this._function | |
.InvokeAsync<HttpRequest, IActionResult>(req, options) | |
.ConfigureAwait(false); | |
return result; | |
} | |
} |
The function method still keeps the existing structure, but substitutes the existing static
property of IFunctionFactory
with the constructor so that we can minimise the changes on the existing code-base.
Unit Testing with Constructor Injection
Now, we know Azure Function allows constructor injection. Why does this really matter? Let's think of unit testing code. Even before the constructor injection being enabled, we were able to perform unit testing, but it was pretty ugly. Now, we have a constructor, which means the same unit testing can be done in more beautiful way. Let's have a look at the code below:
[TestMethod] | |
public async Task Given_Request_Response_Should_Return_Result() | |
{ | |
var logger = new Mock<ILogger>(); | |
var function = new Mock<IGetSamplesFunction>(); | |
function.SetupProperty(p => p.Log, logger.Object); | |
var result = new ObjectResult("hello world"); | |
function.Setup(p => p.InvokeAsync<HttpRequest, IActionResult>( | |
It.IsAny<HttpRequest>(), | |
It.IsAny<FunctionOptionsBase>())) | |
.ReturnsAsync(result); | |
var trigger = new SampleHttpTrigger2(function.Object); | |
var req = new Mock<HttpRequest>(); | |
var log = new Mock<ILogger>(); | |
var response = await trigger.GetSamples2(req.Object, log.Object).ConfigureAwait(false); | |
response.Should().BeOfType<ObjectResult>(); | |
var @return = response as ObjectResult; | |
@return.Value.Should().Be("hello world"); | |
} |
As the SampleHttpTrigger
class doesn't have the static
modifier any more, and it accepts a dependency through the constructor, we just simply mock the dependency, IGetSamplesFunction
here in this example. This is not different from any other unit testing approach at all. Can you see how easy the unit testing is now?
Azure Functions Keeps Growing!
So far, we've walked through how to use constructor injection for Azure Functions, without using the static
modifier. Like Azure WebJobs, Azure Functions, that works very well at the low level like replacing very simple workload, has now evolved to provide more sophisticated features and better development experiences. It is now 1) testable by proper dependency injection that has been dealt in this post, 2) deployable by providing container packaging, and 3) discoverable by Open API adoption. With these three aspects, I'm expecting there will be more use cases that we have never seen before.
This was originally posted at Platform Engineering Blog.