Building Your First ASP.NET Core RESTful API for Node.js Developers - Introduction (Part 1)
Responses (0)
Over the past decade, many developers started their backend development journey with Node.js. What makes Node.js compelling to developers is the benefit of creating client-side and server-side applications with a single programming language: JavaScript. This convenience, along with the growing interest in frameworks written with modern programming languages like Golang and Rust, means more developers are less likely to branch out to older, more established technologies like ASP.NET. Developed and maintained by Microsoft, ASP.NET (Active Server Pages Network Enabled Technologies) is a framework for creating dynamic web applications and services on the .NET platform. You can write ASP.NET applications with any .NET programming language: C#, F# or Visual Basic. ASP.NET is widely used across many industries, most notably, by large corporations and government agencies.
Despite Microsoft's efforts to adapt ASP.NET to the rapidly evolving web development landscape, such as the release of ASP.NET MVC in 2009 in response to the popularity of MVC frameworks like Django and Ruby on Rails, ASP.NET continued to suffer from several limitations:
Dependency on the
System.Web.dll
Assembly -System.Web.dll
enables client-server communication over the HTTP protocol for ASP.NET Web Forms, MVC and Web API. Over time,System.Web.dll
accumulated more and more disparate units of functionality and became more integrated with other parts of the .NET Framework. SinceSystem.Web.dll
manages so much, making changes toSystem.Web.dll
and releasing them on pace with what's trending in the web development landscape would be impossible.Tight Coupling with IIS (Internet Information Services) - ASP.NET applications could only run on Windows-only IIS web servers. This prevented ASP.NET applications from being built and ran on non-Windows platforms like Linux.
To migrate away from ASP.NET's monolithic design, Microsoft re-implemented ASP.NET as a modular, cross-platform compatible, open-source framework named ASP.NET Core. Released in 2016, ASP.NET Core comes with built-in support for dependency injection and provides...
Application Frameworks (e.g., ASP.NET Core MVC)
Utility Frameworks (e.g., Identity Framework and Entity Framework)
Middleware (e.g., Rate Limiting, Logging and Response Compression)
Web Servers (e.g., Kestrel and IIS)
Project Templates (e.g., ASP.NET Core Web API, Blazor WebAssembly App, Console Application and Razor Page)
Abstractions, Libraries, Packages and APIs (e.g., ASP.NET Core SignalR, Caching, HTTP, Localization and OpenAPI)
With these features, you can build and run lightweight web applications on Windows, Linux and macOS via the .NET Core runtime. In fact, developers can host their web applications not just on IIS, but also, Nginx, Docker, Apache and much more. This all makes ASP.NET Core applications suitable for containerization and optimized for cloud-based environments. For any missing functionality, you can fetch them as packages from NuGet. As the package manager for .NET, NuGet is equivalent to npm for Node.js.
At its core, an ASP.NET Core application is a self-contained console application that self-hosts a web server (by default, the cross-platform web server Kestrel), which processes incoming requests and passes them directly to the application. Once it finishes handling a request, the application passes the response to the web server, which sends the response directly to the client (or reverse proxy). Keeping the web server independent of the application this way makes testing and debugging much simpler, especially when compared to previous versions of ASP.NET where IIS directly executes the application's methods.
So why might Node.js developers consider learning ASP.NET Core? In the latest round of TechEmpower benchmark, ASP.NET Core significantly outperforms Node.js, sending back almost nine times more plaintext responses per second.
Below, I'm going to show you how to build your first ASP.NET Core RESTful API with C#, a strongly-typed, object-oriented language. Throughout this tutorial, I will relate concepts and patterns to those that you may have already encountered in an Express.js RESTful API.
Installation and Setup#
To get started, verify that you have the latest LTS version of the .NET Core SDK, v6, installed on your machine.
$ dotnet --info
The .NET Core SDK (Software Development Kit) consists of everything you need to create and run .NET applications:
.NET Core CLI
.NET Core Runtime, Libraries and Templates
dotnet
Driver
If your machine does not have the .NET Core SDK installed, then download the latest LTS version of the .NET Core SDK for your specific platform and follow the installation directions.
Once installed, create a new directory named weather-api
. Then, within this directory, create a new solution file:
$ mkdir weather-api
$ cd weather-api
$ dotnet new sln
A solution file lists and tracks all of the projects that belong to a .NET Core application. For example, the application may include an ASP.NET Core Web API project, several class libraries (for directly interfacing with databases via the Entity Framework) and an ASP.NET Core with React.js project. With a solution file, the dotnet
CLI knows which projects to restore NuGet packages for (dotnet restore
), build (dotnet build
) and test (dotnet test
) in your application. In this case, you will find a weather-api.sln
file within the root of the project directory.
Let's create a new ASP.NET Core Web API project:
$ dotnet new webapi -n API
The dotnet new
command scaffolds a new project or file based on a specified template, such as sln
for a solution file and webapi
for an ASP.NET Core Web API. The -n
option tells the dotnet new
command the name of the outputted project/file. In this case, you will find the ASP.NET Core Web API project located within an API
directory.
Let's add this project to the solution file:
$ dotnet sln add API/
You can verify that the project has been added to the solution file by running the following command, which lists all of the projects added to the solution file:
$ dotnet sln list
If you open the solution file, then you will find the "API" project listed with a project type GUID (FAE04EC0-301F-11D3-BF4B-00C04F79EFBC
for C#), a reference to the project's .csproj
file and a unique project GUID.
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{<unique_project_guid>}"
Let's restore the project's NuGet packages. For Node.js developers, this is similar to running npm install
/yarn install
on a freshly cloned Git repository to reinstall dependencies.
$ dotnet restore
If you are building this project on macOS, then you can find the NuGet packages in the ~/.nuget/packages
directory. These packages relate to the package referenced in the API/API.csproj
: Swashbuckle.AspNetCore
. Swashbuckle.AspNetCore
sets up Swagger for ASP.NET Core APIs.
You can check the project's obj/project.nuget.cache
file for absolute paths to the project's NuGet packages.
A Look Into the Default ASP.NET Core RESTful API#
Let's take a look at the three C# files in the API
directory:
Program.cs
WeatherForecast.cs
Controllers/WeatherForecastController.cs
Much like the index.js
file of a simple Express.js RESTful API, the Program.cs
file also bootstraps and starts up a RESTful API, but for ASP.NET Core. It follows a minimal hosting model that consolidates the Startup.cs
and Program.cs
files from previous ASP.NET versions into a single Program.cs
file. Plus, the Program.cs
file now makes use of top-level statements and implicit using
directives to eliminate extra boilerplate code like the class with a Main
method and using
directives respectively.
As you can see in the Program.cs
file, setting up and running a RESTful API with ASP.NET Core requires significantly less code than previous ASP.NET versions.
(Program.cs
)
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Program.cs
begins with instantiating a new WebApplicationBuilder
, a builder for web applications and services. WebApplicationBuilder
follows the builder pattern, which breaks down the construction of a complex object into multiple, distinct steps. This means that we delay the creation of the builder
object (var app = builder.Build()
) until we finish configuring it.
Upon instantiation, the builder
object comes with preconfigured defaults for several properties:
Configuration Providers - For determining how to load command-line arguments, environment variables, user secrets,
appsettings.json
, etc.Logging Providers - For determining where to send logging output to (e.g., console and debugger).
Services - For determining which services provide the necessary functionalities for the application (e.g.,
Microsoft.AspNetCore.Hosting.Server.IServer
andIHostApplicationLifetime
).
Alongside these preconfigured defaults, we explicitly register additional services to the built-in DI (dependency injection) container with WebApplicationBuilder.Services
. This DI container simplifies dependency injection in ASP.NET Core (i.e., automatically resolves dependencies and manages their lifetimes) and is responsible for making all registered services available to the entire application. Here, the following methods get called on builder.Services
:
AddControllers()
- Registers the required services for API controllers.AddEndpointsApiExplorer()
- Registers the required services for creating the metadata of API endpoints that's commonly consumed by tools like OpenAPI-based generators.AddSwaggerGen()
- Registers the required services for the Swagger generator.
After registering these services, call the builder
object's Build()
method to build the WebApplication
(host) with these configurations. By default, the WebApplication
uses Kestrel as the web server.
Then, we check if the application is running within a development environment, and if so, then add Swagger middleware (UseSwagger()
and UseSwaggerUI()
) to the application's middleware pipeline. Notice how these Use{Feature}
extension methods that add middleware are prefixed with Use
, which is similar to how Express.js calls app.use()
to mount middleware functions.
Calling the UseSwaggerUI()
method automatically enables the static file middleware. Express.js also provides a built-in middleware function for serving static assets (app.use(express.static("<path_to_static_files>"))
).
The remaining middleware gets applied to all requests regardless of environment:
UseHttpsRedirection()
- Adds middleware that redirects all HTTP requests to HTTPS.UseAuthorization()
- Adds middleware that enables and performs authorization.
After all of this middleware gets added to the middleware pipeline, we call the MapControllers()
method to automatically create an endpoint for each of the application's controller actions and add them to the IEndpointRouteBuilder
. This method saves us the trouble of having to explicitly define the routes ourselves.
Lastly, we call the Run()
method to run the application.
So to start up the ASP.NET Core RESTful API, run the dotnet run
command, which runs the project in the current directory, within the API
directory.
$ cd API
$ dotnet run
Note: If our application consisted of multiple projects, then you can specify which project you want to run by passing a --project
option to dotnet run
(e.g., dotnet run --project API
to just run the API
project) without having to change the current directory.
When you run this command, you may come across the following error message:
Building...
Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions, Action`1 configureOptions)
at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IEnumerable`1 listenOptions, AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at Microsoft.AspNetCore.Builder.WebApplication.Run(String url)
at Program.<Main>$(String[] args) in <parent_directory_absolute_path>/weather-api/API/Program.cs:line 25
If you do, then follow the directions in the error message. Run the dotnet dev-certs https --clean
command to remove all existing ASP.NET Core development certificates, and run the dotnet dev-certs https
to create a new untrusted developer certificate. To trust this certificate, run the command dotnet dev-certs https --trust
. Then, re-run the dotnet run
command and the error message should no longer pop up. Alternatively, you can remove https://localhost:7101;
from applicationUrl
in the API/Properties/launchSettings.json
file, which stores profiles that tell ASP.NET Core how to run a specific project.
Within a browser, you can visit the Swagger documentation at http://localhost:5077/swagger
.

Here, you will find that the RESTful API comes with only a single endpoint: GET /WeatherForecast
.
If you expand the endpoint's accordion item, then a summary of the endpoint will appear:

This summary provides an example response (status code, value, etc.) for the endpoint.
If you test this endpoint by visiting http://localhost:5077/WeatherForecast
in the browser, or sending a GET
request to http://localhost:5077/WeatherForecast
via a REST client like Postman or a CLI utility like cURL, then you will get a response that contains four weather forecasts.

To see how the RESTful API handles requests to the GET /WeatherForecast
endpoint, open the Controllers/WeatherForecastController.cs
file.
(WeatherForecastController.cs
)
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
If you have developed an Express.js RESTful API, then you should be familiar with the concept of controllers. After all, route callback functions act as controllers.
To understand how this file works, let's first take a look at the [ApiController]
attribute. This attribute tells ASP.NET Core that the controller class will opt-in to using opinionated, commonly-used API functionality like multipart/form-data request inference and automatic HTTP 400 responses.
A route attribute ([Route("[controller]")]
) is placed on the controller and coerces all controller actions to use attribute routing. The [controller]
token in the route attribute expands to the controller's name, so the controller's base URL path is /{controller_name}
, or in this case, /WeatherForecast
. This means the URL path of /WeatherForecast
can match the WeatherForecast.Get()
action method. Since this action method is marked with the HttpGet
attribute, only GET
requests to /WeatherForecast
will run this action method.
When declaring it, a controller class in ASP.NET Core RESTful APIs should derive from the ControllerBase
class, which provides the properties and methods needed for processing any HTTP request.
This controller only contains one action method, WeatherForecast.Get()
. This action method returns four weather forecasts. Each weather forecast is created with the WeatherForecast
model that's defined in the API/WeatherForecast.cs
file:
(WeatherForecast.cs
)
namespace API;
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
This model represents the shape of a weather forecast's data. In the WeatherForecast.Get()
action method, we pass several values to the model:
A date that's offsetted from the current date based on the current weather forecast's index.
A random temperature (in °C) that's within the range of -20 (inclusive) and 55 (exclusive).
A random summary from the
Summaries
array (private static readonly
makes this field only visible to this class).
And the model automatically populates each property accordingly.
Next Steps#
Proceed to the second part of this tutorial series to see how to add your own endpoints to this RESTful API.