Don't Do That, Do This: The .NET 6 Edition

This post is my annual contribution to the 2021 C# Advent Calendar. Please check out all the great posts from our wonderful community!

Have you heard? .NET 6 has officially arrived. There's a lot of good stuff: C# 10, performance improvements, Hot Reload, Minimal APIs, and much more. As is the case for most releases, a few big features tend to get most of the hype.

What about the features and improvements that don't knock both your socks off but also help to make your daily development experience more productive? A lot of these "quality of life" features in .NET 6 can help by removing boilerplate and pain and can help you get to the point: shipping quality software.

As I get into the holiday spirit, consider this a stocking of sorts: just some random, little things that I hope you'll find enjoyable. (And despite the catchy title, there are always tradeoffs: do what works for you.)

Don't chunk large collections manually, use the new LINQ API instead

When working with large collections of data, you likely need to work with smaller "chunks" of it—a big use case would be if you're getting a lot of data back from a third-party API. If there's no pagination set up and you have a bunch of data in memory, you'll probably want a way to "page" or split up the data.

What's a .NET developer to do? Do things the hard way. You'd probably do some logic to set a page size, check what page you're on and if there are any elements left, then update your code when you add pages to a collection. It'd be a series of Take and Skip LINQ calls, or maybe even an extension method, like this one that's popular on Stack Overflow:

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
    {
        int i = 0;
        var splits = from item in list
                     group item by i++ % parts into part
                     select part.AsEnumerable();
        return splits;
    }
}

Don't do that, do this: use the new LINQ Chunk API. When we call Chunk on 200 elements, we'll get 20 lists of 10 elements each.

int pageSize = 10;
IEnumerable<Employee[]> employeeChunk = employees.Chunk(pageSize);

If you need just a date, don't use DateTime, use DateOnly

If you want to work with dates and times in .NET, you typically start with DateTime, TimeSpan, or DateTimeOffset. What if you only need to work with dates and only need a year, month, or day? In .NET 6, you can use a new DateOnly struct. (You can also use TimeOnly. I also promise this isn't the start of a dating app.)

We've all done something like this:

var someDateTime = new DateTime(2014, 8, 24);
var justTheDate = someDateTime.ToShortDateString();

Console.WriteLine(someDateTime); // 8/24/2014 12:00:00 AM
Console.WriteLine(justTheDate); // "8/24/2014"

Don't do that, do this: use the DateOnly struct.

var someDateTime = new DateOnly(2014, 8, 24);
Console.WriteLine(someDateTime); // 8/24/2014

There's a lot more you can do with these, obviously, in terms of manipulation, calculation days between dates, and even combining with DateTime. Apart from being easier to work with, it also offers better type safety for just dates, a Kind property, and simpler serialization.

Don't wire up a lot of custom code for logging HTTP requests, use the new logging middleware

Before .NET 6, logging HTTP requests wasn't hard but a little cumbersome. Here's one way: you'd probably have logic to read the request body, use your expert knowledge of ASP.NET Core middleware to pass the stream to whatever is next on the pipeline, and remember to register your middleware—all for a very common activity for any reasonably complex web application.

Don't do that, do this: use the new .NET 6 HTTP Logging middleware to make your life easier.

Add this to your project's middleware:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpLogging();

    // other stuff here, removed for brevity
}

Then, customize the logger as you see fit.

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpLogging(logging =>
    {
        logging.LoggingFields = HttpLoggingFields.All;
        logging.RequestHeaders.Add("X-Request-Header");
        logging.ResponseHeaders.Add("X-Response-Header");
        logging.RequestBodyLogLimit = 4096;
        logging.ResponseBodyLogLimit = 4096;
    });
}

You'll want to be watchful of what you log and how often you log it, but it's a great improvement.

Don't use hacks to handle fatal Blazor Server exceptions, use the ErrorBoundary component

What happens when an unhandled exception in Blazor Server occurs? It's treated as fatal because "the circuit is left in an undefined state which could lead to stability or security problems in Blazor Server." As a result, you might need to throw try/catch blocks all over the place as a preventive measure or encapsulate logic in JavaScript since no C# code runs after the unhandled exception.

Don't do that, do this: use the new ErrorBoundary component. It isn't a global exception handler but will help deal with unpredictable behavior, especially with components you don't and can't control.

You can see my article for the full treatment, but here's the gist: I can add an ErrorBoundary around the @Body of my default layout.

<div class="main">
    <div class="content px-4">
        <ErrorBoundary>
            @Body
        </ErrorBoundary>
    </div>
</div>

Of course, you probably want to go further than a catch-all boundary. Here's me iterating through a list:

<tbody>
    @foreach (var employee in Employees)
    {
        <ErrorBoundary @key="@board">
            <ChildContent>
                <tr>
                    <td>@employee.Id</td>
                    <td>@employee.FirstName</td>
                    <td>@employee.LastName</td>
                 </tr>
            </ChildContent>
            <ErrorContent>
                Sorry, I can't show @employee.Id because of an internal error.
            </ErrorContent>
        </ErrorBoundary>
    }
</tbody>

Don't use Server.Kestrel verbose logging for just a few things, use the new subcategories

If I want to enable verbose logging for Kestrel, I'd previously need to use Microsoft.AspNetCore.Server.Kestrel. That still exists, but there are also new subcategories that should make things less expensive. (Computationally; you're still on the hook for holiday gifts, sorry.)

In addition to Server.Kestrel, we now have Kestrel.BadRequests, Kestrel.Connections, Kestrel.Http2, and Kestrel.Http3.

Let's say you only want to log bad requests. You'd normally do this:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.Server.Kestrel": "Debug"
    }
  }
}

Don't do that. Do this:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.Server.Kestrel.BadRequests": "Debug"
    }
  }
}

Is it the sexiest thing you'll read today? Unless you love logging more than I thought you did, probably not. But it'll definitely make working with Kestrel verbose logging much easier.

Don't get lost in brackets, use C# 10 file-scoped namespaces instead

C# 10 introduces the concept of file-scoped namespaces.

Here's a typical use of namespaces in C#:

namespace SuperheroApp.Models
{
   public class Superhero
   {
      public string? FirstName { get; set; }
      public string? LastName { get; set; }
   }
}

Instead of downloading a bracket colorizer extension, do this instead:

namespace SuperheroApp.Models;

public class Superhero
{
   public string? FirstName { get; set; }
   public string? LastName { get; set; }
}

Also, for the record (ha!), you can make this even simpler if you want to take advantage of immutability and value-like behavior:

namespace SuperheroApp.Models;

public record Superhero(string? FirstName, string? LastName);

You should be aware of what you're getting with positional parameters, but we can all agree this isn't your grandma's C# (and I'm here for all of it, my apologies to grandma).

Speaking of brackets ...

Since we're on the topic of brackets, C# 10 also introduces extended property patterns.

You could use property patterns in a switch expression like this:

public static int CalculateSuitSurcharge(Superhero hero) =>

        hero switch
        {
            { Suit: { Color: "Blue" } } => 100,
            { Suit: { Color: "Yellow" } } => 200,
            { Suit: { Color: "Red" } } => 300
            _ => 0
        };

Don't do that. Do this:

public static int CalculateSuitSurcharge(Superhero hero) =>

        hero switch
        {
            { Suit.Color: "Blue" } => 100,
            { Suit.Color: "Yellow" } => 200,
            { Suit.Color: "Red" } => 300
            _ => 0
        };

Wrapping up

I hope you enjoyed this post, and you learned a thing or two about how .NET 6 can make your developer life just a little bit easier. Have you tried any of these? Do you have others to share? Let me know in the comments or on Twitter.