C# Nullable Reference Types: IntelliSense Confusion

The feature and concept of Nullable reference types were introduced in C# 8.0 and it basically made all types non-nullable by default and ensured that these types could never be assigned the value null. This is one of my favorite features in C# recently, but there are scenarios where a mixed nullable environment could cause confusion.

Confusion

To enable the assignment of the value null to a type, you have to explicitly mark that type. This uses the same concept of nullable as introduced in C# 2.0, where you, for example, make an int nullable by adding a question mark after it: int?.

When we look at a regular example service-class, we can see which benefits can be had from Nullable reference types:

public class ProductService
{
    // This method accepts a non-null string for 'productId'
    // and always returns a string
    public string FormatProductId(string productId)
    {
        // ...
    }

    // This method accepts a nullable 'formattedProductId'
    // and returns a string or null
    public string? TryGetProductName(string? formattedProductId)
    {
        // ...
    }
}

This makes things all fine and clear. We know that the method FormatProductId never returns null and that it doesn't accept null in its parameter. We also know that the method TryGetProductName returns a string which could be null and that the parameter accepts a string which could be null.

This is great, this means that we don't have to perform a null-check on productId-parameter of the FormatProductId-method, right? Well, not exactly...

Confusion: Mixed nullable environments

In an environment where all your code has Nullable reference types enabled, you can trust the output of a method and the input to its parameters. In a mixed nullable environment, things are not as straight forward, especially when you look at how IntelliSense in Visual Studio signals what to expect from the code.

Scenario 1: Modern app & legacy library

Imagine that your new modern app has Nullable reference types enabled, but you're using an external library that is legacy and does not have this enabled. This external library can be your own old library or something you've included from NuGet.

The problem now becomes that the external library is signaling, for example, that it has a method that returns a string and not a string?, so you should be able to trust that it is not null, right? Unfortunately not. Even with a local non-nullable project, IntelliSense tells me that the returned string is not null, even when it is.

Value is not null

Scenario 2: Legacy app & modern library

Imagine that you have just put together a nice library that you want others to use, either in your project, organization or publicly through NuGet. One of the best parts about using Nullable reference types is that the compiler will warn you if you try to send in a null value as a parameter to a method that explicitly states that it doesn't support null.

Nice, now you can clean out all those noisy null-checks at the top of all the methods, right? Unfortunately not. Your code might be used by another assembly (or an older version of Visual Studio), which doesn't detect the non-nullability.

In a way, this means you have to reverse the way you do null-checks in your code.

public class ProductService
{
    // This method does not accept a null-value
    // and if it does, it should throw an exception
    public string FormatProductId(string productId)
    {
        if (productId == null)
            throw new ArgumentNullException(productId);
        // ...
    }

    // This method accepts null-values
    // and should adjust its logic accordingly
    public string? TryGetProductName(string? formattedProductId)
    {
        return
            formattedProductId != null
            ? GetProductName(formattedProductId)
            : null;
    }
}

Key takeaways

My own take-aways from exploring this aspect of Nullable reference types are:

  • When building a library, always check for null in incoming method-arguments, even when Nullable reference types is enabled
  • When consuming an external legacy library, don't trust the return-type to not be null (even if it says it's not)
  • In a mixed nullable environment, the feature to guard us against NullReferenceExceptions is likely to mistakenly cause some more of them
  • When this feature is fully adopted, there will be a reduction in a lot of the overhead in null-handling code

Thoughts

Hopefully, in .NET 5, this feature is enabled by default and these kinds of confusions, and described associated errors can be avoided.

One idea for an improvement to the IntelliSense-behavior around assemblies that are not known to have Nullable reference types enabled could be to show all these types as nullable. Both because it makes things super-clear, but also because of the fact that they actually are nullable.

This change would make everything in the whole .NET Core CLR light up as nullable, but as of .NET Core 3.1, it all is nullable, by definition.

HTML Encode TagHelper in ASP.NET Core

