Categories
Uncategorized

New Features in Autofac 7.0

Autofac 7.0 has just been released, and I wanted to take a moment to call out two of the cool new features in this release.

Required Properties

.NET 7 introduced the idea of “required” properties. These are properties that must be set when constructing an object; this is enforced by the compiler:

public class MyComponent
{
    public required string RequiredProperty { get; set; }
}

// Compiler error: CS9035
// Required member 'MyComponent.RequiredProperty' must be set
// in the object initializer or attribute constructor.	
var newInstance = new MyComponent();

To resolve this, you’ll have to provide RequiredProperty:

public class MyComponent
{
    public required string RequiredProperty { get; set; }
}

var newInstance = new MyComponent
{
    RequiredProperty = "foo"
};

How does this affect Autofac? Specifically, how does it affect components registered using reflection in 6.x?

public class MyComponent
{
    public required ILogger Logger { get; set; }
}

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();

var container = builder.Build();

// Logger will be null in the result.
var component = container.Resolve<MyComponent>();

That feels wrong, right? If the compiler says that, in order to create an instance of MyComponent, you must populate Logger, then Autofac probably shouldn’t be able to circumvent that.

The above code runs because required properties are only a compiler check; there is no requirement to populate those properties when creating an instance of a class via Activator.CreateInstance().

In Autofac 7.0, we now automatically inject all required properties for a component registered by type when we create an instance of said component. These properties are required, so if we can’t resolve them, we throw an error:

public class MyComponent
{
    public required ILogger Logger { get; set; }
}

var builder = new ContainerBuilder();
builder.RegisterType<MyComponent>();

var container = builder.Build();

// Throws an exception, ILogger has not been registered.
var component = container.Resolve<MyComponent>();

You do not need to call PropertiesAutowired() on a registration to make this behaviour kick in, it applies automatically to all reflection-based registrations, including assembly-scanned types.

There are a few additional considerations around required properties, particularly around mixing in constructors, and you can find those in our property injection documentation.

Unloadable Plugins and Autofac

.NET Core introduced the AssemblyLoadContext class, which allows you to load in a set of assemblies that are somewhat isolated from the set of assemblies already loaded. These loaded assemblies can reference each other, and even contain different versions of assemblies already loaded into the “default” load context. You also have the added bonus of potentially being able to unload those assemblies, and reclaim that memory, when they are no longer needed.

If you’re not familiar with what I’m talking about, the dotnet docs on AsssemblyLoadContext are a pretty handy intro.

Now, up until this point, Autofac has been pretty intent on maintaining internal caches of types it has seen, regardless of when it saw them, or where those types were loaded from.

This meant that, if you registered types from a loaded assembly into an Autofac lifetime scope, you would no longer be able to unload that assembly.

In Autofac 6.5 we made some progress towards being able to “forget” about those types by switching from a static reflection cache to an instance cache that can be readily cleared, but there was still more work needed.

In Autofac 7.0, you can now begin a lifetime scope specifically for the purpose of ensuring that a load context can be unloaded even after using it with Autofac.

This is enabled by a new BeginLoadContextLifetimeScope() method, which creates a sort of “load context aware” lifetime scope; we ensure that when the scope is disposed, any internal Autofac references to type or assembly metadata relating to the assemblies loaded from the custom load context is cleaned away, allowing it to be unloaded.

Once you have that lifetime scope, it works like any other scope you’ve used before; IEnumerable<TService> can combine elements from the new scope and any parent scopes, new scopes can be created from it, and so on.

Here’s an example of how you can use it, but you can find more information in our documentation:

//
// In PluginDefinition project.
//

public interface IPlugin
{
  void DoSomething();
}

//
// In MyPlugin project.
//

public class MyPlugin : IPlugin
{
    public void DoSomething()
    {
      Console.WriteLine("Hello World");
    }
}

//
// In the application.
//

var builder = new ContainerBuilder();

// Components defined in the "Default" load context will be available in load context lifetime scopes.
builder.RegisterType<DefaultComponent>();

var container = builder.Build();

var loadContext = new AssemblyLoadContext("PluginContext", isCollectible: true);

using (var scope = container.BeginLoadContextLifetimeScope(loadContext, builder =>
{
  var pluginAssembly = loadContext.LoadFromAssemblyPath("plugins/MyPlugin.dll");

  builder.RegisterAssemblyTypes(pluginAssembly).AsImplementedInterfaces();
}))
{
  // The application should reference the PluginDefinition project, which means the
  // default load context will have loaded the IPlugin interface already. When the
  // MyPlugin assembly gets loaded it should share the same type and allow resolution
  // with the common interface.
  var plugin = scope.Resolve<IPlugin>();

  plugin.DoSomething();
}

loadContext.Unload();

Breaking Changes

The introduction of these new features produced a couple of relatively minor breaking changes; you can find the full list of these in the documentation on upgrading from 6.x to 7.x.

We also dropped direct support for .NET 5 (it is now generally out of support by the dotnet team).

Wrapping Up

As always, if you find a problem with this latest release, let us know by raising an issue; we’ll gradually update our various integrations to use the new version of Autofac, but we’d greatly welcome contributions from anyone willing to help us update the ones they’re familiar with.

As with a lot of OSS projects, it’s just a couple of us working on Autofac in our spare time, and we’re always very keen to encourage contribution!

Categories
Uncategorized

Bash: Getting the systemd version and comparing it

This is just a quick post, but I wanted to spare both future me, and anyone else, from struggling with the precise syntax to extract the systemd version in a bash script and applying some work-around on older versions.

I recently had to work around a bug in systemd (specifically https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=927911), and needed to modify a service file if the systemd version was less than 241.

