Medo.Config

Almost every application needs configuration handling and .NET has a reasonable way of doing this. But, I personally find it slightly overly complicated and I don’t like XML files it uses. I perfer a simple file format and thus, I have used my own configuration handling classes for years now. Code was simple and available as part of my Medo library.

So, after probably years of just copying class file from project to project, I finally decided to rewrite my configuration handling as a NuGet library. Better late than never, I guess.

So, what does Medo.Config offer?

Firstly, API interface is as simple as it gets.

Config.Write("KeyS", "Value");
Config.Write("KeyN", 42);
var s = Config.Read("KeyS", "DEFAULT");
var n = Config.Read("KeyN", 0);

By inferring type from default value, it ensures you don’t have to deal with nulls. While you do have Config.User property for more direct access, that is for special occasions only. And yes, it does support system configuration files under Linux.

Secondly, it uses a reasonable configuration file format. Each entry is just a key/value pair.

Key1: Value1
Key2: Value2

While it uses colon (:) internally, it will read key/value pairs with equals (=) just fine. This ensures this format is easily read or written by a human.

On topic of readability, I am fan of comments and I really hate when my comment gets removed when I save configs. Well, not here. For example, writing Key here is not going to delete comment. Honestly, back in days when I decided to “roll my own” this was really high on my priority list.

# Comment that stays
Key: Value

Proper configuration handling on Linux was also high on my priority list. Mind you, old code was only partially handling this as I used .app as my config. With this new code, I finally switched to XDG directory structure. This ensures that my home directory stays clear and that configuration is separate from user state.

Yes, this class supports both configration (user and system) and state. While you can use the same functions to access state, everything gets stored into a separate state file.

Config.State.Write("State", 1.0);
var s = Config.State.Read("State", 0.0);

But wait, that’s not all. This library also supports recent file handling. You just add files as you use them and library will handle ordering, duplicate handling, count limiting, and other such details.

Config.Recent.Files.Add(new FileInfo("file.txt"));
foreach (var file in Config.Recent.Files) {
  // do somethign with a recent file
}

This is not a rocket science but it surely comes in handy.

What a Difference Does a Comma Make

When starting a new C# project I like to create the basic structure immediately. In my case this means, src/, examples/, and tests/. Quite often I just copy an existing project and adjust it as needed. In this case, I copied .NET 8 project and adjusted it for my “usual” multi-framework setup.

<TargetFrameworks>net10.0,net8.0,netstandard2.0</TargetFrameworks>

I mean, for libraries, it’s just fair.

However, this time I was surprised by the error:

Package MSTest.TestFramework 4.1.0 is not compatible with net100 (net10.0,Version=v0.0). Package MSTest.TestFramework 4.1.0 supports:
  - net462 (.NETFramework,Version=v4.6.2)
  - net8.0 (.NETCoreApp,Version=v8.0)
  - net9.0 (.NETCoreApp,Version=v9.0)
  - netstandard2.0 (.NETStandard,Version=v2.0)
  - uap10.0 (UAP,Version=v10.0)

Since I did upgrade my MSTest package references to the latest, I thought it had someething to do with it. But even restoring packages to the same version used in another project caused the same essential error. It took me a while to notice the same thing most of you already have. The correct target line should have been:

<TargetFrameworks>net10.0;net8.0;netstandard2.0</TargetFrameworks>

Behind a really misleading error was a simple typo.

After running it again, all worked so I decided to upgrade packages (again). And then I stumbled upon other issue:

error Microsoft.NET.Test.Sdk doesn't support netstandard2.0 and has not been tested with it.
Consider upgrading your TargetFramework to net8.0 or later.

It seems that Microsoft decided to abandon the whole .NET Standard concept. Which is a shame because it really limits creation and testing of .NET 4.x code under Linux.

In the end, I downgraded packages slighty:

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
  <PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
  <PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
  <PackageReference Include="coverlet.collector" Version="6.0.2">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

And finally, after spending way too much time playing with config, I could finally start coding.

VSCode Rulers Config Change

Illustration

