Azure Functions v2: The .NET Core Way
Ricardo Alves
Software Engineer | .NET Specialist | Tech Lead | Software Architect | Writer | Entrepreneur - providing services in UK and Europe
I think we can all agree that Azure Functions are awesome. Of course they have their restrictions and caveats, like cold start and a limited amount of memory available. However for most of tasks, those are not really impediments and in the end some advantages like cost, scaling and infrastructure make serverless apps a very good choice for a huge set of solutions.
Not long ago Microsoft introduced version 2.0, implemented on the top of .NET Core. In this article I will focus on this new version of Azure Functions and how it evolved from version 1.
.NET Core: Cross-platform & Performance
When Microsoft released the first versions of Azure Functions they were basically a .NET Framework application running somewhere else and reacting to some triggers. This is still pretty much the same in version 2 with a key difference: they now run as a .NET Core application. For the ones whom already worked with .NET Core the advantages are quite clear.
The first big advantage of using .NET Core as the underlying framework is the possibility to have your functions working not only on Windows but also in all supported platforms as MacOS and Linux. This brings to developers the option to chose where to develop and test their application. Another good side-effect is performance, being a well known fact how faster .NET Core is compared to .NET Framework in many aspects.
The big difference here is the fact an Azure Functions project doesn't have a Program class neither a Main function, because the "application start" is made under the covers by Azure. However it does have an optional Startup class where you can configure how your functions are gonna be initialized, especially regarding dependency injection configuration (we'll talk more about DI in the next topic).
This class is slightly different than it is in a conventional .NET Core application. First, as we don't have a Program class anymore there's not a UseStartup statement anywhere else, thus the Startup needs to have an annotation explicitly declaring it as the startup class using an assembly annotation where we pass the type of our class (it really doesn't need to be called Startup, you know). Finally, if you want to use DI, the class needs to be inherited from Microsoft.Azure.Functions.Extensions.DependencyInjection.FunctionStartup.
It should look something like this:
Dependency Injection
As expected, running as a .NET Core application it also makes available all of its inherent functionalities, including the native dependency injection mechanism. Before that, DI wasn't even officially supported in Azure Functions and many developers have been forced to implement their own solution. But now dog days are gone and we can rely on all advantages .NET Core DI.
As shown in the topic above, simply inherit your Startup class from FunctionStartup, override the Configure method and use IFunctionHostBuilder the same way you'd use IServiceCollection in a regular .NET Core application.
Note: Remember though, the more dependencies your function have the longer will be your function app cold start.
Languages
Here we may have some limitations if compared to Azure Functions version 1, although I believe most of developers won't ever notice:
- All functions inside a same function app need to be written using the same language. So if you create your app and choose C# you'll need to stick with it. This is supposed to speed up app initialization minimizing the infamous function app cold start.
- No more experimental languages. Say goodbye to Bash, Batch (.cmd) and PHP. F# is not supported anymore on the portal but you still can make your app using Visual Studio or VS Code and deploy the compiled version.
- On the other hand, say hello to Java 8!
Packages and Features
In order to increase performance, some bindings, triggers and features are not available out-of-the-box anymore. It means that depending on what your function reacts to or depends on, you'll have to include and register the corresponding package, which is a very trivial task nowadays.
In practice, only HTTP and timing bindings are automatically added to a new function app. All the other bindings need to be included by the developer. This way only what is really used is loaded at runtime, which means a better decoupling and also allows us to update the version of a binding independently of the rest.
Conclusion
It's been a year since Azure Functions v2 has been released and available and it's a big improvement compared to it predecessor. Although we can argue there's a lot of things to be improved yet (like better portal UI, storage triggers, direct function-to-function calls, etc), in my opinion Microsoft is moving in the right direction here.