Categories
c# Uncategorized

Making Users Re-Enter their Password: ASP.NET Core & IdentityServer4

It’s often good security behaviour to require users to re-enter their password when they want to change some secure property of their account, like generate personal access tokens, or change their Multi-factor Authentication (MFA) settings.

You may have seen the Github ‘sudo’ mode, which asks you to re-enter your password when you try to change something sensitive.

Sudo Mode Dialog
The Github sudo mode prompt.

Most of the time a user’s session is long-lived, so when they want to do something sensitive, it’s best to check they still are who they say.

I’ve been working on the implementation of IdentityServer4 at Enclave for the past week or so, and had this requirement to require password confirmation before users can modify their MFA settings.

I thought I’d write up how I did this for posterity, because it took a little figuring out.

The Layout

In our application, we have two components, both running on ASP.NET Core 3.1

  • The Accounts app that holds all the user data; this is where Identity Server runs; we use ASP.NET Core Identity to do the actual user management.
  • The Portal app that holds the UI. This is a straightforward MVC app right now, no JS or SPA to worry about.

To make changes to a user’s account settings, the Profile Controller in the Portal app makes API calls to the Accounts app.

The Portal calls APIs in the Accounts app

All the API calls to the Accounts app are already secured using the Access Token from when the user logged in; we have an ASP.NET Core Policy in place for our additional API (as per the IdentityServer docs) to protect it.

The Goal

The desired outcome here is that specific sensitive API endpoints within the Accounts app require the calling user to have undergone a second verification, where they must have re-entered their password recently in order to use the API.

What we want to do is:

  • Allow the Portal app to request a ‘step-up’ access token from the Accounts app.
  • Limit the step-up access token to a short lifetime (say 15 minutes), with no refresh tokens.
  • Call a sensitive API on the Accounts App, and have the Accounts App validate the step-up token.

Issuing the Step-Up Token

First up, we need to generate a suitable access token when asked. I’m going to add a new controller, StepUpApiController, in the Accounts app.

This controller is going to have a single endpoint, which requires a regular access token before you can call it.

We’re going to use the provided IdentityServerTools class, that we can inject into our controller, to do the actual token generation.

Without further ado, let’s look at the code for the controller:

[Route("api/stepup")]
[ApiController]
[Authorize(ApiScopePolicy.WriteUser)]
public class StepUpApiController : ControllerBase
{
    private static readonly TimeSpan ValidPeriod = TimeSpan.FromMinutes(15);

    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IdentityServerTools _idTools;

    public StepUpApiController(UserManager<ApplicationUser> userManager,
                               IdentityServerTools idTools)
    {
        _userManager = userManager;
        _idTools = idTools;
    }

    [HttpPost]
    public async Task<StepUpApiResponse> StepUp(StepUpApiModel model)
    {
        var user = await _userManager.GetUserAsync(User);

        // Verify the provided password.
        if (await _userManager.CheckPasswordAsync(user, model.Password))
        {
            var clientId = User.FindFirstValue(JwtClaimTypes.ClientId);

            var claims = new Claim[]
            {
                new Claim(JwtClaimTypes.Subject, User.FindFirstValue(JwtClaimTypes.Subject)),
            };

            // Create a token that:
            //  - Is associated to the User's client.
            //  - Is only valid for our configured period (15 minutes)
            //  - Has a single scope, indicating that the token can only be used for stepping up.
            //  - Has the same subject as the user.
            var token = await _idTools.IssueClientJwtAsync(
                clientId,
                (int)ValidPeriod.TotalSeconds,
                new[] { "account-stepup" },
                additionalClaims: claims);

            return new StepUpApiResponse { Token = token, ValidUntil = DateTime.UtcNow.Add(ValidPeriod) };
        }

        Response.StatusCode = StatusCodes.Status401Unauthorized;

        return null;
    }
}

A couple of important points here:

  • In order to even access this API, the normal access token being passed in the requested must conform to our own WriteUser scope policy, which requires a particular scope be in the access token to get to this API.
  • This generated access token is really basic; it has a single scope, “account-stepup”, and only a single additional claim containing the subject.
  • We associate the step-up token to the same client ID as the normal access token, so only the requesting client can use that token.
  • We explicitly state a relatively short lifetime on the token (15 minutes here).

Sending the Token

This is the easy bit; once you have the token, you can store it somewhere in the client, and send it in a subsequent request.

Before sending the step-up token, you’ll want to check the expiry on it, and if you need a new one, then prompt the user for their credentials and start the process again.

For any request to the sensitive API, we need to include both the normal access token from the user’s session, plus the new step-up token.

I set this up when I create the HttpClient:

private async Task<HttpClient> GetClient(string? stepUpToken = null)
{
    var client = new HttpClient();

    // Set the base address to the URL of our Accounts app.
    client.BaseAddress = _accountUrl;

    // Get the regular user access token in the session and add that as the normal
    // Authorization Bearer token.
    // _contextAccessor is an instance of IHttpContextAccessor.
    var accessToken = await _contextAccessor.HttpContext.GetUserAccessTokenAsync();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    if (stepUpToken is object)
    {
        // We have a step-up token; include it as an additional header (without the Bearer indicator).
        client.DefaultRequestHeaders.Add("X-Authorization-StepUp", stepUpToken);
    }

    return client;
}

That X-Authorization-StepUp header is where we’re going to look when checking for the token in the Accounts app.

Validating the Step-Up Token

To validate a provided step-up token in the Accounts app, I’m going to define a custom ASP.NET Core Policy that requires the API call to provide a step-up token.

If there are terms in here that don’t seem immediately obvious, check out the docs on Policy-based authorization in ASP.NET Core. It’s a complex topic, but the docs do a pretty good job of breaking it down.

Let’s take a look at an API call endpoint that requires step-up:

[ApiController]
[Route("api/user")]
[Authorize(ApiScopePolicy.WriteUser)]
public class UserApiController : Controller
{
    [HttpPost("totp-enable")]
    [Authorize("require-stepup")]
    public async Task<IActionResult> EnableTotp(TotpApiEnableModel model)
    {
        // ... do stuff ...
    }
}

That Authorize attribute I placed on the action method specifies that we want to enforce a require-stepup policy on this action. Authorize attributes are additive, so a request to EnableTotp requires both our normal WriteUser policy and our step-up policy.

Defining our Policy

To define our require-stepup policy, lets jump over to our Startup class; specifically, in ConfigureServices, where we set up Authorization using the AddAuthorization method:

services.AddAuthorization(options =>
{
    // Other policies omitted...

    options.AddPolicy("require-stepup", policy =>
    { 
        policy.AddAuthenticationSchemes("local-api-scheme");
        policy.RequireAuthenticatedUser();
        
        // Add a new requirement to the policy (for step-up).
        policy.AddRequirements(new StepUpRequirement());
    });
});

The ‘local-api-scheme’ is the built-in scheme provided by IdentityServer for protecting local API calls.

That requirement class, StepUpRequirement is just a simple marker class for indicating to the policy that we need step-up. It’s also how we wire up a handler to check that requirement:

public class StepUpRequirement : IAuthorizationRequirement
{
}

Defining our Authorization Handler

We now need an Authorization Handler that lets us check incoming requests meet our new step-up requirement.

So, let’s create one:

public class StepUpAuthorisationHandler : AuthorizationHandler<StepUpRequirement>
{
    private const string StepUpTokenHeader = "X-Authorization-StepUp";

    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ITokenValidator _tokenValidator;

    public StepUpAuthorisationHandler(
        IHttpContextAccessor httpContextAccessor,
        ITokenValidator tokenValidator)
    {
        _httpContextAccessor = httpContextAccessor;
        _tokenValidator = tokenValidator;
    }

    /// <summary>
    /// Called by the framework when we need to check a request.
    /// </summary>
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        StepUpRequirement requirement)
    {
        // Only interested in authenticated users.
        if (!context.User.IsAuthenticated())
        {
            return;
        }

        var httpContext = _httpContextAccessor.HttpContext;

        // Look for our special request header.
        if (httpContext.Request.Headers.TryGetValue(StepUpTokenHeader, out var stepUpHeader))
        {
            var headerValue = stepUpHeader.FirstOrDefault();

            if (!string.IsNullOrEmpty(headerValue))
            {
                // Call our method to check the token.
                var validated = await ValidateStepUp(context.User, headerValue);

                // Token was valid, so succeed.
                // We don't explicitly have to fail, because that is the default.
                if (validated)
                {
                    context.Succeed(requirement);
                }
            }
        }
    }

    private async Task<bool> ValidateStepUp(ClaimsPrincipal user, string header)
    {
        // Use the normal token validator to check the access token is valid, and contains our
        // special expected scope.
        var validated = await _tokenValidator.ValidateAccessTokenAsync(header, "account-stepup");

        if (validated.IsError)
        {
            // Bad token.
            return false;
        }

        // Validate that the step-up token is for the same client as the access token.
        var clientIdClaim = validated.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.ClientId);

        if (clientIdClaim is null || clientIdClaim.Value != user.FindFirstValue(JwtClaimTypes.ClientId))
        {
            return false;
        }

        // Confirm a subject is supplied.
        var subjectClaim = validated.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);

        if (subjectClaim is null)
        {
            return false;
        }

        // Confirm that the subject of the stepup and the current user are the same.
        return subjectClaim.Value == user.FindFirstValue(JwtClaimTypes.Subject);
    }
}