I love having rulers in text editor. While I don’t necessarily obey them at all times, they are useful as an indication of line length. That is, assuming they stay in the background. With VSCode 1.113 my background rulers suddenly lost their color and moved to foreground as grey lines. Quite distracting.

I double checked my config and it was the same as always:

"editor.rulers": [ 72, 78, 80, 132 ],
"workbench.colorCustomizations": {
  "[Default Dark Modern]": {
    "editorRuler.foreground": "#402020"
  },
  "[Default Light Modern]": {
    "editorRuler.foreground": "#F0E0E0"
  }
}

It didn’t take me long to find issue 54312. VSCode now allows customizable ruler color. However, in the process of this, editorRuler.foreground style was removed. Thus, my theme defaulted to ugly gray.

However, it is simple to recreate the old behavior:

"editor.rulers": [
  { "column": 72,  "color": "#402020" },
  { "column": 78,  "color": "#402020" },
  { "column": 80,  "color": "#402020" },
  { "column": 132, "color": "#512f2f" },
],

Here I even use the perk that caused the issue in the first place - my last ruler is a slightly different color. The only downside for me was the fact you cannot setup ruler color per theme. Thus, you might need to adjust colors every time theme is changed. Considering I almost never change the theme, this is something I am ready to live with.

Gray rulers be gone!

Speed Test from Command Line

I still like using SpeedTest by Ookla. It’s not the only kid in the town anymore but old habits die slowly. And honestly, it is a decent speed tool.

So, when I wanted to measure Internet speed from my Ubuntu server, I was happy to see it was available. And install is trivial

sudo apt install speedtest-cli

However, on my Ubuntu 24.04 installation this doesn’t really help since result is always 0.00. Never mind, there is a source availabe in now archived speedtest-cli repo. With a small change, this script will work a charm.

mkdir -p ~/Downloads
cd ~/Downloads
wget https://raw.githubusercontent.com/sivel/speedtest-cli/refs/heads/master/speedtest.py
chmod +x speedtest.py
sed -i '1s/python/python3/' speedtest.py
./speedtest.py

Processing Data on CAPsMAN

Illustration

As I was going over changelog for Mikrotik’s RouterOS 7.21, I noticed the new functionality: “On CAPsMAN Data Processing”. Well, when I say new, it’s actually the functionality that was available on 6.x and earlier. However, with RouterOS 7.x it was gone. Since I used it, I did try to resist as long as I could but eventually I had to cry myself through RouterOS 7 upgrade.

But, let’s rewind a bit. What this feature actually allows? For this we need to understand how “normally” CAPsMAN on Mikrotik processed wireless traffic.

CAPsMAN is a way for Mikrotik to allow centralized control over multiple wireless devices. In my example, than meant controlleing two different access points from my (non-wireless) router. When doing so, you do essentially all configuration on router (CAPsMAN) and wireless devices (CAPs) get their configuration automatically updated to match. CAP devices essentially become just fancy wireless switches without much logic outside of (admittedly quite a few) customizations.

As switches, that means that anything on the same medium, i.e. wireless network, gets to communicate with each other. Of course, you can isolate users of networks but there is no real way to centrally allow some users to communicate with others or not. I am lying a bit here - you can always setup local firewall rules for stuff happening between networks but it gets annoying to keep config on multiple devices. I mean, CAPSman was there to centralize config, not to leave us in the same mess we were in before.

If you were willing to sacrifice some speed, RouterOS 6.x allowed you to redirect ALL wireless traffic to your CAPsMAN. This ensured your router sees each packet and thus you get to use firewall on your router. Even for devices connected on the same network on the same access-point. This made configuration really centralized.

But, those more familiar with enterprise setups might tell I am doing it wrong. And yes, this is not a feature for places where you want complete isolation and control. This is more of a control-light approach.

In enterprise networks, guest network will be completely isolated - usually using VLANs. And it should be. In my network, I will isolate guest network using firewall. And that is way less secure as you’re always one error away from complete mess. But, for home network where even guests are reasonably trusted, it is a small price to pay for flexibility. For example, allowing guests to access rest of home computers for the purpose of a local LAN game becomes trivial.

This is not a feature for professionals and strict environments. But it simplifies home network design a lot. And finally, RouterOS 7 line brings that feature back.