autofac, dotnet comments edit

Yesterday I released Autofac 9.1.0. It’s listed as a minor release - 0.X.0 semver increment - because it’s all new, backwards-compatible features, but it’s actually pretty big, I think.

AnyKey Support

Autofac now natively supports the concept of AnyKey. It behaves the same way AnyKey works in Microsoft.Extensions.DependencyInjection, but it is native to Autofac directly. The unit tests here show some very detailed examples of usage, but on a high level:

var builder = new ContainerBuilder();
builder.RegisterType<Service>().Keyed<IService>(KeyedService.AnyKey);

var container = builder.Build();

// Registering as AnyKey allows it to respond to... any key!
var service = container.ResolveKeyed<IService>("service1");

As noted, it behaves like AnyKey in Microsoft.Extensions.DependencyInjection, so there are some “rules” around when AnyKey will work and when it won’t.

  • You can resolve a single instance and AnyKey will be a fallback. If you resolve a single instance of a service and use a key that you haven’t otherwise registered, an AnyKey service can provide that instance.
  • You can resolve a collection using AnyKey. This allows you to get all keyed services of a certain type, but it will not include the services registered with AnyKey - only services registered with a specific key.
  • AnyKey works with singletons. When you register an AnyKey service as a singleton, a different instance of that service will be cached for each key under which it’s resolved. Beware of this - if you’re not paying attention, it could lead to a memory leak.
  • Registration order is important. As always, last-in-wins. If you register a lot of AnyKey services, the last one registered for the given type will be the one you get when you resolve.

Here are some examples showing usage:

var builder = new ContainerBuilder();
builder.RegisterType<Service1>().Keyed<IService>(KeyedService.AnyKey);
builder.RegisterType<Service2>().Keyed<IService>("a");
builder.RegisterType<Service3>().Keyed<IService>("b");
var container = builder.Build();

// This will ONLY get Service2 and Service3 - things registered with explicit
// keys. It will NOT return Service1, registered with AnyKey.
var allServices = container.ResolveKeyed<IEnumerable<IService>>(KeyedService.AnyKey)

// THIS WILL THROW: You can't resolve a single instance using AnyKey.
_ = container.ResolveKeyed<IService>(KeyedService.AnyKey);

// This will get you the AnyKey service because no specific keyed service was
// registered with `other-key`.
var noRegisteredKey = container.ResolveKeyed<IService>("other-key");

Again, check out the unit tests for some robust examples.

Inject Service Key Into Constructors

The new [ServiceKey] attribute allows you to inject the service key provided during resolution. This is handy in conjunction with AnyKey and is also similar to the construct in Microsoft.Extensions.DependencyInjection, but with native Autofac.

First, mark up your class to take the constructor parameter.

public class Service : IService
{
  private readonly string _id;
  public Service([ServiceKey] string id) => _id = id;
}

Then when you resolve the class, the service key will automatically be injected.

You can also make use of this in a lambda registration.

var builder = new ContainerBuilder();
builder.Register<Service>((ctx, p) => {
  var key = p.TryGetKeyedServiceKey(out string value) ? value : null;
  return new Service(key);
}).Keyed<Service>(KeyedService.AnyKey);

Metrics

Some metrics have been introduced that can allow you to capture counters on how long middleware is taking, how often lock contention occurs, and so on.

Set the AUTOFAC_METRICS environment variable in your process to true or 1 to enable this feature. You can see the set of counters that will become available here.

⚠️ This is NOT FREE. Collecting counters and metrics will incur a performance hit, so it’s not something you want to leave on in production.

General Performance Improvements

A pass over the whole system was made with an eye to trying to claw back some performance. Some additional caching was introduced to help reduce lookups and calculations of reflection data; and a few hot-path optimizations were made for the common situations.

A Personal Note

This is probably the biggest Autofac update made in quite some time - new features, some perf fixes, some metrics - and, with that, I really don’t have any active assisting project maintainers, so it was all just me. I’m pretty proud of this one. I have been suffering from maintainer burnout for a while, but I’ve got a little of my energy back lately and, honestly, it’s due to AI.

