DEV Community

Dave Brock
Dave Brock

Posted on • Originally published at daveabrock.com on

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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

var someDateTime = new DateOnly(2014, 8, 24);
Console.WriteLine(justTheDate); // 8/24/2014
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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;
    });
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't do that. Do this:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.AspNetCore.Server.Kestrel.BadRequests": "Debug"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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; }
   }
}
Enter fullscreen mode Exit fullscreen mode

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; }
}

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
        };
Enter fullscreen mode Exit fullscreen mode

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
        };
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (3)

Collapse
jayjeckel profile image
Jay Jeckel

I love the introduction of the DateOnly and TimeOnly structures, but I absolutely hate the names "DateOnly" and "TimeOnly". That we are now saddled with such silly names makes me resent VB a little bit and wish they would just abandon supporting it if it is going to lead to such pollution of the core language.

Collapse
daveabrock profile image
Dave Brock Author

I agree completely, Jay. For better or worse, with .NET compat conquers all.

Collapse
canro91 profile image
Cesar Aguirre

We all needed the DateOnly long time ago :)