Hosting Models in ASP.NET Core Explained

In this article, lets look at the various ways in which we can deploy an ASP.NET Core application and the options available for cross-platform deploym..

One of the most important features ASP.NET Core offers over the traditional .NET Framework is its platform independence.

ASP.NET Core supports deploying and running applications on Windows, Linux and MacOS environments as well by means of a hosting bundle. It contains the necessary runtime for the application to run without any issues; be it a Linux, Mac or a Windows environment.

A typical ASP.NET Core Web / API application runs on a WebServer, like any other Web development Framework.

What is a WebServer?

A typical web application runs on a web server, which provides a container for the app to receive and respond payloads over the network. Some examples are Microsoft IIS, Apache Tomcat etc.

Further more, a few Web servers (such as IIS) come with capabilities to act as a reverse proxy, which can handle and forward the incoming request to an application which is running inside the web server or to any external node available for access to it.

Alternatively, ASP.NET Core comes with a built-in webserver called Kestrel which works as a container for the ASP.NET Core application to run.

What are ASP.NET Core Hosting Models?

So to deploy an ASP.NET Core application, we can either go with a standard web server or use the built-in Kestrel. This gives us two possible deployment methods:

  1. Self Hosting model and
  2. Reverse Proxy model

1. Deploying via Self Hosting

In this approach, we configure the kestrel webserver which runs the ASP.NET Core application under it open for HTTP requests on a bound IP and Port. We configure these during the host building within the Main method and once configured, the kestrel server begins to listen for all requests over the specified address and port.

To configure an ASP.NET Core application to run over Kestrel, we chain the Host to use Kestrel server while building the Host as below:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
	.UseKestrel()
    .UseStartup<Startup>();

To specify the IP and Ports we can further chain another method to the Host as:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
	.UseKestrel()
    .UseUrls("http://0.0.0.0:3000")
    .UseStartup<Startup>();

When we run the application with this setup, it is hosted inside a Kestrel webserver and starts to listen over port 3000 on the IP address of the host system.

Self-Hosted Code Deploy

In the self-hosting approach, the kestrel server takes the web server role and listens on the configured port(s) for requests. Hence we are required to specify the IP address and Port on which the Kestrel server should listen.

This can be achieved in two ways.

  1. The simplest is to specify the binding host during the host build setup itself by adding a method UseUrls() to the build pipeline. This method accepts a string parameter which would be the qualified network address (://:) on which the kestrel server should listen. We can also provide multiple urls separated by a comma. If we are not interested in specifying the IP address, we can provide a generic IP address 0.0.0.0 on which the kestrel server takes up the IP address of the server where it is self-hosted.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
	.UseIISIntegration()
	.UseKestrel()
	.UseUrls("http://0.0.0.0:80,https://0.0.0.0:443")
    .UseStartup<Startup>();
  1. The second approach is to configure the values in a launchsettings.json, which the application reads while booting up.

As mentioned earlier, this approach should be used only for debugging and development purposes only.

Now that the code changes are made, we begin by publishing the code using dotnet CLI and then booting up the application.

> dotnet publish --runtime <hosting server runtime>

The –runtime parameter of the dotnet publish command takes in an RID (Runtime Identifier) which uniquely identifies an OS architecture against which the ASP.NET Core build is to be generated and run.

For example, to deploy an application targetting Windows 64 bit server environment we use the RID win-x64 which works for all the Windows x64 runtimes of ASP.NET Core 2.x and 3.x platforms. Similarly for a Linux environment we use linux-x64, and for mac we go with osx-x64 which represents OSX 10.12 or higher

> dotnet publish --runtime win-x64 -c Release -o build

The default publish folder will be binReleasenetcoreappX.Ypublish in the source code folder for the respective environment (RID) specified. In our case the code is built and the binaries are published onto a new folder /build under the directory where the command is being executed. The flag -c with value Release specifies that the dotnet runtime should take in Release configurations for packing the binaries.

Finally, the contents of the published folder are copied on to the hosting server and in the path of the build, we boot up the server using the below command:

> dotnet ./<ProjectName>.dll

The project name may be replaced with the dll name if the namespace used is different from the project name. If we are not to specify the host address in the code, we can specify it during the run command as below:

> dotnet <ProjectName>.dll --urls=http://0.0.0.0:80

But this approach cannot override the Urls that are specified at the build host time.

In an AspNetCore3.x scenario, we can make use of the generated exe file .exe which comes as an application executable which resembles running the above command. Double clicking on the exe file would boot up the application self-hosted inside kestrel container for us.

Although this approach is simple to setup and run, using Kestrel server directly is not recommended for production loads. Because:

  • It is a lightweight server that doesn’t come with load balancing capabilities
  • It doesn’t support port sharing among multiple processes

This paves way for the second mode of deployment which overcomes the above mentioned shortcomings.

2. Running through a Reverse-Proxy

In this approach, we use the kestrel webserver as a container for the running application, while a wrapping webserver such as IIS, Apache or Nginx receive the incoming HTTP requests. The received request is then forwarded to the application via the kestrel server and the response goes in the same route. The wrapping webserver acts as a “proxy” for the actual webserver that hosts the application. The wrapping webserver in this approach is known as “a reverse proxy”.

It is recommended to go in this approach for all production builds because of the following advantages provided by these web servers:

  • Limit the hosted app exposure to the Internet
  • Added layer of security and configuration
  • Simplified Load Balancing and HTTPS
  • Only a reverse proxy server can require and validate a X.509 certificate and that server then forwards the request to the lower layers.

In order to have support for IIS integrating with Kestrel for a Windows Server deployment, we chain the Host with UseIISIntegration() method while building the Host.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
	.UseIISIntegration()
	.UseKestrel()
    .UseStartup<Startup>();

This approach is most widely used for deploying ASP.NET Core applications in production, and this is the process that happens even when we deploy our application in Cloud providers such as Azure App Services or AWS Elastic Beanstalk environments.

Reverse-Proxy Example – Code Deploy to an IIS Web Server

If we are to deploy in a production setup, we must first make sure we have added the UseIISIntegration() method to the host build pipepline. Once the code is published, we can now host the published contents by the following steps:

  1. Download and install hosting bundle for the hosting environment. Download the appropriate bundle for the environment to be used from https://dotnet.microsoft.com/download/dotnet-core/3.1).
  2. Publish the application to generate binaries for the appropriate hosting environment (Linux, Windows or Mac OSX) using the RID tags at the CLI command.
  3. Once the code is published and binaries are generated, copy them into a folder in the /www folder under /inetpub directory which is the default lookup folder for IIS. Alternatively, you can maintain the application binaries in another directory and point the same while configuring the application path.
  4. Create a new website in IIS using the IIS Manager, and point the path to the path where the binaries are copied.
  5. Bind the IP and Ports as per the requirement (you can leave it as localhost for the application to use host IP address itself).
  6. Start the application.

As per my observation, when we want to host the application via a reverse proxy approach, it is not recommended to use the UseUrls() in the build pipeline which is intended to be used for self hosting approach – as it might cause issues with the hosting.

I’ve covered the complete hosting in IIS and the probable issues we might run into along with a few best practices here.

I’d highly recommend you to check it out for an extended understanding!

Default image
Sriram Mannava

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity.

Leave a Reply