Card image cap

Exploring ASP.NET Core Fundamentals - Hosting a Web Application

ASP.NET Core  • Posted 2 months ago

ALl the design, development and testing efforts on an application converge into the final stage of application lifecycle (SDLC) which is Deployment. One of the most important features AspNetCore offers over the traditional .NET Framework is its platform independence. AspNetCore supports deploying and running applications on Windows, Linux and MacOS environments as well by means of a hosting bundle provided by AspNetCore. This hosting bundle provides the necessary runtime for the application to run without any issues; be it a Linux, Mac or a Windows environment.

The Importance of 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. 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 IIS or to any external node available for access to IIS. Alternatively, the AspNetCore library uses a built-in webserver called Kestrel which works as a container for the AspNetCore application to run.

This gives us two modes of deployment when using a AspNetCore web application. These are:

  1. Self Hosting model and
  2. Reverse Proxy model

Self Hosting Model

In this approach, we configure the kestrel webserver which runs the AspNetCore 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 AspNetCore 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.

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.

The Reverse Proxy Model

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 AspNetCore 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.

Code Publish and Deploy - Self Hosted Approach:

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 AspNetCore 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 AspNetCore 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 \bin\Release\netcoreappX.Y\publish 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.

Hosting in IIS as an Application - Reverse Proxy Approach:

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.

Tip:

*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.

An in-detail process about hosting in IIS and the probable issues we might run into along with a few best practices has been discussed in another space.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept