As a trainer and developer, I'm constantly diving into the latest and greatest from the .NET team, and the .NET 9 release brings some fantastic new features to ASP.NET Core that are designed to make our lives easier and our applications faster and more robust. Before we do that, let's get a little perspective on how far Microsoft web development technologies have come...
In the Beginning
Before .NET came along, Microsoft’s server-side web technology was Active Server Pages. Developers would mix HTML and Visual Basic in a single page. When the page was requested, server-side processing executed the Visual Basic to produce pure HTML which was returned to the browser.
While ASP would work with any browser, the server had to be running Microsoft’s Internet Information Services (IIS) and therefore could only use the Windows operating system.
Microsoft had their own client-side technology too, allowing VBScript to run within a browser as an alternative to JavaScript. Unfortunately, this was only supported by Internet Explorer, and since JavaScript is supported by all browsers, VBScript never really got off the ground.
Into the 21st Century
From its introduction, .NET was focussed on providing tools for both desktop and web-based applications. Active Server Pages became ASP.NET and provided a gateway for backend programmers familiar with languages like C++ to take their first, small steps into web development.
The main approach was known as ASP.NET Web Forms. A web site could be designed using a drag-and-drop interface of server-side controls, which were then rendered as HTML to send to the browser when a page was requested. Structurally, this was very similar to traditional ASP, but provided much better support for large, scalable, maintainable sites. Coding could be done in Visual Basic or C# and business logic was kept entirely separated from GUI logic, rather than being interspersed with HTML.
Models, Views and Controllers
In 2009, ASP.NET MVC was released, an implementation of the MVC design pattern (which has been around since the 1970s).
A Model is simply a class containing data that needs to be displayed or entered on a web page. A Controller is a class that reads and writes a Model to and from the database and applies business logic and validation. The key to the technology is the View, which is written in a .cshtml file (or .vbhtml, but C# is used far more commonly than Visual Basic nowadays). As the file suffix suggests, the View is a mixture of C# and HTML, and C# features such as conditionals and loops can be used to construct HTML with optional and repeating sections. The ‘Razor’ syntax allows for seamless switching between the two. Good practice dictates that this C# is purely concerned with formatting the output. Business logic – still written in C# - should go in the Controller.
ASP.NET MVC
Compared with WebForms, ASP.NET MVC requires much greater understanding of HTML and CSS, and even of JavaScript, but this gives the advantage that sophisticated browser features can be fully exploited. That said, ASP.NET MVC is still a server-side technology. The HTML pages are generated on the server and sent to the browser. Although JavaScript can be used, ASP.NET MVC simply delivers it to the browser without providing any real developer support.
Going Headless
Meanwhile, client-side technologies were progressing fast. Libraries layered on top of JavaScript make development easier and more robust. Low-level libraries like jQuery led on to full-blown APIs such as Angular and React. Compared with ASP.NET MVC, which requires a round-trip back to the server for every interaction, these technologies provide a far richer user experience. But a server-side process is still required for heavyweight processing, database access and so forth. Such a process, that provides raw data rather than HTML, is known as a web service rather than a website.
The first .NET approach to writing web services was the Windows Communication Foundation, but more recently the trend has been for RESTful web services, and the .NET implementation of this is ASP.NET WebAPI. In many ways this can be regarded as MVC without the V. The Controller manipulates Models as before, but then rather than sending HTML to the browser, it just sends the Model data. It’s up to a browser to act as the View and transform the Model data into a web page. Any browser technology such as Angular or React could be used for this, and Visual Studio provides support in several of these, allowing the client- and server-side code to be developed side-by-side, albeit in different technologies.
Cut to the Core
In 2016, .NET Core was introduced. This was a complete rewrite of .NET with the specific intent of allowing .NET to run on platforms other than Windows. This means that ASP.NET web servers can now be hosted on Linux, without IIS, which can prove more efficient and cost-effective on the cloud.
Not everything was ported to .NET Core. Web Forms were dropped, but ASP.NET MVC and WebAPI have continued to evolve, and new technologies have been added. Razor Pages are similar to MVC, but combine Views and Controllers together into a single class. Minimal APIs allow simple web services to be written with only a few lines of code, without the overhead of Controllers.
And finally, we have a true .NET client-side technology.
Blazor
The introduction of the standard WebAssembly browser technology allows languages other than JavaScript to be run in the browser – C# being one of them. ASP.NET Blazor provides client-side features similar to Angular or React, but written in C#. This can be used in combination with Web API on the server-side, allowing C# to be the programming language across the entire application, with a single code base and a single set of developer skills.
Back to the Future of ASP.NET!
While Blazor is a major focus, many of the updates in .NET 9 improve our traditional Web API and MVC applications.
Keyed Dependency Injection
The introduction of keyed dependency injection in .NET 8 was a great addition to the framework's DI container. It gives us a more precise way to resolve services, especially when you have multiple implementations of the same interface.
builder.Services.AddScoped<IRepository, CachedRepository>();
builder.Services.AddKeyedScoped<IRepository, DbRepository>("raw-access");
// In a constructor
public CachedRepository(
[FromKeyedServices("raw-access")] IRepository realRepository
)
.NET 9 extends this concept to middleware, which is a huge win for composability. Now you can inject a keyed service directly into your middleware's Invoke or InvokeAsync method, which was a common pain point in .NET 8. This is a subtle but powerful change that offers much more flexibility in how we build our request pipelines.
Built-in OpenAPI Support
In .NET 8, you had to use a third-party package like Swashbuckle to generate OpenAPI documents for your Web APIs. .NET 9 changes this by offering built-in support for OpenAPI document generation with the Microsoft.AspNetCore.OpenApi package. This moves API documentation from a nice-to-have third-party tool to a first-class feature of the framework.
This integrated approach means:
Less external dependency: You no longer need to manage a separate package for a core development need.
Automatic schema generation: The documentation stays in sync with your API code.
Minimal configuration: You can enable it with just a few lines of code.
This is a clear move by Microsoft to streamline the development experience and encourage API-first design from the start.
Native AOT
Ahead-of-Time (AOT) compilation continues to mature in .NET 9, and its benefits for cloud-native applications—smaller executable size, faster startup, and lower memory footprint—are becoming even more compelling.
The core challenge with AOT remains its incompatibility with certain dynamic features of .NET, like reflection. However, the .NET team has made significant strides in this area. AOT-compatible versions of key libraries, especially those for JSON serialization, are constantly being improved using source generators, which perform the work at compile-time instead of runtime.
While it's still not a one-size-fits-all solution, .NET 9 makes Native AOT a much more viable option for a wider range of ASP.NET Core applications, particularly for high-density microservices where cold start times and memory usage are critical. The performance gains can be substantial, but as always, a little benchmarking on your specific workload is the best way to prove the value.
Blazor
Blazor has received some major attention, building on the unified rendering model introduced in .NET 8. The goal is clear: to erase the distinction between Blazor Server and Blazor WebAssembly and provide a seamless, integrated experience.
Enhanced Static Asset Delivery
A key optimization in .NET 9 is the app.MapStaticAssets() middleware. This replaces the old app.UseStaticFiles() and brings automatic, production-ready optimizations to your static content.
What it does for you:
Build-time compression: It automatically pre-compresses your static files (e.g.,
.css,.js) using Gzip in development and both Gzip and Brotli for publishing.Content-based E-Tags: Ensures that browsers only download files when their content has actually changed, reducing unnecessary network traffic.
This is a great, low-effort way to boost your app's performance and is a best practice that's now built right into the framework.
Simplified Authentication
.NET 9 simplifies authentication in Blazor with new APIs like AddAuthenticationStateSerialization and AddAuthenticationStateDeserialization. These new methods streamline the management of authentication state, reducing the boilerplate code needed to handle user identity across different rendering modes. This makes it easier to build secure, interactive Blazor applications without a complex setup.
Constructor Injection in Components
While it's been a long time coming, Blazor components now fully support constructor-based dependency injection. This is a massive improvement for developers who prefer this pattern for its clarity and testability. Instead of using the [Inject] attribute on properties, you can now inject your services directly into the component's constructor, aligning Blazor development with standard .NET best practices.
C#
// Old way
@inject IMyService MyService
// New way in .NET 9
@implements IDisposable
public MyComponent(IMyService myService)
{
// ...
}
This change is a big win for code cleanliness and maintainability and further solidifies Blazor's position as a powerful, enterprise-ready framework.