Let’s discuss the best back-end tech stack for C# .NET web applications. What’s the best web development framework released by Microsoft each year?
The API hosting this year was Minimal API, in other years was Blazor, and before that, it was ASP.NET Core. In the past few years, we have gotten more and more technologies, and keeping track of them has become increasingly difficult.
Using Microsoft technologies is complicated, which is why this series of articles seeks to make sense of it all. Azure and .NET are our constraints.
SPA frameworks, Web Assembly, and ASP.NET MVC are options for the client side, while Blazor Server and Razor Pages are examples of server-rendered pages. The REST API server will return actual data in the case of single-page applications, which are now the norm in the industry. The purpose of this post is to show how .NET can be used to create a server that can store APIs.
.NET Server-Side technologies
Over the years, Microsoft has developed many frameworks for web development. A brief historical overview will help clarify things:
As a result of Microsoft’s release of Classic ASP pages in 1998, our journey began. They were highly successful when they were replaced in 2002. With the MVC pattern becoming increasingly popular in 2005, new frameworks like Ruby on Rails gained popularity, resulting in Microsoft’s own MVC framework being developed in 2007 called ASP.NET MVC.
Around this time, Silverlight was released, a plugin-based HTML/CSS platform rather than a server rendering platform. Apple’s announcement that iPhone would not support plugins had something else in mind for many .NET developers. About 2011 was the last year for the technology.
A similar model was used for ASP.NET Web API, which provided server-side REST APIs only. The ASP.NET Web API was released in 2012, following the success of SPA frameworks like AngularJs. A serverless model was offered by Azure Functions. With the release of .NET Core, the successor to ASP.NET MVC and ASP.NET Web API was also released in 2016.
Our story has been completed by Blazor Server and Blazor WebAssembly, which were released in 2018 and released to GA in 2019 and 2020. It was later released in 2021 that .NET 6 as well as the ASP.NET Minimal APIs were released.
I hope you didn’t get confused by this history lesson. There are only a few realistic server-side API options left after this journey:
- ASP.NET Core Web API
- ASP.NET Minimal APIs
- Azure Functions
ASP.NET Core MVC, Razor pages, and Blazor Server are other options for rendering pages with your ASP.NET server. Our previous article discussed those topics. The backend server will need to be one of the above if you choose a client-side option, such as a single-page application (SPA). Those should do well against each other, shall we?
ASP.NET Core Web API
It has evolved into a mature and productive framework for creating APIs in .NET. It is also very performant. Web APIs are the most popular API development method in .NET. A URL route is mapped to a Controller class, and specific endpoints are mapped to various methods (that are called Actions).
Support libraries and features are numerous. Dependency injection is well supported by the system. An easy way to implement and customize things like authorization and serialization is by using a middleware model where you can include behavior before or after specific stages.
With your REST API server, you have all the features and customization you could possibly ask for.
My personal opinion is that this framework has some downsides.
- Getting URL routes and operation types (GET, POST, etc.) from attributes is not as straightforward as it could be.
- Multiple endpoints are usually included in each controller, all of which have their own dependencies. This results in bloated controllers that contain too much noise because they are injected into the constructor and saved as members.
- For a simple application, there are a lot of boilerplate codes and formalities. Newcomers still find it hard to understand how all the pieces fit together, even though Visual Studio’s template usually generates this code. There are two methods called Configure and ConfigureServices, two files called Startup.cs and Program.cs, and four controller files.
A sample from AspNetCore.Docs is shown below. Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
GET /WeatherForecast has the following controller:
[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;
// The Web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
ASP.NET Core Web API can be written in a classic way, as illustrated in the example above. Now that .NET 6 has been released, there is some reduction in ceremony, and now the code from Startup.cs has been moved to Program.cs, making the code more readable.
Check out TechEmpower benchmarks and search for “aspcore” to see how ASP.NET Core performs. One of the best performing frameworks is ASP.NET Core.
ASP.NET Core Minimal APIs
In Node.js, creating an app with an HTTP endpoint takes about five lines of code. Do five files and dozens of lines of code really need to be written for a simple ASP.NET Web API? The answer is no. ASP.NET’s opinionated view is just a result of the way this technology evolved, the C# language, and the way the C# language is used. Consequently, using the Minimal APIs template you can accomplish what Node.js can in 4 lines of code with 5 lines of code.
Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
A using statement, namespace, or class are not present. Using directives, declaring namespaces on the File scope, and declaring top-level statements are all part of C# 10’s magic.
The Configure and ConfigureServices methods do not need to be overridden. Rather, you write all your code in functional language, as shown below:
var builder = WebApplication.CreateBuilder(args);
// Inject services:
builder.Services.AddSingleton<SomeClass>(new SomeClass());
var app = builder.Build();
// Consuming services is just a matter of adding them as a parameter
app.MapPost("/checkDI", ([FromServices]SomeClass someClass) =>
{
var client = httpClientFactory.CreateClient();
return Results.Ok();
});
app.Run();
There is no need to include the [FromServices] attribute.
Are you wondering what we have in ASP.NET Web API when it comes to authorization, middleware, and Swagger? All the familiar and beloved features remain. Simpler is better.
Additionally, some overhead has been removed, resulting in a slight performance improvement.
ASP.NET Web API has a lot of overhead and ceremony, and Fast Endpoints allows you to write HTTP servers in .NET without that. Here’s a sample:
Program.cs:
global using FastEndpoints;
var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();
var app = builder.Build();
app.UseFastEndpoints();
app.Run();
An endpoint class:
public class MyEndpoint : Endpoint<MyRequest>
{
public override void Configure()
{
Verbs(Http.POST);
Routes("/api/user/create");
AllowAnonymous();
}
public override async Task HandleAsync(MyRequest req, CancellationToken ct)
{
var response = new MyResponse()
{
FullName = req.FirstName + " " + req.LastName,
IsOver18 = req.Age > 18
};
await SendAsync(response);
}
}
Pretty neat, right?
Azure Functions
There are many differences between Azure Functions and ASP.NET. There is no server involved in this model, so you don’t need to think about it or manage it. Your code will be run when needed by a server that appears when you write it. With Azure, your requests can be handled by up to 200 instances of servers running in parallel, depending on the request load.
In this programming model, you do not have to worry about managing your server, since you are only concerned about solving problems. My concern with this is that Azure App Service also offers managed hosting, which allows you to scale automatically and not have to worry about managing VMs much. Some Azure Function features, however, are just not available in an App Service, such as:
- By using Azure Portal, you don’t need to open Visual Studio to write an HTTP handler.
- Depending on the trigger, the Function can respond to HTTP requests, upload files to blob storage, run SQL queries, handle queue messages, or run scheduled tasks.
- Durable functions enable you to chain multiple functions together and create entire flows, where one triggers another and passes state on.
Azure App Service has a background task scheduling feature called WebJobs, which is available via an Azure App Service. A Azure Function can be compared to a WebJob as a Service, except with more features.
The Azure Functions hosting plans are divided into three categories:
- Unlike a consumption plan, a consumption plan only requires payment when your functions are active. Even in the case of heavy usage, the server instances will automatically scale up and down based on usage. Eventually, your function will be killed if it’s been running for 10 minutes. Your API request latency will increase significantly if the servers that run your functions are idle for a long period of time, resulting in them having to cold start before they can run again.
- A dedicated Azure App Service (also known as a Dedicated plan) is for running Azure Functions. As the service is always on, the cold start issue is eliminated. In addition, long-running functions have an unlimited maximum timeout. The dedicated VM will cost you for the entire period and not just during running your code.
- All the benefits of the consumption plan are included in the Premium plan. In order to avoid cold-start downtime, it pre-warms servers before starting. Limitations on running time are removed. The basic consumption plan does not support virtual networks.
There is also a very tempting free grant of 1 million requests and 400,000 GBs of resources included in the consumption plan. As we try to decide between the options, we’ll discuss it a bit.
What about Azure Kubernetes Service (AKS) and microservices?
You can use .NET to develop server-side applications, as discussed in this post. A Web API might be implemented with ASP.NET or a function may be implemented with Azure. In contrast to an API server, Kubernetes orchestrates multiple services. When choosing a deployment method, a Kubernetes cluster or a virtual machine must be considered.
AKS allows you to create ASP.NET Web API projects for each service in a microservice architecture. These same projects can also be deployed to Azure App Service. When choosing a deployment method, there are several factors to consider, including latency, isolation, and more. We’ll discuss those in the next post.
There are some compelling reasons to use Azure Service Fabric instead of Kubernetes for microservices in Azure, despite its lesser reputation. The next post will also cover that topic in more detail.
Making a choice
The article provided a comprehensive overview of the most popular modern API server technologies in .NET. In this article, we will examine each one and see what its advantages and disadvantages are.
- The classic .NET RESTful API solution is ASP.NET Core Web API. You can’t go wrong with this approach. Although there are some problems, they are not that big a deal. ASP.NET Web API is likely already familiar to your developers, which is a big advantage.
- There is not much difference between Minimal Hosting APIs and Web APIs because the core technology is the same. Methods will replace controllers for each endpoint instead of controllers. I would use Minimal APIs for small or medium projects since it seems to be a fairly safe choice. The choice becomes more challenging in large applications since in theory, you would want to take some more time with the technology. Versions 2 and 3 are often reworked significantly by Microsoft. A case and point can be found in .NET Core. The community’s response in the future would also be interesting to see.
- Fast Endpoints was briefly discussed as an opinionated alternative to Minimal APIs. The approach taken by this library is absolutely amazing to me. You have thought of everything, including how you create classes per endpoints, how you configure routing and verbs, what you map, and what you validate. ASP.NET Minimal APIs enable this code to be more fail-safe. There is no comparison between ASP.NET Minimal APIs and ASP.NET Web APIs in terms of speed. Right now, 106 out of 112 issues have been closed on the GitHub repository; over 1,000 commits have been made.Although, open source libraries are difficult to use in the .NET world. For anything big, Microsoft will make their own project and 99.9% of the community will prefer their version. Open source projects never receive the same investment, making them more dangerous to use. Can Fast Endpoints be maintained if its maintainer stops doing so? They’re not eager to be replaced by 20 others. All of that must be considered when making your decision.
- My reasoning for using Azure Functions as your application’s API is that there are many benefits to doing so. There are a few similarities between the two solutions in terms of development. The Visual Studio projects you create will include both ASP.NET and Functions. The publishing process for both will be similar. There is no difference between auto-scaling and not auto-scaling. It is just as possible to have an Azure Function with a complex design as an Action with a complex design.A serverless model should be chosen based on your needs. The cold-starting of Functions can sometimes take up to 10 seconds when running in a Consumption plan. There’s nothing to be alarmed about, but it’s also nothing to be happy about. A Virtual Machine or App Service is cheaper at some point if you have few requests, but if you have a lot, it is cheaper to use an App Service. It might be easier to just pay for the App Service and not worry about monitoring the costs of Azure Functions, since App Services are pretty inexpensive. In that case, Azure Functions serve no real purpose. You can either host them in an App Service plan or use them as part of a basic plan. In addition, if you would like to solve the cold-start issue with the Premium plan, but it does cost more, you can do so.A Function is great for many things, but I don’t think it should be used for an application’s Web API. You may need to schedule tasks, listen for queue events, or run code when the database changes. The best way to accomplish these kinds of background tasks is to use functions.
What’s next?
It’s time to deploy your client and server technologies to Azure now that we’ve chosen the technology. Next, we’ll cover all the options for deploying and hosting in Azure, including App Services, Static Web Apps, and Kubernetes.
Let’s keep in touch for the next one. I hope this was useful for you.