Hosting ASP.NET Core API inside Azure Functions (without TestServer)

I do believe that serverless hosting is capable of covering most of the needs for the most of web applications. Serverless architecture is great in relieving us from the pain of managing the computation resources and infrastructure, being surprisingly cost-efficient at the same time.

If you are an ASP.NET Core developer, I do expect that you might be well interested in an option of serverless hosting of your applications in Azure especially having 1,000,000 request executions per month for free.

Azure platform offers developers Azure functions as a serverless compute platform. While the initial purpose expected from Microsoft was to provide a quick and lightweight solution to manage various automation tasks, Azure Functions are still capable of running fully-functional ASP.NET Core applications. At the moment, such an option is not offered by existing templates, but you can apply the following instructions and samples to host your existing application inside serverless Azure Function. If you wish to skip the explanation, you may proceed straightly to the sample at GitHub.

Steps

At a high level we need to have 2 components:

  • ASP.NET Core application we want to be hosted in a serverless platform
  • Azure function application, in which we are going to host the existing app

During the following steps we will create and configure both applications:

  1. Get your development environment ready according to official instructions.
  2. Create Web API and Azure function projects (the following commands were executed in PowerShell, but I assume they will work out in Unix shell just fine):
# Create ASP.NET Core Web API project
dotnet new webapi -n "WebApp"
# Create Azure Function project
func init "FuncApp" --worker-runtime dotnet
# Proceed to its folder
cd FuncApp
# Create new HTTP Trigger function
func new --name "WebAppProxy" --language "C#" --template HttpTrigger
# At this point we need to ensure that both projects (WebApp and FuncApp) # target the same version of .NET Core
# so please open their *.csproj files and check the following tag:
# netcoreapp2.2
# Reference ASP.NET Core package
dotnet add package "Microsoft.AspNetCore.App"
# Reference Web API project
dotnet add reference "../WebApp/"

3. Now we need to make a small adjustment for Web API project in Startup.cs:

/* This method gets called by the runtime.
   Use this method to add services to the container. */
public void ConfigureServices(IServiceCollection services)
{
   services
/* Logging is required */
      .AddLogging() 
    .AddMvc()
/* Explicitly add WebApp assembly as application part
   This is required because WebApp isn't executing assembly
   when being hosted as Azure Function */
      .AddApplicationPart(Assembly.Load("WebApp"));
}

4. At last it’s time to implement Azure Function in WebAppProxy.cs so that it delegates request handling to WebApp:

[FunctionName(nameof(WebAppProxy))]
public static async Task<IActionResult> Run(
    [HttpTrigger(
        AuthorizationLevel.Anonymous,
        "get", "post", "put", "patch",
        Route = "{*any}")]
    HttpRequest req,
    ExecutionContext context,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    var configRoot = new ConfigurationBuilder()
        .SetBasePath(context.FunctionAppDirectory)
        .AddEnvironmentVariables()
        .Build();

    var hostingEnvironment = new HostingEnvironment()
    {
        ContentRootPath = context.FunctionAppDirectory
    };

    /* Add required services into DI container */
    var config = configRoot.GetWebJobsRootConfiguration();
    var services = new ServiceCollection();
    services.AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore"));
    services.AddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
    services.AddSingleton<IHostingEnvironment>(hostingEnvironment);
    services.AddSingleton<IConfiguration>(config);

    /* Instantiate standard ASP.NET Core Startup class */
    var startup = new Startup(config);
    /* Add web app services into DI container */
    startup.ConfigureServices(services);
    /* Initialize DI container */
    var serviceProvider = services.BuildServiceProvider();
    /* Initialize Application builder */
    var appBuilder = new ApplicationBuilder(
           serviceProvider, new FeatureCollection());
    /* Configure the HTTP request pipeline */
    startup.Configure(appBuilder, hostingEnvironment);
    /* Build request handling function */
    var requestHandler = appBuilder.Build();
    /* Set DI container for HTTP Context */
    req.HttpContext.RequestServices = serviceProvider;
    /* Handle HTTP request */
    await requestHandler(req.HttpContext);
    /* This dummy result does nothing.
         HTTP response is already set by requestHandler */
    return new EmptyResult();
}

5. Finally, it’s time to run Azure function:

func settings add FUNCTIONS_WORKER_RUNTIME dotnet
func host start -build

At this point I do expect you will receive test data calling the following endpoint: http://localhost:7071/api/values

Closing

I must confess that the described approach isn’t really straightforward, and I had to spend some time to get it working. I hope that it will be useful for developers looking for the cheapest way of application hosting in Azure, until Microsoft offers a cleaner approach.

References

Official Azure Functions documentation:

https://docs.microsoft.com/en-us/azure/azure-functions/

Sample source code:

https://github.com/NicklausBrain/serverless-core-api

Share article: