.NET 6 development is close to completion, and we will have a look at four new features which you can use in an ASP.NET Core 6 application.

It is due to be released in November 2021, alongside C# 10 and Visual Studio 2022.

As .NET 6 is still in preview, some features are still in development. However, the completed features can be used by downloading Visual Studio 2022 preview.

We will have a look at some of the completed features in this article.

Feature #1: Console template

The console template is one of the biggest changes in .NET 6.

Traditionally, a console application would have a namespace, class name and a Main method. The Main method would execute when the console application is started.

With an ASP.NET Core application, the Main method is where web application instance would be built, configured and ran.

// Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace RoundTheCode.DotNet6
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
However, with .NET 6, you can do away with the console application’s namespace, class name and Main method. As a result, the file just has the contents on the Main method included.
// Program.cs
using RoundTheCode.DotNet6;
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).Build().Run();

Feature #2: Merger of Program and the Startup class

In ASP.NET Core 5, there would be two classes to configure the application.

We looked at the Program class above which builds and runs the application. In addition, there is a Startup class, which is where we make our configuration changes, such as adding a database connection, or using dependency injection.

// Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace RoundTheCode.DotNet6
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<ITeamService, TeamService>();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "RoundTheCode.DotNet6", Version = "v1" });              
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "RoundTheCode.DotNet6 v1");
c.RoutePrefix = string.Empty;
});
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

However, with ASP.NET Core 6, these two classes have now been merged into the Program.cs file.

And, with the elimination of namespace, class and Main method, it means we simply have one Program.cs file that creates the WebApplicationBuilder instance, and configures the settings for the application.

// Program.cs
using RoundTheCode.DotNet6;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<ITeamService, TeamService>();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "RoundTheCode.DotNet6", Version = "v1" });
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "RoundTheCode.DotNet6 v1");
c.RoutePrefix = string.Empty;
});
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.Run();

Feature #3: Minimal APIs

ASP.NET Core 6 introduces Minimal APIs.

With ASP.NET Core 5, it was possible to use the mapping methods inside the UseEndpoints method. However, the only parameter that was able to be passed through that was the HttpContext instance.

As a result, the MVC route would have been a better solution as dependencies can be passed in as parameters.

But with ASP.NET Core 6, there are new mapping methods as part of the WebApplication instance. And, they allow you to pass in dependencies as methods.

For example, a WebApplication instance is created when the application calls builder.Build().

var app = builder.Build();

Within the WebApplication instance, there are methods that represent each of the CRUD verbs.

  • MapGet
  • MapPost
  • MapPut
  • MapDelete

With that, we can go ahead and set up a new API endpoint by passing in the route, any parameters that we need, and then returning a response.

app.MapGet("team/{id}", (ITeamService teamService, int id) => teamService.Read(id));

If we were to run our API at this point, it would run fine. However, if we have Swagger configured for our Web API, it will not appear in the Swagger documentation.

We need to call the AddEndpointsApiExplorer endpoint when building up our web application for it to appear in Swagger.

// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); // Need to add this in for it to appear in Swagger.
builder.Services.AddSingleton<ITeamService, TeamService>();
...
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "RoundTheCode.DotNet6 v1"));
}
app.MapGet("team/{id}", (ITeamService teamService, int id) => teamService.Read(id));
...
app.Run();
By running our application now, our endpoint will now appear in our Swagger documentation.

Feature #4: DateOnly and TimeOnly structs

A feature that is long overdue in .NET 6 is the ability to set an object that is just a date, or just a time.

Traditionally, the DateTime struct would need to be used. That would mean that the time would exist, even if we were just using the date. And vice-versa with the time.

With the DateOnly struct, we can add a new instance by specifying the year, month and day. And it has similar methods to the DateTime struct.

Here is an example of how to add seven days to a DateOnly struct.

var dayNow = new DateOnly(2021, 08, 30);
var dayWeekTime = dayNow.AddDays(7);
return dayWeekTime.ToShortDateString();

It’s a similar story with TimeOnly struct. Creating a new TimeOnly struct allows for us to pass in parameters, such as hours, minutes and seconds.

From there, we can do things, like adding a certain number of hours. If the number of hours goes beyond 24, it will revert back to 0.

Here is an example of adding 8 hours to 18:00 in a TimeOnly struct. The output would be 02:00.

var timeNow = new TimeOnly(18, 0, 0);
var timeNowEightHours = timeNow.AddHours(8);
return timeNowEightHours.ToString();
Rate this post