Razor Pages Startup in .NET 6

When you create a new web app using .NET 6 (from the new RC1 templates), you might notice something is missing. There is no Startup class. What happened to it? And how do you configure your new .NET 6 web app?

The ASP.NET Core templates have been updated to use the latest C# language features, and a new minimal hosting API in .NET 6. The main driver behind these changes is simplicity. The ASP.NET team want to remove unnecessary code from your app, so you have less code to understand and look after, and there is a lower barrier to entry for newer C# developers. They want to provide "a more focused, low ceremony way of creating a web application".

In a .NET 6 app, your services and middleware configuration take place in the Program.cs file instead of being delegated to a separate Startup class with its constructor, ConfigureServices and Configure methods. Here's what the revised version of Program.cs looks like:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");

    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

The first thing to notice is that the Main method is missing. This is still, after all, a console app at heart. As of C# 9, introduced last year, the Main method is no longer necessary. Instead, this file utilises a feature called top level statements, where the namespace, class declaration and Main methods are generated by the compiler. You just start writing executable code, which the compiler places within the generated Main method.

In .NET 5, the Program class includes a large number of using directives, whether they were needed or not. This version doesn't have a single one. C# 10 introduces a new feature called global usings, where a pre-defined selection of using directives are registered globally and are no longer required in individual class files. The selection includes all the namespaces that you tend to see at the top of every class file in an ASP.NET Core web app, so this new feature greatly reduces the boilerplate noise in your code base.

Looking at the code itself, it does exactly the same thing as the Startup class in .NET 5 and earlier. The code registers services and builds a pipeline. But this is a lot simpler. It makes use of the new minimal hosting API, based around the new WebApplicationBuilder type through which you configure your app. The WebApplicationBuilder type has a number of properties that help to simplify service registration and configuration:

  • Environment - provides access to the IWebHostEnvironment
  • Services - the IServiceCollection that is injected into ConfigureServices in previous versions
  • Configuration - representing among other things, the IConfiguration injected into the older Startup class constructor
  • Logging - enabling logging configuration via the ILoggingBuilder
  • Host - an IHostBuilder that enables configuration of host specific services including third party DI containers
  • WebHost - the IWebHostBuilder represented by webBuilder parameter in the ConfigureWebHostDefaults method in the .NET 3.1/5 version of Program.cs

Services are added to the Services property. The project template shows how Razor Pages is configured:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

You use the Configuration property to access the IConfiguration for the app, so that you can access connection strings and so on:

builder.Services.AddDbContext<MyContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("MyContext"));
});

The Build method on the WebApplicationBuilder type returns a configured application as a WebApplication type, which is new. The WebApplication represents a merger of the IApplicationBuilder, IEndpointRouteBuilder and IHost interfaces which we are familiar with from current versions of ASP.NET Core. You can use your existing extension methods on IApplicationBuilder to build your pipeline - UseStaticFiles, UseRouting and so on. And because it implements IEndpointRouteBuilder, no more lambdas required to call MapRazorPages (or MapControllers etc). You can call that directly on the WebApplication instead.

So the big question is: do you need to update your startup code if you migrate your .NET 5 Razor Pages app to take advantage of the long term support provided by .NET 6? The answer is No. The two-file, generic host approach to app configuration is still supported. However, if you are in the middle of writing a book about Razor Pages which is scheduled to be published after .NET 6 is released, you might just have to redo all the code samples and update the nine chapters written so far to reflect the new way of doing things...

Speaking of which, my new book Razor Pages in Action is available to purchase in MEAP now, which gives you access to chapters as they are written (and amended). You can get 35% off the price of the book (along with all other Manning products) by quoting discount code au35bri when you make your purchase. You get the full version of the book when it is finally published in the first half of next year.

Final note - there are two new settings in the csproj file that determine how new C# features are applied to your app - ImplicitUsings and Nullable. The first determines whether implicit, or global usings are enabled for the project (default is enabled). The second, Nullable, enables you to opt in to Nullable Reference Types which was introduced in C# 8 to help you avoid the dreaded NullReferenceException at runtime. The default is also enabled. The main consequence of this is that your project could end up with a lot of new warnings:

Summary

Web app configuration in .NET 6 has been simplified to a single file to reduce the concept count for developers approaching .NET for the first time. Two new types are introduced - the WebApplicationBuilder and the WebApplication. You don't need to use this pattern. The existing way of doing things that make use of a Startup class is still supported. The new project templates also embrace a number of newer C# features.