20: The Magnificent 7!

17 Jun 2022

We've been hearing that goods things are coming our way in .NET 7 so we thought we spend some time talking about what's coming in ASP.NET Core, C#11 and the Core APIs based on Preview 4.

Random fact

The mathematical symbol for division (÷) is called an obelus. (https://en.wikipedia.org/wiki/Obelus)

An obelus (plural: obeluses or obeli) is a term in typography that refers to a historical mark which has resolved to three modern meanings:

The dagger symbol originated from a variant of the obelus, originally depicted by a plain line − or a line with one or two dots ÷. It represented an iron roasting spit, a dart, or the sharp end of a javelin symbolizing the skewering or cutting out of dubious matter.

Introduction

What’s new in .NET 7!!

.NET 7 preview 4 was released on 10/05/22 and we wanted to take a look at what is coming in C#11 and ASP.NET Core and Core Apis (.Net 7).

ASP.NET Core updates in .NET 7

Here’s a summary of what’s new in this preview release:

  • HTTP/2 performance improvements

    • .NET 7 Preview 4 introduces a significant re-architecture of how Kestrel processes HTTP/2 requests. ASP.NET Core apps with busy HTTP/2 connections will experience reduced CPU usage and higher throughput.

    • HTTP/2 allows up to 100 requests to run on a TCP connection in parallel. This is called multiplexing. It’s a powerful feature but makes HTTP/2 complex to implement. Before Preview 4, HTTP/2 multiplexing in Kestrel relied on C#’s lock keyword to control which request could write to the TCP connection. While lock is a simple solution to writing safe multi-threading code, it’s inefficient under high thread contention. Threads fighting over the lock waste CPU resources that could be used for other work.

      Kestrel profiling and benchmarking showed:

      • High thread contention when a connection is busy.
      • CPU cycles are wasted by requests fighting over the write lock.
      • Idle CPU cores as requests wait for the write lock.
      • Kestrel HTTP/2 benchmarks are lower than other servers in busy connection scenarios.

      The solution is to rewrite how HTTP/2 requests in Kestrel access the TCP connection. A thread-safe queue replaces the write lock. Instead of fighting over who gets to use the write lock, requests now queue up in an orderly line, and a dedicated consumer processes them. Previously wasted CPU resources are available to the rest of the app.

      These improvements are visible in gRPC, a popular RPC framework that uses HTTP/2. Kestrel + gRPC benchmarks show a dramatic improvement:

  • Typed results for minimal APIs

    • In .NET 6 we introduced the IResult interface to ASP.NET Core to represent values returned from minimal APIs that don’t utilize the implicit support for JSON serializing the returned object to the HTTP response. The static Results class is used to create varying IResult objects that represent different types of responses, from simply setting the response status code, to redirecting to another URL.
  • OpenAPI improvements for minimal APIs

    • The OpenAPI specification provides a language-agnostic standard for describing RESTful APIs. In .NET 7 Preview 4, we’re introducing support for the new Microsoft.AspNetCore.OpenApi package to provide APIs for interacting with the OpenAPI specification in minimal APIs. Package references to the new package are included automatically in minimal API-enabled application that are created from a template with --enable-openapi. In other cases, the dependency can be added as a package reference.

    The package exposes a WithOpenApi extension method that generates an OpenApiOperation derived from a given endpoint’s route handler and metadata.

    app.MapGet("/todos/{id}", (int id) => ...)
        .WithOpenApi();
    

    The WithOpenApi extension method above generates an OpenApiOperation associated with a GET request to the /todos/{id} endpoint. A second WithOpenApi extension method overload can be used to extend and override the generated operation.

    app.MapGet("/todos/{id}", (int id) => ...)
        .WithOpenApi(operation => {
            operation.Summary = "Retrieve a Todo given its ID";
            operation.Parameters[0].AllowEmptyValue = false;
            return operation;
        });
    
  • Return multiple results types from minimal APIs

    • The new Results<TResult1, TResult2, TResultN> generic union types, along with the TypesResults class, can be used to declare that a route handler returns multiple IResult-implementing concrete types, and any of those types implementing IEndpointMetadataProvider will contribute to the endpoint’s metadata, enabling the framework to automatically describe the various HTTP results for an API in OpenAPI/Swagger:
    // Declare that the lambda returns multiple IResult types
    app.MapGet("/todos/{id}", async Results<Ok<Todo>, NotFound> (int id, TodoDb db)
    {
        return await db.Todos.FindAsync(id) is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
    });
    
  • Route groups

    • .NET 7 Preview 4 introduces the MapGroup() extension method, which helps organize groups of endpoints with a common prefix. It allows for customizing entire groups of endpoints with a singe call to methods like RequireAuthorization() and WithMetadata().
    // ...
    // Was: app.MapTodosApi()
    app.MapGroup("/public/todos").MapTodosApi();
    // Auth configuration is left as an exercise for the reader. More to come in future previews.
    app.MapGroup("/private/todos").MapTodosApi().RequireAuthorization();
    
  • Client results in SignalR

    • Previously, when using SignalR, the server could invoke a method on a client but didn’t have the ability to wait for a response. This scenario is now supported with .NET 7 Preview 4. The server uses ISingleClientProxy.InvokeAsync() to invoke a client method, and the client returns a result from its .On() handler.
  • gRPC JSON transcoding

    • The first preview of gRPC JSON transcoding is now available with .NET 7 Preview 4. gRPC JSON transcoding allows gRPC services to be called as RESTful APIs. This enables apps to support gRPC and REST without duplication.
    • One limitation with gRPC is not every platform can use it. Browsers don't fully support HTTP/2, making REST APIs and JSON the primary way to get data into browser apps. Even with the benefits that gRPC brings, REST APIs and JSON have an important place in modern apps. Building gRPC and JSON Web APIs adds unwanted overhead to app development. (https://docs.microsoft.com/en-gb/aspnet/core/grpc/httpapi)
    syntax = "proto3";
    
    import "google/api/annotations.proto";
    
    package greet;
    
    service Greeter {
      rpc SayHello (HelloRequest) returns (HelloReply) {
    
      option (google.api.http) = {
          get: "/v1/greeter/{name}"
        };
      }
    }
    
    message HelloRequest {
      string name = 1;
    }
    
    message HelloReply {
      string message = 1;
    }
    

    Allows the SayHello gRPC method to be invoked as gRPC and as a JSON Web Api

  • Project template option to use Program.Main method instead of top-level statements

    In .NET 6, we updated the ASP.NET Core project templates to use modern C# features from C# 8 through to C# 10. One of these features was top-level statements, a feature from C# 9, which removes the need to explicitly define an application entry-point, typically in the form of a Main method declared on a Program class (Program.Main).

    While many people appreciate the reduced boilerplate code this feature results in, others expressed frustration that this change was made in the project templates without an option to continue using the traditional Program.Main structure instead.

    In this preview, we’ve added a template option that allows the creation of new projects without using top-level statements. If using the .NET CLI, you can specify the --use-program-main option like so:

    > dotnet new web --use-program-main
    

    If creating projects with Visual Studio, you can select the new “Do not use top-level statements” checkbox during project creation:

  • Rate limiting middleware

    • The new rate limiting middleware in .NET 7 Preview 4 provides a convenient way to limit the rate of incoming HTTP requests.
    • The following example applies a ConcurrencyLimiter globally with a maximum of 1 concurrent lease:
    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseRateLimiter(new RateLimiterOptions
    {
        Limiter = PartitionedRateLimiter.Create<HttpContext, string>(resource =>
        {
            return RateLimitPartition.CreateConcurrencyLimiter("MyLimiter",
                _ => new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1));
        })
    });
    
    app.Run();
    

    The string passed as the first argument to CreateConcurrencyLimiter is a key used to distinguish different component limiters in the PartitionedRateLimiter.