To do this, you can extract the version using systemctl --version, pipe it to a fun sed pattern, then use it:

#!/bin/bash

systemdver=$(systemctl --version | sed -nE "s/systemd ([0-9]+).*/\1/p")

# If the systemd version is prior to 241, do something
if [[ $systemdver -lt 241 ]];
then
   echo 'do whatever'
fi

Replace 241 with which ever version you need to compare against.

That’s it, hope this helps someone.

Categories
javascript Uncategorized web

Clipping image edges at runtime with CSS clip-path

This is going to be a rare front-end post from me, but I thought I’d share a handy technique for making a web browser treat the non-transparent edge of an image as the actual edge, rather than using the bounding box as it does by default.

I spent some time this year building a collage editing tool for an artist friend of mine, who runs workshops where students create collages out of existing images; I created a digital app to help out in the time of COVID, which you can find at https://collageit.co.uk (it’s actually pretty fun to play with).

The thing is that, when editing a collage, images overlap with each other a lot, and users interact with images in a more physical manner than just with ‘layers’, as you might find in a lot of image editors; that’s the nature of collage art. So, why does the default browser behaviour make this so difficult to implement?

Let’s say I have this image below. The image has a transparent background. From a visual perspective, the edge of this image is the edge of the circle, right?

Just a red button.

But, as you may know, the browser (and mostly every other image rendering system) will treat the edge of the image as the bounding box.

The actual image box.

This means that any mouse-over events or other interactions will all be with the image’s bounding box edge. If I want things to happen only when a user clicks on the actual image, things get tricky.

CSS clip-path

I’m going to start off just showing the working solution, which I’ve set up in codepen. Try it out with some of your own images if you like.

Due to canvas security restrictions, any images you want to load into the codepen need an Access-Control-Allow-Origin header in order to work. Imgur is a pretty good source that works.

So, we can display an image, and have the browser interact with the ‘actual’ edge of the image, rather than the bounding box.

How does this work?

The actual clipping behaviour is done with a CSS clip-path property. This property allows you to define a custom polygon that clips both the actual displayed portion of an image, as well as the area where mouse interactions are detected.

/* A simple clip example */
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);

However, we can actually exploit this property to provide explicit x + y positions for the clip:

clip-path: polygon(55px 49px,55px 50px,54px 50px,54px 51px,53px 51px,53px 52px,52px 52px, ....);

There isn’t really a limit to the length of the clip-path, so we could actually define the entire edge of the image using this mechanism if we wanted.

This is how I’m clipping the image in my approach.

Marching Squares

You may have realised (especially if you load your own images in the codepen) that I haven’t actually pre-computed this path and loaded it in, or embedded it in my HTML ahead of time. This path is computed at runtime when we load the image!

We do this using something called the Marching Squares algorithm. Basically, it’s an algorithm that detects a contour (or edge) of something. In this case, I use it to detect the path around the image where the alpha component of the image transitions to opaque.

I’m using an implementation of Marching Squares by Mike Bostock, from the d3 graphics library. The license is included in the codepen source; if you use that code, please maintain the license!

So, what are the steps here?

  1. Load the image from whatever source.
let loadPromise = new Promise((resolve, reject) => {        
        const imageElement = new Image();
        imageElement.crossOrigin = "anonymous";
        imageElement.onload = ev => {
            resolve(imageElement);
        };
        imageElement.onerror = ev => {
            reject(`Could not load image from ${imageUrl}.`);
        }
        imageElement.src = imageUrl;
});

// Wait for the load to finish.
const loadedImage = await loadPromise;

  1. Resize an in-memory canvas and render the image into it.
// I've defined 'workingCanvas' elsewhere.
const canvasWidth = workingCanvas.width = loadedImage.width;
const canvasHeight = workingCanvas.height = loadedImage.height;

const drawContext = workingCanvas.getContext("2d");

// Wipe the canvas.
drawContext.clearRect(0, 0, workingCanvas.width, workingCanvas.height);

// Draw the image.
drawContext.drawImage(loadedImage, 0 ,0);

  1. Get the raw pixel data for the image so we can analyse it.
// Get the raw pixel data that we can analyse.
const pixelData = drawContext.getImageData(0, 0, workingCanvas.width, workingCanvas.height)
                             .data;
  1. Define a function that, for any given x,y coordinate, can assess whether the point is part of the image.
// This is used by the marching squares algorithm
// to determine the outline of the non-transparent
// pixels on the image.    
const defineNonTransparent = function (x, y)
{
    // Get the alpha value for a pixel.
    var a=pixelData[(y*canvasWidth+x)*4+3];
    
    // This determines the alpha tolerance; I'm extremely intolerant of transparency;
    // any transparency is basically counted as the edge of the image.
    return(a>99);
}
  1. Finally, get the edge points of the image.
// Get the edges.
var points = contour(defineNonTransparent);

From the edge points we can trivially construct a clip path to use in the css polygon construct.

function getClipPath(imgData) 
{
    let clipPathSet = [];

    imgData.boundingPoints.forEach(p => clipPathSet.push(`${p.x}px ${p.y}px`));

    return clipPathSet.join(',');
};

Limitations

There are a couple of limitations to this approach (which I can largely accept for my purposes):

  1. The algorithm can only detect one edge in an image; so if your image is made up of multiple disconnected parts, only one of them is going to be picked up and clipped.
  2. I can’t detect internal transparency with this algorithm right now, so if you have a blank space inside the image, that’ll be treated as part of the image rather than something you can click through.
  3. This isn’t going to work in old browsers (IE is out, basically).

It’s quite likely that there are better ways to do this, and I’d be interested to learn of better solutions if you have them!