Again, let’s take a look at the important bits of the class:

  • The handler derives from AuthorizationHandler<StepUpRequirement>, indicating to ASP.NET that we are a handler for our custom requirement.
  • We stop early if there is no authenticated user; that’s because the step-up token is only valid for a user who is already logged in.
  • We inject and use IdentityServer’s ITokenValidator interface to let us validate the token using ValidateAccessTokenAsync; we specify the scope we require.
  • We check that the client ID of the step-up token is the same as the regular access token used to authenticate with the API.
  • We check that the subjects match (i.e. this step-up token is for the same user).

The final hurdle is to register our authorization handler in our Startup class:

services.AddSingleton<IAuthorizationHandler, StepUpAuthorisationHandler>();

Wrapping Up

There you go, we’ve now got a secondary access token being issued to indicate step-up has been done, and we’ve got a custom authorization handler to check our new token.

Categories
Uncategorized

Loading Plugins/Extensions at Run Time from NuGet in .NET Core : Part 1 – NuGet

This post is the first in a short series, writing up my efforts creating an plugin/extension system working in .NET Core that:

  • Loads extension packages from NuGet, with all their dependencies (this post).
  • Loads the extensions into my .NET Core Process.
  • Allows the loaded extensions to be unloaded.

Background

As a bit of context, I’m currently building an open-source BDD testing platform that goes beyond Gherkin, AutoStep, which is built entirely in C#, on top of .NET Core 3.1.

In AutoStep, I need to be able to load in extensions that provide additional functionality for AutoStep. For example, extensions might provide:

  • Bindings for some UI platform or library
  • Custom Report Formats
  • Integration with some external Test Management System

In terms of what’s in them, AutoStep extensions are going to consist of things like:

  • .NET DLLs
  • AutoStep Test Files
  • Dependencies on various NuGet packages (Selenium.WebDriver anyone?).

All of the above items fit pretty well within the scope of NuGet packages, and I don’t want to build my own extension packaging, hosting, versioning and so on, so I’m going to say that each extension can be represented by a NuGet package.

AutoStep does not require the .NET Core SDK to build or run any tests, so I can’t just create a csproj, chuck PackageReferences in and be done with it.

I need to bake the idea of extensions into the platform itself.


If you want to jump ahead, you can check out the GitHub repository for AutoStep.Extensions, which provides the NuGet package used to load extensions into our VS Code Language Server and our commmand-line runner.

Loading Extensions from NuGet

Microsoft supplies the NuGet Client SDK, to work with both NuGet packages and source repositories; specifically the NuGet.Protocol and NuGet.Resolver packages.

The documentation on how to actually use the NuGet Client libraries is a bit sparse, so I’m permanently indebted to Martin Bjorkstrom for writing a blog post on it that I used as a pretty detailed guide to get me started.

Loading our extension packages from NuGet involves three phases:

  1. Determine the best version of an extension package to install, given a version range (and normal NuGet rules).
    For example, if the version of the extension requested is 1.4.0, and there is a 1.4.5 version available, we want that one.
  2. Get the list of all NuGet package dependencies (recursively) for each extension.
  3. Download and Extract your packages.

Choosing the Extension Version

This is (relatively) the easy bit. First up, we’ll create some of the context objects we need to get started:

/// <summary>
/// Represents the configuration for a single extension to install.
/// </summary>
public class ExtensionConfiguration
{
    public string Package { get; set; }
    public string Version { get; set; }
    public bool PreRelease { get; set; }
}

public async Task LoadExtensions()
{
    // Define a source provider, with the main NuGet feed, plus my own feed.
    var sourceProvider = new PackageSourceProvider(NullSettings.Instance, new[]
    {
        new PackageSource("https://api.nuget.org/v3/index.json"),
        new PackageSource("https://f.feedz.io/autostep/ci/nuget/index.json")
    });

    // Establish the source repository provider; the available providers come from our custom settings.
    var sourceRepositoryProvider = new SourceRepositoryProvider(sourceProvider, Repository.Provider.GetCoreV3());

    // Get the list of repositories.
    var repositories = sourceRepositoryProvider.GetRepositories();

    // Disposable source cache.
    using var sourceCacheContext = new SourceCacheContext();

    // You should use an actual logger here, this is a NuGet ILogger instance.
    var logger = new NullLogger();

    // My extension configuration:
    var extensions = new[]
    { 
        new ExtensionConfiguration
        {
            Package = "AutoStep.Web",
            PreRelease = true // Allow pre-release versions.
        }
    };
}

Next, let’s write a method to actually get the desired package identity to install. The GetPackageIdentity method goes through each repository, and either:

  • Picks the latest available version if no version range has been configured or,
  • If a version range has been specified, uses the provided NuGet VersionRange class to find the best match given the set of all versions.
private async Task<PackageIdentity> GetPackageIdentity(
          ExtensionConfiguration extConfig, SourceCacheContext cache, ILogger nugetLogger,
          IEnumerable<SourceRepository> repositories, CancellationToken cancelToken)
{
    // Go through each repository.
    // If a repository contains only pre-release packages (e.g. AutoStep CI), and 
    // the configuration doesn't permit pre-release versions,
    // the search will look at other ones (e.g. NuGet).
    foreach (var sourceRepository in repositories)
    {
        // Get a 'resource' from the repository.
        var findPackageResource = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();

        // Get the list of all available versions of the package in the repository.
        var allVersions = await findPackageResource.GetAllVersionsAsync(extConfig.Package, cache, nugetLogger, cancelToken);

        NuGetVersion selected;

        // Have we specified a version range?
        if (extConfig.Version != null)
        {
            if (!VersionRange.TryParse(extConfig.Version, out var range))
            {
                throw new InvalidOperationException("Invalid version range provided.");
            }

            // Find the best package version match for the range.
            // Consider pre-release versions, but only if the extension is configured to use them.
            var bestVersion = range.FindBestMatch(allVersions.Where(v => extConfig.PreRelease || !v.IsPrerelease));

            selected = bestVersion;
        }
        else
        {
            // No version; choose the latest, allow pre-release if configured.
            selected = allVersions.LastOrDefault(v => v.IsPrerelease == extConfig.PreRelease);
        }

        if (selected is object)
        {
            return new PackageIdentity(extConfig.Package, selected);
        }
    }

    return null;
}

Let’s plug that code into our previous code, so we’re now getting the identity:

// ...

// My extension configuration:
var extensions = new[] { new ExtensionConfiguration
{
    Package = "AutoStep.Web",
    PreRelease = true // Allow pre-release versions.
}};

foreach (var ext in extensions)
{
    var packageIdentity = await GetPackageIdentity(ext, sourceCacheContext, logger, repositories, CancellationToken.None);

    if (packageIdentity is null)
    {
        throw new InvalidOperationException($"Cannot find package {ext.Package}.");
    }
}

With this we get a package identity of AutoStep.Web.1.0.0-develop.20 (the latest pre-release version at the time).

Get the List of Package Dependencies

This is where things get interesting. We need to get the complete set of all dependencies, across all the extensions, that we need to install in order to use the extension package.

First off, let’s look at an initial, very naive solution, which just does a straight-forward recurse through the entire dependency graph.

private async Task GetPackageDependencies(PackageIdentity package, SourceCacheContext cacheContext, 
                                          NuGetFramework framework, ILogger logger, 
                                          IEnumerable<SourceRepository> repositories,
                                          ISet<SourcePackageDependencyInfo> availablePackages, 
                                          CancellationToken cancelToken)
{
    // Don't recurse over a package we've already seen.
    if (availablePackages.Contains(package))
    {
        return;
    }

    foreach (var sourceRepository in repositories)
    {
        // Get the dependency info for the package.
        var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>();
        var dependencyInfo = await dependencyInfoResource.ResolvePackage(
            package,
            framework,
            cacheContext,
            logger,
            cancelToken);

        // No info for the package in this repository.
        if (dependencyInfo == null)
        {
            continue;
        }

        // Add to the list of all packages.
        availablePackages.Add(dependencyInfo);

        // Recurse through each package.
        foreach (var dependency in dependencyInfo.Dependencies)
        {
            await GetPackageDependencies(
                new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion),
                cacheContext,
                framework,
                logger,
                repositories,
                availablePackages,
                cancelToken);
        }

        break;
    }
}

That does indeed create the complete graph of all libraries required by that extension, the problem is that it has 104 packages in it!

Long package list

I’ve got the AutoStep.Web package at the top there, but I’ve also got
System.Runtime, which I definitely don’t want.

All the extensions are going to reference the AutoStep.Extensions.Abstractions package (because that’s where we define our interfaces for extensions), but we don’t want to download it ourselves!

Besides the fact that we don’t need to download these shared packages, if we load in the AutoStep.Extensions.Abstractions assembly from the extension’s dependencies, it will not be compatible with the version referenced by the host process.