For a specific scenario recently, I wanted to display the HTML-encoded output of a TagHelper in ASP.NET Core. So I wanted to use the TagHelper, but not output its actual result, but see the raw HTML which would have been included in my template.

HTML

So I created another TagHelper, which allows me to wrap any HTML, inline code in ASP.NET Core and other TagHelpers, and get all the content inside the TagHelper's tag to be HTML-encoded, like this:

<html-encode>
    <a href="@Url.Action("Index")">Read More</a>
    @Html.TextBox("No_Longer_Recommended-TagHelpers_Preferred")
    <my-other-tag-helper />
</html-encode>

From this, I will get the raw HTML of the link with an UrlHelper-result, the result of the HTML-helper and the result of my other TagHelper.

The source-code for the html-encode-TagHelper is as follows:

[HtmlTargetElement("html-encode", TagStructure = TagStructure.NormalOrSelfClosing)]
public class HtmlEncodeTagHelper : TagHelper
{
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var childContent = output.Content.IsModified
            ? output.Content.GetContent()
            : (await output.GetChildContentAsync()).GetContent();

        string encodedChildContent = WebUtility.HtmlEncode(childContent ?? string.Empty);

        output.TagName = null;
        output.Content.SetHtmlContent(encodedChildContent);
    }
}

API Rate Limit HTTP Handler with HttpClientFactory

Most APIs have a Rate Limit of some sort. For example, GitHub has a limit of 5000 requests per hour. This can partly be handled by limiting your use by timing your requests to the API or through caching of the results.

What about when an API limits your requests per second? This is probably something you would want to handle somewhere central in your code and not spread out everywhere where you make an HTTP call to the API.

Funnel

For me, the solution was to add a Outgoing request middleware to the setup of the HttpClientFactory.

With this, I can just configure the startup services to use this RateLimitHttpMessageHandler-class with the HttpClientFactory:

services
    .AddHttpClient<IApi, Api>()
    .AddHttpMessageHandler(() =>
        new RateLimitHttpMessageHandler(
            limitCount: 5,
            limitTime: TimeSpan.FromSeconds(1)))
    .AddDefaultTransientHttpErrorPolicy();

This ensures that wherever I use the class IApi, through dependency injection, it will limit the calls to the API to only 5 calls per second.

The simplified version of code for the RateLimitHttpMessageHandler:

public class RateLimitHttpMessageHandler : DelegatingHandler
{
    private readonly List<DateTimeOffset> _callLog =
        new List<DateTimeOffset>();
    private readonly TimeSpan _limitTime;
    private readonly int _limitCount;

    public RateLimitHttpMessageHandler(int limitCount, TimeSpan limitTime)
    {
        _limitCount = limitCount;
        _limitTime = limitTime;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var now = DateTimeOffset.UtcNow;

        lock (_callLog)
        {
            _callLog.Add(now);

            while (_callLog.Count > _limitCount)
                _callLog.RemoveAt(0);
        }

        await LimitDelay(now);

        return await base.SendAsync(request, cancellationToken);
    }

    private async Task LimitDelay(DateTimeOffset now)
    {
        if (_callLog.Count < _limitCount)
            return;

        var limit = now.Add(-_limitTime);

        var lastCall = DateTimeOffset.MinValue;
        var shouldLock = false;

        lock (_callLog)
        {
            lastCall = _callLog.FirstOrDefault();
            shouldLock = _callLog.Count(x => x >= limit) >= _limitCount;
        }

        var delayTime = shouldLock && (lastCall > DateTimeOffset.MinValue)
            ? (limit - lastCall)
            : TimeSpan.Zero;

        if (delayTime > TimeSpan.Zero)
            await Task.Delay(delayTime);
    }
}

dotnet-guid: Generate GUIDs/UUIDs with the Command Line

There have been a few projects, although not many, which I've been involved in, where generating GUIDs/UUIDs has been important. For that, I used to use online-tools like https://guidgenerator.com, but when I switched machine, the autocomplete in my browser lost that link.

