Hosting your ASP.NET Core MVC APIs inside Azure Functions
Image by

Hosting your ASP.NET Core MVC APIs inside Azure Functions

Azure Functions


Migrate your ASP.NET Core MVC APIs based application inside Azure Functions to leverage the Consumption plan billing model for your existing applications.

TLDR: Check the sample on GitHub.


This is mainly a proof of concept to show the possibilities. TestServer, which is used in this recipe, should be reviewed for thread safety/performance and a new custom server should be developed to use this recipe in production. Thanks to Christian Weyer for pointing me to this blog post which provides an alternative approach inspired from TestServer.



⏲️ Preparation👨‍🍳 Ready In
30 minutes1 hour
  1. Create  a new MVC API web app based on ASP.NET Core 2.1 by following this tutorial: Create a web API with ASP.NET Core MVC.
  2. Start your newly created application, make sure it starts properly.
  3. Edit the newly created project properties.
    Edit Project Properties
    Edit Project Properties
  4. Do the following changes:
    1. Change Project SDK to Microsoft.NET.Sdk.Razor:
      <Project Sdk="Microsoft.NET.Sdk.Razor">
    2. Add Razor Pages and Azure Functions configuration items inside the first PropertyGroup
    3. Add the following PackageReference items inside the first ItemGroup
        <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
        <PackageReference Include="Microsoft.AspNetCore.App" version="2.1.6" />
        <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
        <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
        <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
        <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.6" PrivateAssets="All" />
        <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.5.1" />
  5. Edit project launchSettings.json file
    Edit Launch Settings
    Edit Launch Settings
  6. Do the following changes:
    1. Delete iisSettings and keep only profiles.
    2. Inside profiles, remove IIS Express.
    3. Inside profiles, modify TodoApi to look like this: 
        "profiles": {
          "TodoApi": {
            "commandName": "Project",
            "commandLineArgs": "host start --pause-on-error",
            "environmentVariables": {
              "ASPNETCORE_ENVIRONMENT": "Development"
            "applicationUrl": "http://localhost:7072/"
  7. Add two files to the project root:
    1. Add host.json
        "version": "2.0",
        "extensions": {
          "http": {
            "routePrefix": ""
    2. Add local.settings.json
        "IsEncrypted": false,
        "Values": {
          "AzureWebJobsDisableHomepage": "true",  
          "FUNCTIONS_WORKER_RUNTIME": "dotnet",
          "AzureWebJobsStorage": "UseDevelopmentStorage=true",
          "AzureWebJobsDashboard": "UseDevelopmentStorage=true",
        "Host": {
          "LocalHttpPort": 7072,
          "CORS": "*"
    3. Make sure that both newly added files have a Copy to Output Directory action of Copy if newer.
      Copy if newer
      Copy if newer properties for new files
  8. Go back to project properties, and make sure that local.settings.json will not be published by making sure "CopyToPublishDirectory=Never" to it:
    <None Update="local.settings.json">
  9. Go into project properties and configure following Post-build events:
    Post-Build Events
    Post-Build Events
    xcopy /y "$(ProjectDir)appsettings*.json" "$(TargetDir)\"
  10. Add a new Azure Functions HTTP Trigger, by right clicking on your project and adding a New Azure Function.
    A New Azure Function
    Add a New Azure Function
    1. In the list of project items, select Azure Function, name it Host.cs and press Add.
      Create New Function
      Create New Function
    2. In the New Azure Function dialog, pick Http trigger, and select Access rights of Anonymous. This is where we will be adding our host code.
      Select HTTP Trigger
  11. In Host.cs, remove any code that was generated and create one Function to cover Root API path and another one to cover other roots.
      1. The first HttpTrigger will cover all routes except the root.  Important: Note that the HttpTrigger attribute route RegEx excludes some specific paths that are required for Azure Functions to operate normally. Make sure those paths are not used by the Razor Pages.
          public static async Task<HttpResponseMessage> RunAllPaths(
          CancellationToken ct,
          [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", "put", "patch", "options", Route = "{*x:regex(^(?!admin|debug|runtime).*$)}")]HttpRequestMessage req,
          ILogger log,
          ExecutionContext ctx)
             // [Insert code here] 
      2. The second HttpTrigger will cover only the root: 
          public static async Task<HttpResponseMessage> Root(
          CancellationToken ct,
          [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", "put", "patch", "options", Route = "/")]HttpRequestMessage req,
          ILogger log,
          ExecutionContext ctx)
             // [Insert code here] 
  12. In order to host our Razor Pages, we will need to leverage the TestServer class which is usually used for hosting inside unit tests. Keep in mind that this recipe is a proof of concept and that this approach will need to be revisited (refer to Disclaimer at the start of this recipe). In Host.cs, add two static members and a static constructor in order to initialize the Host stack:
    private static readonly TestServer Server;
    private static readonly HttpClient ServerHttpClient;
    static Host()
        var functionPath = Path.Combine(new FileInfo(typeof(Host).Assembly.Location).Directory.FullName, "..");
        Environment.SetEnvironmentVariable("HOST_FUNCTION_CONTENT_PATH", functionPath, EnvironmentVariableTarget.Process);
        Server = new TestServer(WebHost
            .ConfigureAppConfiguration((builderContext, config) =>
                    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                        optional: true, reloadOnChange: true)
        ServerHttpClient = Server.CreateClient();
  13. In each previously created Function, you can now simply use the ServerHttpClient variable to forward any incoming request to the host. To do so, simply insert the following code as the implementation of each Function:
    return await ServerHttpClient.SendAsync(req, ct);
  14. To simplify local testing, comment out UseHttpsRedirection.
    // app.UseHttpsRedirection();
  15. Start your Azure Function application by pressing F5. If you are running Azure Functions for the first time, the following dialogs might appear:
    1. Azure Functions CLI tools download dialog, wait for the download to complete, this is mandatory to run your app.
      Downloading Azure Functions CLI Tools
      Downloading Azure Functions CLI Tools
    2. Windows Security Alert dialog. Check both Private networks and Public networks and press Allow Access.
      Windows Security Alert
      Windows Security Alert
  16. To see your MVC API hosted in Azure Functions, navigate your browser to: http://localhost:7072/api/values
  17. Make sure the application works as expected and then stop it.
  18. The application will now be manually published to a new Azure Functions App hosted on Azure.
  19. In order to publish the newly created web site, right-click on the project name in the Solution Explorer and select Publish
    Configuration Publication
    Configuration Publication
  20. From the Pick a publish target dialog, select Create New and check Run from package file. Press Publish.
    Publication Settings
    Publication Settings
  21. Select your Function App name and create a new Resource Group, a new Hosting Plan and a new Storage Account. Press Create and wait for resource to be created.
    Create App Service
    Create App Service
  22. Once the resources have been created, the publish will start.
    Publishing Function
    Publishing Function
    1. If you get the following dialog, simply press Yes.
      Update Functions Version on Azure
      Update Functions Version on Azure
  23. Click on the Site URL and browse your newly deployed Function. 
    1. If you encounter any error, configure Application Insights to see the logs of the Function. Make sure you added the APPINSIGHTS_INSTRUMENTATIONKEY key into the local.settings.json. Configure your publish application settings with the Application Insights instrumentation key and configure the Remote value with the value from the newly created Application Insights.
      Configure Application Settings For Publication
      Configure Application Insights Instrumentation Key
    2. Publish your application again and start troubleshooting.

Tips and substitutions:

  1. You can replace MVC API by Razor Pages in order to migrate your existing websites to Azure Functions. See Hosting your ASP.NET Core Razor Pages inside Azure Functions.