What's new in C# 11

The following features are available in Visual Studio 2022 version 17.3:

  • Extended nameof scope

    • Type parameter names and parameter names are now in scope when used in a nameof  expression in an attribute declaration on that method. This feature means you can use the nameof operator to specify the name of a method parameter in an attribute on the method or parameter declaration. This feature is most often useful to add attributes for nullable analysis.
  • Raw string literals.

    • These are a new format for string literals. Raw string literals can contain arbitrary text, including whitespace, new lines, embedded quotes, and other special characters without requiring escape sequences. A raw string literal starts with at least three double-quote (""") characters. It ends with the same number of double-quote characters. Typically, a raw string literal uses three double quotes on a single line to start the string, and three double quotes on a separate line to end the string. The newlines following the opening quote and preceding the closing quote aren't included in the final content:
    string longMessage = """
        This is a long message.
        It has several lines.
            Some are indented
                    more than others.
        Some should start at the first column.
        Some have "quoted text" in them.
        """;
    

    Any whitespace to the left of the closing double quotes will be removed from the string literal. Raw string literals can be combined with string interpolation to include braces in the output text. Multiple $ characters denote how many consecutive braces start and end the interpolation:

    var location = $$"""
       You are at {{{Longitude}}, {{Latitude}}}
       """;
    
    

    The preceding example specifies that two braces start and end an interpolation. The third repeated opening and closing brace are included in the output string.

  • Newlines in string interpolation expressions.

    • The text inside the { and } characters for a string interpolation can now span multiple lines. The text between the { and } markers is parsed as C#. Any legal C#, including newlines, is allowed. This feature makes it easier to read string interpolations that use longer C# expressions, like pattern matching switch expressions, or LINQ queries.
  • List patterns.

    • List patterns extend pattern matching to match sequences of elements in a list or an array. For example, sequence is [1, 2, 3] is true when the sequence is an array or a list of three integers (1, 2, and 3). You can match elements using any pattern, including constant, type, property and relational patterns. The discard pattern (_) matches any single element, and the new range pattern (..) matches any sequence of zero or more elements.

More information can be found via these links https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-preview-4/ and https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11

.NET 7 stuff

  • Performance improvements, particularly around startup
  • Http 3 support
  • Added new TAR APIs (System.Formats.Tar) that allow reading, writing, archiving, and extracting of Tar archives.

Nullability annotations added to all Microsoft.Extensions libs

Nullability annotations completed across the Microsoft.Extensions.* libraries:

Background:

  • C# 8 introduced nullability checks features to minimize the likelihood that your code causes the runtime to throw NullReferenceException. There are 3 features:
    • Improved static flow analysis to determine if a variable may be null before dereferencing it
    • Variable annotations that devs use to declare the intended null-states for variables
    • Attributes to annotate APIs so that flow analysis can determine null-state

.Net 7 has completed adding [MemberNotNull(nameof(Property))] attributes to all Microsoft.Extensions.* libraries. Why?

  • Adds resilience to the libs
  • Validates the Nullability feature itself
  • Find null-related bugs in the runtime

Adding Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly

  • Prior to Preview 4, the lowest increment of time available in the various date and time structures was the “tick” available in the Ticks property.
  • In .NET, a single tick is 100ns. Developers traditionally have had to perform computations on the “tick” value to determine microsecond and nanosecond values.
  • Preview 4 addresses that by introducing both microseconds and milliseconds to the date and time implementations

More improvements and new APIs for System.Text.RegularExpressions

For preview 4 we are adding the remaining planned APIs in order to add span support into our Regex library. The changes span several issues:

OS project/utility of the week

Agent ransack (https://www.mythicsoft.com/agentransack/)

Finding files that other search engines miss

Agent Ransack is a free file search tool for finding files on your PC or network drives. It has a Lite mode, which is FREE for both personal and commercial use but also a Professional mode that includes optional pay-for features.

First released in April 2000 the Agent Ransack desktop search app has been helping people find files for over 20 years.