Minimally for this reason, and mostly for fun, I decided to write a .NET Core Global Tool which quickly generates one or multiple GUIDs/UUIDs, in whatever format could be needed.

Installation

Download the .NET Core SDK 2.1 or later. The install the dotnet-guid .NET Global Tool, using the command-line:

dotnet tool install -g dotnet-guid

Usage

Usage: guid [arguments] [options]

Arguments:
  Count         Defines how may GUIDs/UUIDs to generate. Defaults to 1.

Options:
  -?|-h|--help  Show help information
  -n            Formatted as 32 digits:
                00000000000000000000000000000000
  -d            Formatted as 32 digits separated by hyphens:
                00000000-0000-0000-0000-000000000000
  -b            Formatted as 32 digits separated by hyphens, enclosed in braces:
                {00000000-0000-0000-0000-000000000000}
  -p            Formatted as 32 digits separated by hyphens, enclosed in parentheses:
                (00000000-0000-0000-0000-000000000000)
  -x            Formatted as four hexadecimal values enclosed in braces,
                where the fourth value is a subset of eight hexadecimal values that is also enclosed in braces:
                {0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}
  -e            Defines if the GUIDs/UUIDs should be empty, using zero-values only.
  -u            Defines if the GUIDs/UUIDs generated should be upper-cased letters.

Examples

To get a single GUID/UUID, simply type:

guid

To get 3 random GUIDs/UUIDs, with letters in upper-case, formatted with brackets:

guid 3 -b -u

You can find the source-code on GitHub, the package on Nuget and the latest builds on MyGet.

You can find a great list of more .NET Core Global Tools on GitHub, maintained by Nate McMaster.

dotnet-cleanup: Clean Up Solution, Project & Folder

We developers like to think of developing software as an exact science, but sometimes, you just need to wipe your source-files to solve some kinds of problems.

For .NET-developers, there are many issues on Stackoverflow which are solved by just deleting your bin and obj-folders. For people using Node.js, probably just as many answers contains the step of removing your node_modules-folder.

Those are some of the reasons why I created dotnet-cleanup, which is a .NET Core Global Tool for cleaning up solution, project or folder. This was made easy by following Nate McMaster's post on getting started with creating a .NET Core global tool package.

Deleted files and folders are first moved to a temporary folder before deletion, so you can continue working with your projects, while the tool keeps cleaning up in background.

Installation

Download the .NET Core SDK 2.1 or later. The install the dotnet-cleanup .NET Global Tool, using the command-line:

dotnet tool install -g dotnet-cleanup

Usage

Usage: cleanup [arguments] [options]

Arguments:
  PATH                  Path to the solution-file, project-file or folder to clean. Defaults to current working directory.

Options:
  -p|--paths            Defines the paths to clean. Defaults to 'bin', 'obj' and 'node_modules'.
  -y|--confirm-cleanup  Confirm prompt for file cleanup automatically.
  -nd|--no-delete       Defines if files should be deleted, after confirmation.
  -nm|--no-move         Defines if files should be moved before deletion, after confirmation.
  -t|--temp-path        Directory in which the deleted items should be moved to before being cleaned up. Defaults to system Temp-folder.
  -v|--verbosity        Sets the verbosity level of the command. Allowed levels are Minimal, Normal, Detailed and Debug.
  -?|-h|--help          Show help information

The argument PATH can point to a specific .sln-file or a project-file (.csproj, .fsharp, .vbproj). If a .sln-file is specified, all its projects will be cleaned.

If it points to a folder, the folder will be scanned for a single solution-file and then for a single project-file. If multiple files are detected an error will be shown and you need to specify the file.

If not solution or project is found, the folder will be cleaned as a project.

Example

To cleanup a typical web-project, you can specify the paths to be cleaned in the projects like this:

cleanup -p "bin" -p "obj"  -p "artifacts" -p "npm_modules"

You can find the source-code on GitHub, the latest builds on MyGet and the package on Nuget.

You can find a great list of more .NET Core Global Tools on GitHub, maintained by Nate McMaster.