I used Codex and Claude Opus on this latest set of changes to help me out and do some of the heavy lifting. Don’t get me wrong, I reviewed all of what was output, but it was so energizing to be able to tell something else to go look for changes, dig for performance optimization opportunities, and validate them. I could create a robust set of tests (or, in some cases, adapt existing Microsoft container compatibility tests) to guide implementation and basically say, “Make it pass the tests.” I can safely say I would not have been able to deliver this new set of functionality in any near future without tools like that. Between time constraints and flagging motivation, it wouldn’t have happened.

I’m not at “just vibe code everything” level and I’m definitely not ready for Gas Town, but I see the value here and it counters some of the burnout.

comics, personal comments edit

After about 31 years of being a customer of Things From Another World, I closed my comic book subscription box last night.

It feels weird.

I’ve had a comic box there longer than most of the employees have been alive. I started out at the Milwaukie, OR, location, which is just across the street from Dark Horse. (Dark Horse Comics owns TFAW, so there’s a connection.) I moved to the Beaverton, OR, location when I moved to the west side of Portland for work. Through it all I’ve gone at least once a month to pick up Daredevil, Strangers in Paradise, Sin City… lots. I’ve seen TFAW go from just a couple of stores to something like six or seven, add a full online presence, and then cut all the way back to just a couple of locations again.

I have a closet full of long boxes. The storage aspect is not awesome. But they’re my comics, and I’ve had ‘em forever, and I love ‘em.

I’d started getting some notifications from TFAW that I wasn’t subscribed to enough comics, or at least not enough that came out regularly, and I needed to either subscribe to more or close my box. I’d seen it a couple of times before and usually found something new to pick up, but lately I just haven’t wanted something new on a regular basis.

When I told the clerk I wanted to close my box, they didn’t even blink. “OK.” No hard sell, no asking me why, no attempt to retain the customer. Just, “OK.” I was like, “I’ve had a box for over 30 years, I guess I’m not subscribed to enough, but it seems weird that I’ve been a customer this long - that you’ve been getting my money for this long - and there’s not some sort of provision for that.”

“Yup,” said the clerk.

And that was it. It was very anti-climactic and, honestly, disappointing.

I took the last comic left in the box - Umbrella Academy - Plan B #3 - and left the store.

halloween, costumes comments edit

This year we had 227 trick-or-treaters. That’s a bit on the higher end for us, but not quite as many as last year.

2025: 227 trick-or-treaters.

Average Trick-or-Treaters by Time Block

Year-Over-Year Trick-or-Treaters

Halloween was on a Friday and it was raining pretty constantly.

As with 2024, we technically started handing out candy at 5:30 because the number of kids this year starting early was overwhelming. I’ve grouped the 5:30p to 6:00p kids (38) in with the 6:00p - 6:30p kids (57) to total 95 in the 6:00p - 6:30p time block. Given this is the second time we’ve had to start early, I wonder if I need to adjust the time capturing to start at 5:30 from here on out.

Missing/incongruous data:

  • 2017 we were remodeling the house and didn’t hand out candy.
  • 2018 I walked with Phoenix and the tally got lost somewhere.
  • 2020 was COVID and no one handed out candy.
  • 2022 we were getting our floors redone due to a leak and the whole house was torn up so we didn’t hand out candy.
  • 2024 we bundled 5:30-6:00 (24) in with 6:00-6:30 (22) which affects the average for that block.
  • 2025 we bundled 5:30-6:00 (38) in with 6:00-6:30 (57) which affects the average for that block.

Cumulative data:

  Time Block