The actual requirement for our behaviour here is:

All packages provided by the host process should be excluded from the set of dependencies to install.

Filtering the Dependencies

At runtime, how do we know what the set of installed packages are for a .NET Core Application? Luckily, there happens to be an existing file containing this information, the {AssemblyName}.deps.json file that gets copied to your output directory.

You probably haven’t had to worry about it much, but if you look in your application’s output directory, you’ll find it.

It contains the complete package reference graph for your application, and looks a little something like this:

{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v3.1",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v3.1": {
      "NugetConsole/1.0.0": {
        "dependencies": {
          "Microsoft.Extensions.DependencyModel": "3.1.3",
          "NuGet.Protocol": "5.5.1",
          "NuGet.Resolver": "5.5.1"
        },
        "runtime": {
          "NugetConsole.dll": {}
        }
      },
      "Microsoft.CSharp/4.0.1": {
        "dependencies": {
          "System.Collections": "4.3.0",
          "System.Diagnostics.Debug": "4.3.0",
          "System.Dynamic.Runtime": "4.3.0",
          "System.Globalization": "4.3.0",
          "System.Linq": "4.3.0",
          "System.Linq.Expressions": "4.3.0",
          "System.ObjectModel": "4.3.0",

// ...a lot more content...

Handily, we don’t have to parse this ourselves. If you add the Microsoft.Extensions.DependencyModel package to your project, you can directly access this content using DependencyContext.Default, which gives you a DependencyContext you can interrogate.

Let’s define a method that takes this DependencyContext and a PackageDependency, and checks whether it is provided by the host:

private bool DependencySuppliedByHost(DependencyContext hostDependencies, PackageDependency dep)
{
    // See if a runtime library with the same ID as the package is available in the host's runtime libraries.
    var runtimeLib = hostDependencies.RuntimeLibraries.FirstOrDefault(r => r.Name == dep.Id);

    if (runtimeLib is object)
    {
        // What version of the library is the host using?
        var parsedLibVersion = NuGetVersion.Parse(runtimeLib.Version);

        if (parsedLibVersion.IsPrerelease)
        {
            // Always use pre-release versions from the host, otherwise it becomes
            // a nightmare to develop across multiple active versions.
            return true;
        }
        else
        {
            // Does the host version satisfy the version range of the requested package?
            // If so, we can provide it; otherwise, we cannot.
            return dep.VersionRange.Satisfies(parsedLibVersion);
        }
    }

    return false;
}

Then, let’s plug that in to our existing GetPackageDependencies method:

private async Task GetPackageDependencies(PackageIdentity package, SourceCacheContext cacheContext, NuGetFramework framework, 
                                          ILogger logger, IEnumerable<SourceRepository> repositories, DependencyContext hostDependencies,
                                          ISet<SourcePackageDependencyInfo> availablePackages, CancellationToken cancelToken)
{
    // Don't recurse over a package we've already seen.
    if (availablePackages.Contains(package))
    {
        return;
    }

    foreach (var sourceRepository in repositories)
    {
        // Get the dependency info for the package.
        var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>();
        var dependencyInfo = await dependencyInfoResource.ResolvePackage(
            package,
            framework,
            cacheContext,
            logger,
            cancelToken);

        // No info for the package in this repository.
        if (dependencyInfo == null)
        {
            continue;
        }


        // Filter the dependency info.
        // Don't bring in any dependencies that are provided by the host.
        var actualSourceDep = new SourcePackageDependencyInfo(
            dependencyInfo.Id,
            dependencyInfo.Version,
            dependencyInfo.Dependencies.Where(dep => !DependencySuppliedByHost(hostDependencies, dep)),
            dependencyInfo.Listed,
            dependencyInfo.Source);

        availablePackages.Add(actualSourceDep);

        // Recurse through each package.
        foreach (var dependency in actualSourceDep.Dependencies)
        {
            await GetPackageDependencies(
                new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion),
                cacheContext,
                framework,
                logger,
                repositories,
                hostDependencies,
                availablePackages,
                cancelToken);
        }

        break;
    }
}

This cuts down on the set of packages significantly, but it’s still pulling down some runtime-provided packages I don’t want:

AutoStep.Web : 1.0.0-develop.20       // correct
Selenium.Chrome.WebDriver : 79.0.0    // correct
Selenium.WebDriver : 3.141.0          // correct
Newtonsoft.Json : 10.0.3              // correct
Microsoft.CSharp : 4.3.0              // Ah. This is a runtime package...
System.ComponentModel.TypeConverter : 4.3.0 
System.Collections.NonGeneric : 4.3.0       
System.Collections.Specialized : 4.3.0
System.ComponentModel : 4.3.0
System.ComponentModel.Primitives : 4.3.0
System.Runtime.Serialization.Primitives : 4.3.0
System.Runtime.Serialization.Formatters : 4.3.0
System.Xml.XmlDocument : 4.3.0

So, something is still not right. What’s causing these packages to be present?

Well, simply put, my program doesn’t use System.ComponentModel, so it isn’t in the list of my dependencies. But it is provided by the host, because it’s part of the distributed .NET Runtime.

Ignoring Runtime-Provided Packages

We want to filter out runtime-provided packages completely, but how do we know which ones to exclude? We can’t just filter out any System.* packages, because there are a number of System.* packages that aren’t shipped with the runtime (e.g. System.Text.Json).

As far as I can tell, it’s more or less impossible to determine the full set at run time dynamically.

After some considerable searching however, I found a complete listing of all runtime-provided packages in an MSBuild task in the dotnet SDK, called PackageConflictOverrides, which tells the build system which packages don’t need to be restored! Yay!

This allowed me to define the following static lookup class (excerpt only). You can find a full version here.

/// <summary>
/// Contains a pre-determined list of NuGet packages that are provided by the run-time, and
/// therefore should not be restored from an extensions dependency list.
/// </summary>
internal static class RuntimeProvidedPackages
{
    /// <summary>
    /// Checks whether the set of known runtime packages contains the given package ID.
    /// </summary>
    /// <param name="packageId">The package ID.</param>
    /// <returns>True if the package is provided by the framework, otherwise false.</returns>
    public static bool IsPackageProvidedByRuntime(string packageId)
    {
        return ProvidedPackages.Contains(packageId);
    }

    /// <summary>
    /// This list comes from the package overrides for the .NET SDK,
    /// at https://github.com/dotnet/sdk/blob/v3.1.201/src/Tasks/Common/targets/Microsoft.NET.DefaultPackageConflictOverrides.targets.
    /// If the executing binaries ever change to a newer version, this project must update as well, and refresh this list.
    /// </summary>
    private static readonly ISet<string> ProvidedPackages = new HashSet<string>
    {
        "Microsoft.CSharp",
        "Microsoft.Win32.Primitives",
        "Microsoft.Win32.Registry",
        "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple",
        "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl",
        "System.AppContext",
        "System.Buffers",
        "System.Collections",
        "System.Collections.Concurrent",
        // Removed a load for brevity....
        "System.Xml.ReaderWriter",
        "System.Xml.XDocument",
        "System.Xml.XmlDocument",
        "System.Xml.XmlSerializer",
        "System.Xml.XPath",
        "System.Xml.XPath.XDocument",
    };
}

Ok, so let’s update our DependencySuppliedByHost method to use this look-up:

private bool DependencySuppliedByHost(DependencyContext hostDependencies, PackageDependency dep)
{
    // Check our look-up list.
    if(RuntimeProvidedPackages.IsPackageProvidedByRuntime(dep.Id))
    {
        return true;
    }

    // See if a runtime library with the same ID as the package is available in the host's runtime libraries.
    var runtimeLib = hostDependencies.RuntimeLibraries.FirstOrDefault(r => r.Name == dep.Id);

    if (runtimeLib is object)
    {
        // What version of the library is the host using?
        var parsedLibVersion = NuGetVersion.Parse(runtimeLib.Version);

        if (parsedLibVersion.IsPrerelease)
        {
            // Always use pre-release versions from the host, otherwise it becomes
            // a nightmare to develop across multiple active versions.
            return true;
        }
        else
        {
            // Does the host version satisfy the version range of the requested package?
            // If so, we can provide it; otherwise, we cannot.
            return dep.VersionRange.Satisfies(parsedLibVersion);
        }
    }

    return false;
}

Now, when we run our code, we get precisely the set of packages we want!

AutoStep.Web : 1.0.0-develop.20
Selenium.Chrome.WebDriver : 79.0.0
Selenium.WebDriver : 3.141.0
Newtonsoft.Json : 10.0.3

Downloading and Extracting

At the moment, our list of dependencies ‘might’ contain duplicates. For example,
two different extensions might reference two different versions of NewtonSoft.Json.
We need to pick one to install that will be compatible with both.

To do this, we need to use the supplied PackageResolver class to constrain the set of packages
to only the ones we want to actually download and install, in a new GetPackagesToInstall method:

private IEnumerable<SourcePackageDependencyInfo> GetPackagesToInstall(SourceRepositoryProvider sourceRepositoryProvider, 
                                                                      ILogger logger, IEnumerable<ExtensionConfiguration> extensions, 
                                                                      HashSet<SourcePackageDependencyInfo> allPackages)
{
    // Create a package resolver context.
    var resolverContext = new PackageResolverContext(
            DependencyBehavior.Lowest,
            extensions.Select(x => x.Package),
            Enumerable.Empty<string>(),
            Enumerable.Empty<PackageReference>(),
            Enumerable.Empty<PackageIdentity>(),
            allPackages,
            sourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource),
            logger);

    var resolver = new PackageResolver();

    // Work out the actual set of packages to install.
    var packagesToInstall = resolver.Resolve(resolverContext, CancellationToken.None)
                                    .Select(p => allPackages.Single(x => PackageIdentityComparer.Default.Equals(x, p)));
    return packagesToInstall;
}

Once we have that list, we can pass it to another new method that actually downloads and extracts the packages for us, InstallPackages.

private async Task InstallPackages(SourceCacheContext sourceCacheContext, ILogger logger, 
                                    IEnumerable<SourcePackageDependencyInfo> packagesToInstall, string rootPackagesDirectory, 
                                    ISettings nugetSettings, CancellationToken cancellationToken)
{
    var packagePathResolver = new PackagePathResolver(rootPackagesDirectory, true);
    var packageExtractionContext = new PackageExtractionContext(
        PackageSaveMode.Defaultv3,
        XmlDocFileSaveMode.Skip,
        ClientPolicyContext.GetClientPolicy(nugetSettings, logger),
        logger);

    foreach (var package in packagesToInstall)
    {
        var downloadResource = await package.Source.GetResourceAsync<DownloadResource>(cancellationToken);

        // Download the package (might come from the shared package cache).
        var downloadResult = await downloadResource.GetDownloadResourceResultAsync(
            package,
            new PackageDownloadContext(sourceCacheContext),
            SettingsUtility.GetGlobalPackagesFolder(nugetSettings),
            logger,
            cancellationToken);

        // Extract the package into the target directory.
        await PackageExtractor.ExtractPackageAsync(
            downloadResult.PackageSource,
            downloadResult.PackageStream,
            packagePathResolver,
            packageExtractionContext,
            cancellationToken);
    }
}

Let’s go ahead and plug those extra methods into our main calling method:

public async Task LoadExtensions()
{
    // Define a source provider, with nuget, plus my own feed.
    var sourceProvider = new PackageSourceProvider(NullSettings.Instance, new[]
    {
        new PackageSource("https://api.nuget.org/v3/index.json"),
        new PackageSource("https://f.feedz.io/autostep/ci/nuget/index.json")
    });

    // Establish the source repository provider; the available providers come from our custom settings.
    var sourceRepositoryProvider = new SourceRepositoryProvider(sourceProvider, Repository.Provider.GetCoreV3());

    // Get the list of repositories.
    var repositories = sourceRepositoryProvider.GetRepositories();

    // Disposable source cache.
    using var sourceCacheContext = new SourceCacheContext();

    // You should use an actual logger here, this is a NuGet ILogger instance.
    var logger = new NullLogger();

    // My extension configuration:
    var extensions = new[]
    {
        new ExtensionConfiguration
        {
            Package = "AutoStep.Web",
            PreRelease = true // Allow pre-release versions.
        }
    };

    // Replace this with a proper cancellation token.
    var cancellationToken = CancellationToken.None;

    // The framework we're using.
    var targetFramework = NuGetFramework.ParseFolder("netcoreapp3.1");
    var allPackages = new HashSet<SourcePackageDependencyInfo>();

    var dependencyContext = DependencyContext.Default;

    foreach (var ext in extensions)
    {
        var packageIdentity = await GetPackageIdentity(ext, sourceCacheContext, logger, repositories, cancellationToken);

        if (packageIdentity is null)
        {
            throw new InvalidOperationException($"Cannot find package {ext.Package}.");
        }

        await GetPackageDependencies(packageIdentity, sourceCacheContext, targetFramework, logger, repositories, dependencyContext, allPackages, cancellationToken);
    }

    var packagesToInstall = GetPackagesToInstall(sourceRepositoryProvider, logger, extensions, allPackages);

    // Where do we want to install our packages?
    // For now we'll pop them in the .extensions folder.
    var packageDirectory = Path.Combine(Environment.CurrentDirectory, ".extensions");
    var nugetSettings = Settings.LoadDefaultSettings(packageDirectory);

    await InstallPackages(sourceCacheContext, logger, packagesToInstall, packageDirectory, nugetSettings, cancellationToken);
}

With all these changes, here’s what the ./extensions folder looks like when we run this:

> ls ./extensions
AutoStep.Web.1.0.0-develop.20
Newtonsoft.Json.10.0.3
Selenium.Chrome.WebDriver.79.0.0
Selenium.WebDriver.3.141.0

All the packages we need are now on disk!


Wrapping Up

At the end of this post, we now have a mechanism for loading packages and a filtered set of dependencies from NuGet.

In the next post, we will load those packages into a custom AssemblyLoadContext and use them in our application.

You can find the complete set of code from this post in this gist.

The ‘production’ code this fed into is in the GitHub repository for AutoStep.Extensions, which provides the NuGet package used to load extensions into our VS Code Language Server and our commmand-line runner.

Categories
Uncategorized

Blazor WebAssembly, Monaco and Antlr – Building the AutoStep Editor as a Blazor App

I’m writing this post to show people the possibilities of WebAssembly and Blazor, using an open-source project I’m working on right now.

In this post we’ll cover:

  • Integrating the Monaco Code Editor with Blazor (and Razor Component Libraries in general)
  • Blazor to TypeScript Interop Tips
  • Manual Tokenisation of Code in Monaco (by a .NET Assembly!), including a quick look at performance.
  • Feeding Compilation Results from .NET to Monaco.

With the tools available to me, I can do real-time syntax highlighting and compilation of AutoStep tests in-browser, using WebAssembly to run my .NET library that does a lot of the heavy lifting, and the Monaco editor to provide the actual text editor behaviour. You can do some really cool stuff when you combine the power of .NET with a web-based user interface.

You can find all the code for the AutoStep Editor I’m going to be referencing in the GitHub repository, https://github.com/autostep/AutoStep.Editor.

Before we dive in, there’s a bit of background to cover.

Background

To give a little context, I’m currently building the AutoStep Toolkit.

AutoStep is a new compiler, linker and runner for BDD (Business Driven Development) tests, based on Gherkin syntax, but with some extra language features.

You can find the core library that provides this functionality at https://github.com/autostep/AutoStep.

I need to build a User Interface for writing AutoStep tests that is targeted at non-developer users, so using Visual Studio or VS Code as an editor doesn’t give the user experience I want.

I’ve chosen Blazor because:

  • I can load my netstandard AutoStep package directly into WebAssembly, so I don’t need a server component to run compilation.
  • I prefer to keep the amount of Javascript I have to write to a minimum.
  • I can share types between the front-end and the AutoStep project system.

Right now, I’m just building the basic editor control, before we build the rest of the user interface around it.

Below you can see a little demo GIF of how the editor control looks right now. You can see real-time syntax highlighting and test compilation as you type, with syntax errors being presented by the editor.

The rest of this post is basically going to go over how it works, and some of the WebAssembly magic that gives us this behaviour.

Integrating Monaco

Monaco is the VS Code editor, released as a standalone package that anyone can use; it’s really powerful, and gives us loads of basic text editor behaviour out of the box, even before we add the syntax highlighting and IDE-type functionality.

The first task was to get Monaco working as a Blazor component. I knew that I would need at least some Javascript code to function as the Interop layer, so rather than put that code in my main Blazor Client project (AutoStep.Editor.Client), I decided to put all the Monaco behaviour in a new Razor Component Library (AutoStep.Monaco), which I can use from my main project.

That way, I can keep the node_modules out of my main application project, nice and self-contained in it’s own folder.

I feel like it’s a pretty pleasing pattern to keep any JS interop out of the main Blazor app, in separated components. That way, the main application stays clean and only has to worry about components and general app state.

It also makes each component easier to test on its own.

I’m going to use TypeScript for my interop code, partly because I just like being in a typed world, but also because I can then consume the type definitions exposed by Monaco.

The only actual npm package I need to install and redistribute is monaco-editor, but I also need Webpack to compile my TypeScript and bundle everything together, plus the various Webpack plugins.

You can check the full package.json file for the set of required packages. There’s only 10 packages listed, but even these dependencies result in 5483 installed packages!

To configure Webpack correctly, I used the Monaco Webpack Plugin, which just simplifies getting Monaco building under Webpack. If you follow the instructions in their README, you can’t really go wrong.

Static Files in Razor Component Libraries

One nice feature of Blazor is that if you put your static files in the wwwroot folder of a Razor Component project, when you reference your Component project from your main Blazor App project, you can reference those static resources in your HTML, just by using the special _content path:

<!-- Use the name of the referenced project (or package) -->
<script src="_content/AutoStep.Monaco/app.bundle.js"></script>

For this to work with the Webpack build, I had to do two things:

  • Configure Webpack to output my bundles to the wwwroot folder
  • Configure Monaco to load its dependencies from the _content/AutoStep.Monaco path

The first part was straight-forward, you just have to change the Webpack output path:

//...
output: {
  globalObject: "self",
  filename: "[name].bundle.js",
  path: path.resolve(__dirname, 'wwwroot')
},
//...

For the Monaco configuration, the _content path has to be configured in three different locations. The first two are in the Webpack configuration file:

module: {
    rules: [
        // Other rules here...
        {
            test: /\.ttf$/,
            loader: 'file-loader',
            options:
            {
                publicPath: "/_content/AutoStep.Monaco"
            }
        }]
},
plugins: [
    new MonacoWebpackPlugin({publicPath: '/_content/AutoStep.Monaco/', languages: []})
]

I’ve also told the MonacoWebpackPlugin to not include any built-in languages in the output, because I’m not going to need them.

Finally, in the ‘entry point’ of your Javascript/Typescript (my MonacoInterop.ts), you need to tell Monaco where to load its web workers from:

// @ts-ignore
self.MonacoEnvironment = {
    getWorkerUrl: function (moduleId, label) {
        return "./_content/Autostep.Monaco/editor.worker.bundle.js";
    }
};

Once all the above is done, I can just include the app bundle in my Blazor Client index.html file, and it will load in all the Monaco dependencies:

<body>
    <app class="d-flex">Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_content/AutoStep.Monaco/app.bundle.js"></script>
    <script src="_content/Blazor.Fluxor/index.js"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

Blazor JS Interop & TypeScript

Once I’ve got the Monaco code loading in, I now need to use it. I’ll just go over a few tips for using TypeScript for doing Blazor JS Interop.

Interop Classes

The first tip is to define a sensible boundary between your .NET code and your TypeScript. First up, let’s define an entry-point TypeScript class attached to ‘window’:

class MyInterop 
{
    doSomething() 
    {
    }

    getSomething() : string
    {
    }
}

window['MyInterop'] = new MyInterop();

In your C# code, create an internal class of the same name, and encapsulate those methods (I’ve also defined wrappers for the IJSRuntime methods that automatically prefix the name of my TypeScript class):

internal class MyInterop
{
    private readonly IJSRuntime jsInterop;
    private readonly ILogger logger;

    private const string InteropPrefix = "MyInterop.";

    public MyInterop(IJSRuntime runtime, ILogger<MyInterop> logger)
    {
        this.jsInterop = jsInterop;
        this.logger = logger;
    }

    public async ValueTask DoSomething()
    {
        await InvokeVoidAsync("doSomething");
    }

    public async ValueTask<string> GetSomething()
    {
        return await InvokeAsync<string>("getSomething");
    }

    private ValueTask<TResult> InvokeAsync<TResult>(string methodName, params object[] args)
    {
        var fullname = InteropPrefix + methodName;
        logger.LogTrace("InvokeAsync: {0}", fullname);
        return jsRuntime.InvokeAsync<TResult>(fullname, args);
    }

    private ValueTask InvokeVoidAsync(string methodName, params object[] args)
    {
        var fullname = InteropPrefix + methodName;
        logger.LogTrace("InvokeVoidAsync: {0}", fullname);
        return jsRuntime.InvokeVoidAsync(fullname, args);
    }
}

Log your JS Interop calls! This will help a lot with debugging later.

In my AutoStep.Monaco library, I’ve got precisely this set-up (albeit with more methods), with the TypeScript in MonacoInterop.ts, and the C# in MonacoInterop.cs.

I added an extension method to my Razor Component Library that adds my MonacoInterop class to the Service Collection; I can call this during startup in my Blazor App.

public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Add services for the Monaco component.
    /// </summary>
    public static IServiceCollection AddMonaco(this IServiceCollection services)
    {
        services.AddSingleton<MonacoInterop>();
        return services;
    }
}

Then I can inject the MonacoInterop class into any of my Razor Components inside my AutoStep.Monaco project, and invoke my TypeScript methods that way.

Calling Back into .NET Code from TypeScript

When an ‘event’ of some form happens inside the Monaco Editor, I need to invoke a method in my .NET Code.

So far, I’ve found the following pattern to be pretty useful.

First up, add a method to your Interop class to register an event handler.

public async ValueTask RegisterLanguageTokenizer(string languageId, string extension, ILanguageTokenizer tokenizer)
{
    // Wrap the 'tokenizer' in a DotNetObjectReference.
    await InvokeVoidAsync("registerLanguageTokenizer", languageId, extension, DotNetObjectReference.Create(tokenizer));
}

The DotNetObjectReference passes the object to JS in a way that tracks the original object.

In the implementation of ILanguageTokenizer, I have a couple of methods, all marked as [JSInvokable], which indicates they can be called from Javascript.

In your TypeScript Interop class, add the registerLanguageTokenizer method:

registerLanguageTokenizer(languageId: string, extension: string, blazorCallback: IBlazorInteropObject)
{
  // Store the blazorCallback object somewhere to call it in an event handler.
}

The IBlazorInteropObject is something I’ve added; it’s a simple TypeScript interface that defines the useful methods available on the object wrapper Blazor actually passes as that parameter.

/**
 * Interface that defines the useful methods on the .NET object reference passed by Blazor.
 */
export interface IBlazorInteropObject {
    invokeMethodAsync<T>(methodName: string, ...args: any[]): Promise<T>;
    invokeMethod<T>(methodName: string, ...args: any[]): T;

}

I can then use this IBlazorInteropObject to invoke my .NET code.

export class AutoStepTokenProvider implements languages.TokensProvider {
    private callback: IBlazorInteropObject;

    constructor(blazorCallback: IBlazorInteropObject) {
        this.callback = blazorCallback;
    }

    getInitialState(): languages.IState {
        return new AutoStepTokenState(this.callback.invokeMethod<number>("GetInitialState"));
    }

    tokenize(line: string, state: languages.IState): languages.ILineTokens {

        if (state instanceof AutoStepTokenState)
        {
            var result: any = this.callback.invokeMethod("Tokenize", line, state.tokenState);

            return { tokens: result.tokens, endState: new AutoStepTokenState(result.endState) };
        }

        throw "Invalid start state";
    }
}

Line Tokenisation & Syntax Highlighting

For people unfamiliar with it, syntax highlighting code usually involves tokenising a given line of code, which uses a lexer to go through a block of text and produce a set of tokens that give the position of named language constructs, like keywords, variables, strings, etc. The editor then knows which colours to apply to different parts of a line of text.

Monaco allows you to define a ‘grammar’ for a language you want to apply syntax highlighting to., using their Monarch system for describing languages using JSON. Monaco then does the tokenising for you, based on that configuration.

The problem with using Monarch in my situation is that the tokenisation would not be context-sensitive. By that, I mean that the tokenisation can only work off the content of the file it is highlighting, and cannot base the set of returned tokens on anything else.

In my situation, I want to highlight the Given/When/Then lines of a test a different colour if there is no backing step to call; in addition, I only know which part of a step is an argument (in red) based on which step it binds against.

This contextual information cannot be obtained just through using a declarative grammar; I need a more manual approach.

Luckily, Monaco lets you define a manual token provider, using the setTokensProvider method. By implementing the Monaco-defined interface languages.TokensProvider, we can run our own custom code when Monaco needs to re-tokenise a line.

I showed you the TypeScript implementation of that interface earlier, when we were looking at how to call a .NET object from Javascript. All that the AutoStepTokenProvider TypeScript class does is call into an object in our Blazor .NET code, the AutoStepTokenizer, to handle the actual tokenisation.

The JS call for tokenisation must be a synchronous call because the Monaco tokenisation methods don’t allow me to return a promise (although it does execute in a background web worker).

Typically you’d want to make asynchronous calls into your .NET code where possible, but we can’t do that here.

To achieve the required tokenisation performance, I added Line Tokenisation support in the core AutoStep library, which is effectively a special-cased fast path through the normal compilation and linking process.

[JSInvokable]
public TokenizeResult Tokenize(string line, int state)
{
    try
    {
        var castState = (LineTokeniserState)state;

        logger.LogTrace("Tokenise Start in State {0}: {1}", castState, line);

        // Use the project compiler (in the core library) to tokenise.
        var tokenised = projectCompiler.TokeniseLine(line, castState);
        
        // Create the set of models that Monaco expects
        var tokenArray = tokenised.Tokens.Select(x => 
            new LanguageToken(x.StartPosition, TokenScopes.GetScopeText(x.Category, x.SubCategory)));

        return new TokenizeResult((int)tokenised.EndState, tokenArray);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Tokenisation Error");
    }

    return new TokenizeResult(0, Array.Empty<LanguageToken>());
}

Once the AutoStep Core library returns the set of tokens for a line, I need to convert those tokens into TextMate scopes. Scopes are effectively names for the different tokens you can get, and Monaco can style each scope differently.

I put the scope mapping configuration in a static array in a TokenScopes class:

static TokenScopes()
{
    // Set up our scopes.
    InitScope("comment.line.number-sign", LineTokenCategory.Comment);
    InitScope("keyword", LineTokenCategory.StepTypeKeyword);
    InitScope("keyword", LineTokenCategory.EntryMarker);
    InitScope("entity.name", LineTokenCategory.EntityName);
    InitScope("entity.name.section", LineTokenCategory.EntityName, LineTokenSubCategory.Scenario);
    InitScope("entity.name.section", LineTokenCategory.EntityName, LineTokenSubCategory.ScenarioOutline);
    InitScope("entity.name.type", LineTokenCategory.EntityName, LineTokenSubCategory.Feature);
    InitScope("entity.annotation", LineTokenCategory.Annotation);
    InitScope("entity.annotation.opt", LineTokenCategory.Annotation, LineTokenSubCategory.Option);
    InitScope("entity.annotation.tag", LineTokenCategory.Annotation, LineTokenSubCategory.Tag);
    InitScope("string", LineTokenCategory.BoundArgument);
    InitScope("string.variable", LineTokenCategory.BoundArgument, LineTokenSubCategory.ArgumentVariable);
    InitScope("variable", LineTokenCategory.Variable);
    InitScope("markup.italic", LineTokenCategory.Text, LineTokenSubCategory.Description);
    InitScope("text", LineTokenCategory.Text);
    InitScope("entity.step.text", LineTokenCategory.StepText);
    InitScope("entity.step.text.bound", LineTokenCategory.StepText, LineTokenSubCategory.Bound);
    InitScope("entity.step.text.unbound", LineTokenCategory.StepText, LineTokenSubCategory.Unbound);
    InitScope("table.separator", LineTokenCategory.TableBorder);
}

Finally, I define my own theme for Monaco so I can style the scopes:

editor.defineTheme('autostep', {
    base: 'vs',
    inherit: true,
    rules: [
        { token: "markup.italic", fontStyle: 'italic' },
        { token: "string.variable", fontStyle: 'italic' },
        { token: "variable", fontStyle: 'italic' },
        { token: "entity.step.text.unbound", foreground: '#969696' },
        { token: "entity.annotation.opt", foreground: '#fbad38' },
        { token: "entity.annotation.tag", foreground: '#fbad38' }
    ],
    colors: {} 
});

editor.setTheme('autostep');

Performance

It’s important to measure performance of code like this, especially because it needs to update the display in real-time as the user types.

If you run the profiler in Chrome DevTools, you can see the activity happening on the background thread that calls into the WebAssembly system, and get an idea of how long your code is spending in the .NET world.

I’ve highlighted which bits are doing what in the call stack, along with some timings.

It’s pretty quick! Even considering the hops into the WebAssembly space and back, tokenisation generally ranges between 3 and 6ms.

A lot of that performance, though, is down to the awesome parser engine we use in the AutoStep Core library, Antlr.

Antlr Overview

Antlr is a parser generator. It can take a grammar describing your language, and output a parser that will turn a block of text into a structured parse tree.

Considering the complexity of the task it has to perform, it produces really efficient parsers.

The Antlr generator is written in Java, but there are runtimes for the parser for a number of platforms, including .NET.

I’m not going to go into loads of depth on how Antlr works, because it is a really broad topic, but I can strongly recommend the excellent book by Terrence Parr, which is a great intro and reference for Antlr.

The full lexer grammar and parser grammar for the AutoStep language can be found in the AutoStep repo.

Line Tokenising Parser

The full parse tree for AutoStep works over the entire file, validating positions and order in a detailed way. That won’t work for tokenising a single line at a time (and risks being too slow), so I added a simpler line-by-line entry-point into the parser (AutoStepLineTokeniser) that helps me tokenise just for this syntax highlighting purpose:

You might ask, why do I even need a parser for this? Surely a lexer is all I need to generate the tokens?

I generate a parse tree for each line because:

  • I want the parser to tell me what ‘alternative’ of the possible line structures I’m looking at.
  • The set of tokens that I report for syntax highlighting are based on similar structures to the full compile, which expect at least a partial parse tree.

Once I have the Antlr parse tree for the single line I can built a set of line tokens with the appropriate categorisations for each token.

If the line is a Step Reference (Given/When/Then), I ask the AutoStep linker if the Step Reference can be bound to an existing step.

The high-level pseudo-code for this whole process looks a little like this:

var parseTree = GetAntlrParseTree(lineText);

if(parseTree is StepReference stepRef)
{
    if(linker.TryBindStep(stepRef))
    {
        return GetTokensForBoundStep(stepRef);
    }

    return GetTokensForUnboundStep(stepRef);
}

return GetRegularTokens(parseTree);

Once the tokens are handed back to the Blazor App, they get turned into scopes and handed off to Monaco for rendering.

Compilation, Linking, and Message Markers

Ok, so we’ve got line tokenisation, and syntax highlighting. Now I want to show underline markers when something is wrong.

Monaco makes this an absolute breeze, with a concept called ‘markers’, which are for precisely this purpose, but let’s take a look at how this is arranged. First, let’s look at the line in the Razor file that renders our custom MonacoEditor component:

<MonacoEditor Uri="@currentFile.FileUri.ToString()" 
              Value="@currentFile.Source.OriginalBody" 
              ModelMarkers="currentMarkers" 
              OnModelChanged="m => CodeChangedHandler(m.CurrentValue)" 
              LanguageId="autostep" />

When the content of the Monaco Editor changes, after a short delay (so we don’t recompile after every keystroke), our CodeChangedHandler will be invoked, with the new content of the editor as an argument.

When currentMarkers changes, the MonacoEditor component will pass those new markers down to the Monaco Javascript.

When the code for the file is changed, we ask the Project Compiler to compile and link the entire project. Only those files that have changed actually get compiled.

When that has completed, we have a set of Compilation & Linker Messages for the file, for example:

(8,17,8,25): Error ASC00011: Not expecting an Examples block here; did you mean to define 'My Scenario' as a Scenario Outline rather than a Scenario?
(3,1): Error ASC20002: There are multiple matching step definitions that match this step.

To use those in Monaco, we just need to convert them into MarkerData structures, i.e. the format Monaco understands. I’ve defined a MarkerData class in C# that serialises directly to the equivalent Javascript structure.

private static MarkerData GetMarkerDataFromMessage(CompilerMessage msg)
{
    var severity = msg.Level switch
    {
        CompilerMessageLevel.Error => MarkerSeverity.Error,
        CompilerMessageLevel.Warning => MarkerSeverity.Warning,
        _ => MarkerSeverity.Info
    };

    var endPosition = msg.EndColumn;

    if(endPosition is null)
    {
        endPosition = msg.StartColumn;
    }
    else
    {
        // Expand message end to the location after the token
        endPosition++;
    }

    return new MarkerData($"ASC{(int)msg.Code:D5}", msg.Message, severity, msg.StartColumn, msg.StartLineNo, endPosition.Value, msg.EndLineNo ?? msg.StartLineNo);
}

Once I have the correct data structures, I can just pass those over to my TypeScript class using regular JS Interop, and call editor.setModelMarkers to update the set.

/**
    * Set the model markers for a text model.
    * @param textModelUri The URI of the text model.
    * @param owner The owner of the markers.
    * @param markers The full set of new markers for the model.
    */
setModelMarkers(textModelUri: string, owner: string, markers: editor.IMarkerData[])
{
    var modelCtxt = this.models[textModelUri];

    if (!modelCtxt) {
        throw "Specified model not created.";
    }

    editor.setModelMarkers(modelCtxt.textModel, owner, markers);
}

Hey, presto! Compilation errors, syntax highlighting, all in the browser with no server work beyond static file hosting!

What’s Next

Features going into the AutoStep Editor over the next few months include:

  • An actual User Interface, rather than just an Editor!
  • Intellisense, and automatic step suggestions as you type.
  • Hover documentation, showing step documentation if you hover over one.
  • Go-To-Reference for steps, that navigates to the Step Definition for a step if you defined a step in an AutoStep file.

Keep an eye on the repository if you want to see how it goes, there may well be another couple of follow-up posts as we make progress.

Categories
Uncategorized

.NET Asynchronous Disposal – Tips for Implementing IAsyncDisposable on your own Types

Background – Why Async Dispose?

The .NET Team recently added support for asynchronous disposal of objects, via a new IAsyncDisposable interface.

A lot of the examples you can find will use this to let you dispose of asynchronous streams, but it’s also useful for disposal of other objects that may potentially trigger I/O.

This allows you to write code like this (which works in .NET Core 3.0, with C# 8):

Notice the await in front of the using statement? That will tell the using block to call DisposeAsync on the SqlConnection object when we exit the using block, instead of the regular Dispose method, and await on the result.

What’s the benefit of this over a typical using? Well, if the database connection needs to go over the network to reset the connection, it will return the thread to the thread pool to do other work, rather than blocking it while the Dispose takes place.

The great thing about .NET Core 3.0 is that for any services you have registered as Scoped (i.e. they only live for the duration of the current HTTP request) that implement IAsyncDisposable will be disposed of asynchronously at the end of the request, giving valuable thread time back to processing actual requests.

I recently added the functionality in the Autofac Dependency Injection library to support calling DisposeAsync on a lifetime scope, which in turn calls DisposeAsync on all services in that scope that implement IAsyncDisposable. This extends to if you use Autofac as your .NET Core Service Provider as well. The functionality will be released in Autofac 5.0.0, or you can check out the relevant GitHub PR now to see what changes went in.

I thought I’d use this blog post to help people write their own classes that implement IAsyncDisposable, since I couldn’t find a lot of documentation on it, and had to go digging into .NET Core code on GitHub to figure out the best approach.

Implement IDisposable As Well

IAsyncDisposable isn’t a replacement for IDisposable, it’s an additional way to dispose.

Basically, if you implement IAsyncDisposable in your class, you should probably implement IDisposable too and perform the synchronous equivalent of your disposal operation.

This goes double for library authors, who are not in control of the code that creates the object that needs to be disposed.

There’s a couple of reasons for this:

  1. If you don’t have a regular Dispose method, code that doesn’t run in an async context will have to block on your DisposeAsync to make it sync, which kind of defies the point, and is unpleasant:
  1. If your class gets put in a container, and then the container is disposed synchronously, an exception will be thrown (this is the behaviour of Autofac and the default .NET Core DI system), because these containers will refuse to call DisposeAsync from inside a regular Dispose:
The exception we get if we don’t dispose asynchronously.

Only Add IAsyncDisposable If You Need To

This one is pretty simple; you should only add IAsyncDisposable to your class if you or a derived class may allocate resources that also implement IAsyncDisposable.

Don’t do this:

SemaphoreSlim doesn’t implement IAsyncDisposable, so all this does is use up another thread pool thread to run the Dispose.

Derived Classes

If you are writing a base class that might have derived classes with resources that need disposing asynchronously, you may wish to introduce a virtual DisposeAsync method if you also have a base Dispose method.

In this case, I would suggest making your default implementation call Dispose directly without awaiting and return synchronously:

Base classes can override the DisposeAsync method if they have resources that can be disposed of asynchronously, otherwise they can just override Dispose.

Only Dispose Once (Sync or Async)

It’s recommended practice to make Dispose re-entrant, and only dispose of its resources once. With the asynchronous disposal behaviour, this is still true, and importantly, you should only allow either Dispose or DisposeAsync to actually do the dispose.

So your classes should have this pattern:

The reason I set isDisposed to true in the above example before awaiting is because setting it afterwards would make it possible for a caller to double-dispose, by not awaiting on DisposeAsync, then calling Dispose. It’s unlikely, but possible.

If the class may be used in a multi-threaded context, consider using Interlocked methods to set isDisposed, to make sure two threads don’t try disposing at the same time.

Targeting netstandard2.0

This bit is mostly targeted at library developers, who might be targeting netstandard versions.

While the language implementations for asynchronous disposal are only available in netstandard2.1, there is a package from Microsoft that provides the IAsyncDisposable interface and related types for netstandard2.0 and .NET 4.6.1, Microsoft.Bcl.AsyncInterfaces (as David Fowler kindly pointed out to me in the Autofac PR).

This allows you to add conditional references that mean your library can implement asynchronous disposal in versions prior to .NET Standard 2.1, by adding the following conditional package reference:

Consider Adding GC.SuppressFinalize to DisposeAsync

If your class has a finalizer (or a derived class may have one), then you may already be calling GC.SuppressFinalize(this) in your Dispose method.

Because your DisposeAsync method is another Dispose method, it should also call GC.SuppressFinalize so the GC doesn’t have to call your destructor later.

This is a more complete example that provides protected virtual methods for disposal, in line with the recommended IDisposable pattern:

Putting GC.SuppressFinalize in the DisposeAsync method will actually cause a violation of the CA1816 analyzer rule if you have the analyzers installed, that says GC.SuppressFinalize should only be called from Dispose. I’m hoping that the rule will get updated at some point, but for now you may need to suppress that rule for the DisposeAsync method.

Wrap-Up

So, IAsyncDisposable can be really handy if you have resources to dispose of that may use I/O in that disposal, but be careful using it, and only add it if you actually need to!

Categories
Uncategorized

ASP.NET Core 3.0 – Logging in the Startup Class (with NLog)

With ASP.NET Core 3.0, the ability to inject an ILogger or ILoggerFactory into the Startup class has been removed, so we can no longer do this:

I understand why this has been done, because creating a temporary DI container just for the startup process adds a lot of complexity and potential for errors.

However, my ConfigureServices method (and the new ConfigureContainer method added in 3.0) does quite a bit of work, including loading extension assemblies; I want to be able to log during that time.

I also want to make sure I only pass around the ILogger from the Microsoft.Extensions.Logging namespace to other objects used at startup.

The Workaround

I use NLog for my logging (https://nlog-project.org/). In my Program’s Main method I configure it like so:

You can grab details on how to create an nlog.config file from the NLog docs, I won’t go into it here.

Then, in my Startup class, I can create the NLogLoggerProvider class (this is sort of a factory for creating the Microsoft ILogger instances), and from that I can get my logger instance:

Hey presto, logging in the Startup class. Note that we are obviously outside the entire DI system for this logging (which is sort of the point).

Categories
arduino Uncategorized

Modding my Rowing Machine with an Arduino – Part 1 – Arduino Basics

I’ve got a rowing machine in my garage that I use pretty regularly, and it displays some statistics on a basic little read-out, including speed, calories burnt, number of ‘strokes’ and so on.

The rowing machine read-out.

My mission is to replace the simple LED read-out with my own board that will capture the sensor data it uses, upload it somewhere and then do some fun stuff with that data.

For background, I have a little past experience in embedded programming, but I haven’t used it in years. I understand the basics of GPIO pins and similar, but I place myself firmly in the ‘beginner’ category with embedded development.

I hadn’t touched an Arduino before today, when I started writing this post, so this is going to cover just getting to grips with Arduino basics, and subsequent updates will go through all the steps needed to mod my rowing machine!

Picking an Arduino Board

My only real criteria for picking an Arduino board is that I can connect the sensors on my rowing machine, and also that I can connect to WiFi to get data off it.

The back of the rowing machine display.

From the connections on my rowing machine, I can tell I need two connections on the board that can read analog sensor values, so I knew I would need two ADCs (Analog to Digital Converters) on my board, one for the speed and one for the counter.

Going through the Arduino website to figure out which board I wanted (turns out there’s a lot of choice), I picked the Arduino Uno Wifi Rev2, which:

  • Has 2 ADCs (for reading the sensors on my rowing machine).
  • A built-in WiFi module for connectivity (so I can upload my data).
  • A pretty attractive price of about £35 (as of writing in May 2019) plus shipping.

Shipping only took a couple of days, and once it arrived I was ready to go. You will also need a USB Type B cable. You can pick one up off Amazon for about £5, but I had an old one lying around.

Setup

After plugging in the Arduino board with the USB connector, I went to the getting started guide for my particular board (the site has different pages for each one).

I believe that a lot of the following content should work for most Arduino boards that don’t have WiFi, but I am not certain.

From there I tried out both the web-based and desktop IDE Arduino provide, but I decided I want to work in a familiar environment. Luckily, it turns out that trusty VS Code can come to my rescue, because it has an extension for working with Arduino, with the added bonus of having decent intellisense.

You will need to download the regular Arduino desktop IDE from here before you can use VS Code with your Arduino; the extension needs to use the tools it supplies.

Go ahead and install the VS Code Arduino extension (by Microsoft), then go and configure the extension if you need to set the installation path for your Arduino installation to the non-default setting.

What is an Arduino Program?

What actually is an Arduino Program anyway, and how is it different from a console application on my desktop?

Basically, all Arduino ‘programs’ run C++ code you write. Each program fundamentally consists of a setup and a loop.

You can sort of think of setup as your ‘main’ method, but you exit it immediately and start looping.

One thing that you need to remember with an embedded program is that your program never stops; it runs until the device doesn’t have power, you reset the board, or you upload a new program. There is no ‘exit’.

In an Arduino program you can do a lot of the things you’d expect from a C++ program, like having additional C++ code files (files with a .cpp extension work just fine). You do have a lot less memory to play with though; the processor on my Arduino only has 6KB of RAM. As someone who tends to work on web applications that consume hundreds of MB, it’s a bit jarring, but 6KB is actually plenty for my needs.

When you want to run your program, you compile it as you would with a normal program; the Arduino components on your desktop then upload the program to the device (over the USB connection) and store it in Flash memory. Again, you can’t have massive applications; my board has 48KB of Flash to fit the program in.

My First Arduino Program

First off, I’m going to make an LED flash. Big stuff, I know. My Arduino (and most of them I think), have a built-in LED that you can turn on and off from your program.

Let’s make a new project in VS Code. Create a new folder somewhere and open it in VS Code.

Then, to start a new ‘project’, run Arduino: Initialize from the command palette. This created a blank app.ino file and let me select my board, which got me started.

I found that I got an intellisense error in the created ino file:

Clicking on the ‘fix-it’ bulb took me to the C++ include path settings:

After a quick search of the Arduino install directory for ‘pgmspace.h’, turned out I was missing an additional include path:

C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\**

After I added that to the list with the necessary extra backslashes, closed and then re-opened my app.ino file, no more squiggles, and I get nice intellisense.

Controlling an LED

My built-in LED is on PIN #13 of the processor (I used the Arduino Board reference to check what was available); in your program there is an LED_BUILTIN constant already supplied that references it.


A bit of embedded basics here; everything you do in embedded programming is basically turning pins on and off on your processor.

The big arrow is pointing to a pin

Want to turn on an LED? Set the ‘level’ of the pin connected to the LED to HIGH (which means it has voltage on it). To turn it off, set the level to LOW (which means it has no/negligible voltage on it).


I had to configure the LED pin to be an output pin in my setup method, and then set it HIGH and LOW in my loop. Put some delays in there and you have a flickering LED!

You can upload the program from the command palette (or CTRL+ALT+U).

GIF of an LED blinking (very exciting!)

You might encounter one or more problems getting that program downloaded (like I did):

COM Port

I had problems getting the COM Port (the Arduino connects as a virtual COM Port over USB) to work first time, so my code wouldn’t upload. I had to go into the main Arduino IDE and install the package it recommended for my board. This installed different drivers, which changed the COM Port to Atmel rather than Microsoft.

I also had to go into Windows Device Manager, go into the settings, and change the COM Port to not be the default COM3. Only then would it actually upload.

Select the Right Board!

Pro Tip – make sure you pick the right board in the VSCode footer, and select the processor.

Mine is the Arduino Uno WiFI Rev2, with the A4809 processor.

Hello World

Next step, I want to do the tried and tested programming exercise of writing Hello World to the ‘Console’.

The Arduino doesn’t have a console to write to in the normal sense you might be familiar with; what it can do however is write to the Serial connection you use to upload your programs, and the Arduino extension in VS Code can display it.


More embedded basics; Remember how I said that everything in embedded programming is turning pins on (HIGH) and off (LOW)? Well, that’s true of writing text to a Serial connection too, but we do it extremely quickly.

Luckily, we don’t have to do all the pin changes ourselves (phew); there are functions already supplied by the Arduino libraries that will do this for us.

When we configure our Serial port, we need to set its ‘baud’, or speed. This tells the Serial code how fast we want to send characters. 9600 baud means that we are sending approximately 9600 bytes/characters per second. Whatever is ‘listening’ on the other end needs to know the speed, otherwise you’ll just get junk out.


So once we’ve setup our Serial Port, in our loop, once per second we print our Hello World to the Serial port.

Once you’ve copied that code into your app.ino, upload the program as you would normally.

Once you’ve uploaded your program, it’s initially a bit anti-climatic, nothing is obviously happening. To see the output, we need to open the Serial Monitor. Click on the little connection logo in the VS Code footer:

This will bring up the Serial Monitor. At this point, you will probably just be getting nonsense, so you will want to change the ‘baud’ for the Serial Monitor to 9600.

Once you’ve done that, you should get your Hello World being displayed!

Next Steps

Ok, so that’s it for now. Subsequent posts will cover:

  • Wiring up my sensors and reading analogue values.
  • Using the WiFi module to sent HTTP requests
  • Using the data I collect!
Categories
Uncategorized

Implementing a shared resource lock in your load-balanced application with MongoDB in C#

In your load-balanced cluster of nice, performant application servers, you may occasionally find that there is an outside resource that requires access be synchronised between each server in the cluster (ideally not too often, because it’s a pain, but there you go).

We encountered this recently when dealing with multiple clients trying to access and update a reporting component we interface with via our application. Each client issued HTTP API requests to the application that would:

  1. Read a resource from the reporting system (by the ID)
  2. Do something
  3. Update the resource

The problem is that no two tasks should be allowed to get past step 1 at the same time for a given named resource. This is starting to look pretty familiar, right?

We see this in a single-server multi-threaded model sometimes (although not too often hopefully; I find synchronous locks are generally bad for performance if used too liberally in web requests).

The problem with the above code is that it only locks a resource in the current process; a different HTTP request, routed to a different server by the load balancer, would happily acquire it’s own lock.

Establishing a distributed lock

What we need now is to lock the resource across our entire cluster, not just on the one server.

We use MongoDB for general shared state between servers in our cluster, so I looked into how to use MongoDB to also synchronise access to our resource between the application servers.

Luckily, it turns out that by using existing MongoDB functionality, this is pretty straightforward to create a short-lived resource lock.

Note
I’ve written this solution in C#, using the official MongoDB C# client, but there’s no reason this wouldn’t apply to a different MongoDB client implementation in any language.

Want to jump to the end?
https://github.com/alistairjevans/mongodb-locks

Create the Collection

First up, I want to create a new MongoDB collection to hold my locks. I’ll create a model and then get an instance of that collection.

That’s pretty basic stuff, you’d have to do that generally to access any MongoDB collection.

Next we’re going to add the function used to acquire a lock, AcquireLock. This method is responsible for the ‘spin’ or retry on the lock, waiting to acquire it.

The AcquireLock method:

  1. Creates a ‘lock id’ from the resource id.
  2. Creates a ‘DistributedLock’ object, which is where the locking mechanism happens (more on this in a moment).
  3. Attempts to get the lock in a while loop.
  4. Waits up to a 10 second timeout to acquire the lock, attempting again every 100ms.
  5. Returns the DistributedLock once the lock is acquired (but only as an IDisposable).

Next let’s look at what is going on inside the DistributedLock class.

DistributedLock

The DistributedLock class is responsible for the actual MongoDB commands, and attempting the lock.

Let’s break down what happens here. The AttemptGetLock method issues a FindOneAndUpdate MongoDB command that does the following:

  1. Looks for a record with an ID the same as the provided lock ID.
  2. If it finds it, it returns it without doing anything (because our update is only a SetOnInsert, not a Set).
  3. If it doesn’t find it, it creates a new document (because IsUpsert is true), with the expected ID.

We’ve set the ReturnDocument option to ‘Before’, because that means the result of the FindOneAndUpdateAsync is null if there was no existing lock document. If there was no existing document, there will be one now, and we have acquired the lock!

When you dispose of the lock, we just delete the lock document from the collection, and hey presto, the lock has been released, and the next thread to try to get the lock will do so.

Using It

Because the AcquireLock method returns an IDisposable (via a Task), you can just use a ‘using’ statement in a similar manner to how you would use the ‘lock’ statement.

Do you see that await inside the using definition? Right, do not forget to use it. If you do forget, all hell will break loose, because Task<IDisposable> also implements IDisposable! So you’ll end up instantly entering the using block, and then disposing of the task afterwards at some point while the lock attempts are happening. This causes many bad things.

Make sense? Good. Unfortunately, we’re not quite done…

Handing Concurrent Lock Attempts

So, while the above version of DistributedLock works pretty well most of the time, at some point it will inevitably throw an exception when handling multiple concurrent lock attempts:

Duplicate keys…

Why does this happen? Well, the upsert functionality in findAndModify is not technically atomic in the way you might expect; the find and the insert are different operations internally to MongoDB, so two lock attempts might both attempt to insert a record.

When that happens, the default ID index on the MongoDB collection will throw an E11000 duplicate key error.

This is actually OK; one of the threads that attempted to acquire a lock will get it (the first one to complete the insert), and the second one will get an exception, so we just need to amend our code to say that the thread with the exception failed to get the lock.

Handling a Crash

The last problem we have to solve is what happens if one of the application servers crashes part-way through a piece of work?

If a thread has a lock, but the server crashes or is otherwise disconnected from MongoDB, it can’t release the resource, meaning no-one else will ever be able to take a lock on the resource.

We need to put in some safeguard against this that allows the lock to eventually be released even if the application isn’t able to do it correctly.

To do this, we can use one of my favourite MongoDB features, TTL Indexes, which allows MongoDB to ‘age out’ records automatically, based on a column that contains an ‘expiry’ time.

Let’s update the original LockModel with an expiry property, and add a TTL index to our collection.

In the above index creation instruction, I’m specifying an ExpireAfter of TimeSpan.Zero, meaning that as soon as the DateTime specified in ExpireAt of the lock document passes, the document will be deleted.

Finally, we’ll update the MongoDB FindOneAndUpdate instruction in DistributedLock to set the ExpireAt property to the current time plus 1 minute.

Now, if a lock isn’t released after 1 minute, it will automatically be cleaned up by MongoDB, and another thread will be able to acquire the lock.

Notes on TTL Indexes and Timeouts

  • The TTL index is not a fast way of releasing these locks; the accuracy on it is low, because by default the MongoDB thread that checks for expired documents only runs once every 60 seconds.
    We’re using it as a safety net for an edge case, rather than a predictable mechanism. If you need a faster recovery than that 60 second window, then you may need to look for an alternative solution.
  • You may notice that I’m using DateTime.UtcNow for the ExpireAt value, rather than DateTime.Now; this is because I have had a variety of problems storing C# DateTimes with a timezone in MongoDB in a reliable way, so I tend to prefer storing UTC values whenever possible (especially when it’s not a user-entered value).

Sample Project

I’ve created a github repo with an ASP.NET Core project at https://github.com/alistairjevans/mongodb-locks that has a complete implementation of the above, with an example API controller that outputs timing information for the lock acquisition.