Year 6:00p - 6:30p 6:30p - 7:00p 7:00p - 7:30p 7:30p - 8:00p 8:00p - 8:30p Total
2006 52 59 35 16 0 162
2007 5 45 39 25 21 139
2008 14 71 82 45 25 237
2009 17 51 72 82 21 243
2010 19 77 76 48 39 259
2011 31 80 53 25 0 189
2013 28 72 113 80 5 298
2014 19 54 51 42 10 176
2015 13 14 30 28 0 85
2016 1 59 67 57 0 184
2019 1 56 59 41 33 190
2021 16 37 30 50 7 140
2024 46 82 52 26 29 235
2025 95 41 43 44 4 227

Our costumes this year:

Jenn as M3gan, me as PDX

2025 has been rough - we had a laundry room leak with repairs that spanned from January through September, we got a new roof… there was a lot of home project stuff going on. I was entirely out of inspiration and energy, so I went simple. I made the shirt using some custom printed PDX carpet fabric I found on Etsy. On my head is a small airplane stuffy I bought off Amazon and tacked down to a headband. The scarf is something I had already. I will hopefully be more on top of things next year.

mac comments edit

I really hate that OneDrive for Business names your OneDrive folder like OneDrive - Name of Your Business with a bunch of spaces and things in there. It jacks up command line stuff because ~/OneDrive - Name of Your Business doesn’t always evaluate ~ as your home drive and it cascades from there.

Instead of doing the smart thing and just creating a symbolic link to something nicer (ln -s './OneDrive - Name of Your Business' ./OneDrive) I thought I’d try to get it to sync to a different location. I really jacked it up. Uninstall/reinstall, several reboots, no luck. I had trouble formulating the right search or AI prompt to explain what I was trying to fix. I finally got the below out of a series of queries and prompts through Google Gemini, so I’m blogging it in case I need it again.

  1. Quit OneDrive: Select the cloud icon in the menu bar, then click Settings > Quit OneDrive.
  2. Locate OneDrive: Find the app in your Applications folder.
  3. Show Package Contents: Right-click on OneDrive and select “Show Package Contents”.
  4. Navigate to Resources: Go to Contents > Resources.
  5. Run the Reset Script: Double-click ResetOneDriveAppStandalone.command. A terminal window will pop up and clean a lot of things.
  6. Restart and Setup: Start OneDrive and complete the setup process.

csharp, dotnet comments edit

The move from Newtonsoft to System.Text.Json for JSON serialization in .NET is not a new thing, but there are two subtle differences that I always forget or get wrong so I figured I’d write them down so I can Google my own answer later.

This is based on .NET 8 and 9. If you come in looking at this later, they may have updated.

Dictionaries

There are Roslyn analyzers that want you to set dictionary-based properties to be get-only.

public class Model
{
  public IDictionary<string, string> WhatAnalyzersWant { get; } = new Dictionary<string, string>()
}
  • Newtonsoft supports this and will add the items to the existing dictionary.
  • System.Text.Json does not support this and the dictionary will remain empty after serialization.

For greatest compatibility between the two frameworks, leave dictionary properties get/set and suppress the analyzer message.

Enums

I work on a lot of services where we specify camelCase style naming, including on the enum members.

To set System.Text.Json up for camelCase enums, it looks like this:

var settings = new JsonSerializerOptions
{
  Converters =
  {
    new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true),
  },
};

For Newtonsoft, it looks like this:

var settings = new JsonSerializerSettings
{
  Converters =
  {
    new StringEnumConverter(new CamelCaseNamingStrategy()),
  },
};

In addition, Newtonsoft supports using System.Runtime.Serialization.EnumMemberAttribute to specify an exact value, which will override the camelCase naming. System.Text.Json does not support this attribute.

public enum Policy
{
  // Only Newtonsoft uses this attribute.
  [EnumMember(Value = "ALWAYS")]
  AlwaysHappens,

  NeverHappens
}

In the above example…

  • Newtonsoft will render ALWAYS and neverHappens.
  • System.Text.Json will render alwaysHappens and neverHappens.

Further, both frameworks allow you to mark an enum with a specific converter, which can also dictate the casing/strategy.

For greatest compatibility between the two frameworks, use the appropriate serializer settings to handle casing on your enum and don’t mark things up with any attributes related to serialization.