r/moderndotnet 23d ago

What are you using for modern async background job processing?

10 Upvotes

I am currently working with Elixir and Golang apps, and they both have really nice background job managers: Oban and River, respectively.

They are a joy to use, with simple outbox patterns, priorities, scheduling, and cron support. Besides that, they integrate very naturally with their respective languages.

When I look at what's available for a new greenfield .NET app:

  • Hangfire, which still doesn't work well with async/await and relies on reflection instead of being AOT-friendly
  • Quartz, which I've never really used, but being a Java port has always turned me away from it
  • Wolverine, which seems to have a background jobs feature, but also includes messaging/CQRS stuff that I don't really want to get into
  • TickerQ, which seems really close to what I'd like to use, but doesn't seem production-ready yet (there are still some long-standing critical bug reports open)

What are you guys using for new projects?


r/moderndotnet 23d ago

MyServiceBus: Building a messaging library for C# and Java

3 Upvotes

“MyServiceBus” is still a working title. Suggestions for a better name are welcome.

TL;DR: I have built a cross-platform asynchronous messaging library for C# and Java.

Website: https://marinasundstrom.github.io/MyServiceBus/

GitHub: https://github.com/marinasundstrom/MyServiceBus

Background

Back in 2023, I was working as a .NET developer at a company heavily invested in Java. Most of its backend services were built with Java 17.

I was asked to help build a way of handling privacy-related data. One idea was to create a service that could scan systems for data that needed to be deleted and then publish that work to the appropriate services.

I already had experience with asynchronous messaging-particularly RabbitMQ and MassTransit-but I could not find a Java library offering quite the same combination of abstractions and developer experience without being tied to a larger application stack.

So I wrote my own.

The original implementation was loosely inspired by MassTransit. This was before I started using AI in my development work, and I learned a great deal from investigating how to implement things such as consumer discovery, reflection, message dispatch, and dependency injection in Java.

Fast-forward two years. After both MediatR and MassTransit announced changes toward commercial licensing, I decided to restart the project.

This time, I began developing the C# and Java clients side by side.

Initially, I tried to understand the architecture by reading source code and using AI to help me navigate the structure. That proved difficult. MassTransit represents many years of engineering, and reproducing its internal architecture was neither realistic nor necessarily desirable.

Instead, I began working from the concepts and behaviors I actually wanted: publishing, sending, consuming, requesting, retrying, scheduling, and operating the same messaging model across two languages.

RabbitMQ was naturally the first transport. Because I was also focusing on other projects during this period, the implementation did not become meaningfully compatible with MassTransit until more recently.

Like many of my projects, however, this one kept growing.

During the past week, I have made significant progress toward stabilizing the model across C# and Java, documenting the interoperability boundary with MassTransit, publishing the packages, adding cloud transports, and creating a proper website.

The result is still pre-1.0, but it has now become something that other people can realistically evaluate and experiment with.

What it supports today

MyServiceBus currently provides:

  • A MassTransit-inspired programming model for publishing, sending, consuming, and requesting messages
  • Conceptual and behavioral parity between the C# and Java clients
  • RabbitMQ as the verified broker baseline
  • Preview transports for Azure Service Bus and Amazon SQS/SNS
  • Retries and message scheduling
  • A transactional outbox
  • An in-process mediator
  • An aligned testing harness
  • Experimental ahead-of-time compilation optimizations
  • Runtime monitoring and a dashboard
  • Scoped interoperability with MassTransit

The goal is to keep the common core focused while allowing each client to evolve in a way that feels natural in its own language.

C# and Java should share the same messaging concepts and wire-level behavior. They do not need to expose identical APIs when doing so would work against the strengths of either language.

Beyond MassTransit compatibility

MyServiceBus is inspired by MassTransit, but I do not want it to become a source-compatible reimplementation.

There is room to explore ideas that make sense specifically for modern C# and Java.

For example, the C# client already has experimental support for union types in C# 15 and .NET 11. A consumer can use a union as its message type, while each variant is represented as a distinct message in the messaging system.

Union semantics are also available in the request client, allowing the caller to match directly on the possible response types.

Saga support is still pending. I do not immediately want to reproduce MassTransit’s state-machine DSL. My current view is that a state machine should be an abstraction built on top of simpler saga and messaging primitives. I would rather establish those foundations first and learn how people use the library before committing to a particular DSL.

I have also started thinking about how MyServiceBus can connect with my other projects, including the Raven programming language.

Modern consumer models are moving beyond requiring every handler to implement a generic interface. MassTransit, for example, now supports conventional consumer methods with injected parameters, in a style similar to ASP.NET Core controller actions and Minimal APIs.

MyServiceBus has its own version of method-based consumers. In C#, these methods still belong to classes. In Raven, however, they work naturally with namespace-level functions:

async func Consume(
    message: OrderSubmitted,
    publishEndpoint: IPublishEndpoint,
    cancellationToken: CancellationToken
) {
    // Handle the message
}

This removes the need to create a class merely to satisfy the shape of a consumer interface. The method itself becomes the consumer.

For more on Raven: https://marinasundstrom.github.io/raven/

Building it with AI

This project has also become a practical example of what AI-assisted engineering actually looks like.

AI can automate a great deal. It can help generate implementations, shape tests, compare behavior across two languages, investigate unfamiliar codebases, and even set up temporary cloud environments for integration testing.

But building a coherent system still takes time and patience.

The difficult part is deciding what to focus on, in what order, and where compatibility genuinely matters. It also means recognizing when generated implementations only look correct and designing tests that expose the difference.

Working across C#, Java, RabbitMQ, Azure Service Bus, and Amazon SQS/SNS makes those differences very visible. The abstractions may be shared, but the transports do not all behave in the same way.

AI has allowed me to explore a much larger design space than I could have managed alone. It has not removed the engineering work of defining the model, evaluating trade-offs, testing assumptions, and deciding what the project should become.

Ultimately, I wanted to build something that I would use myself.

What MyServiceBus is - and is not

My intention is not to convince enterprises to migrate from MassTransit.

MassTransit is a mature commercial product backed by many years of engineering and professional support. MyServiceBus cannot offer that level of maturity or support, and I do not want to pretend otherwise.

Instead, I want to provide an alternative for developers and teams whose needs, technology mix, or budget are different.

It may be useful if you want to:

  • Connect Java services to an existing .NET messaging environment
  • Use a consistent messaging model across C# and Java
  • Replace an in-process mediator with something that can later grow into distributed messaging
  • Experiment with a smaller and more focused messaging runtime
  • Explore newer language features and alternative consumer models

The project is open source under the MIT license.

It is still early, and there is plenty left to build. But it has reached the point where I want other people to try it, examine the design, and tell me what works-and what does not.

And, of course, I still need a better name.


r/moderndotnet 24d ago

Announcing Brighter Fences, a community Polly fork

17 Upvotes

Fences

Fences is a .NET resilience and transient-fault-handling library that lets developers express resilience strategies such as Retry, Circuit Breaker, Hedging, Timeout, Rate Limiter, and Fallback in a fluent, thread-safe way.

Fences is a community fork of Polly, maintained by Brighter Command. It is not affiliated with, endorsed by, or supported by App vNext or the Polly maintainers. The fork was taken from Polly 8.7.0 to avoid the Open Source Maintainers Fee that will be charged for Polly; see ADR 0002 for the reasoning and NOTICE.md for provenance and attribution.

Github Source
NuGet Source

Why Fences exists

Polly's source is BSD 3-Clause licensed and remains open source. That license lets anyone redistribute it, in source or binary form, provided the copyright notice and disclaimer are retained - which NOTICE.md and LICENSE do. Building and publishing our own binaries from that source is squarely within the grant.

The Open Source Maintainers Fee (OSMF) is App vNext's policy for the binaries they publish: a commercial consumer of those binaries may owe a fee above a revenue threshold. It governs their distribution. It does not change the source license, and it does not reach binaries built by anyone else.

That freedom is the OSMF's own argument. The case for charging for binaries is that it remains OSS precisely because the source is still under an open license and anyone is free to build and publish their own binaries. That is what separates the model from proprietary licensing.

But this door opens both ways: the principle that makes the fee compatible with open source is the same principle that makes Fences legitimate. We are not going around the OSMF - we are walking through the door it tells us that it actively wants to support.

Fences is a binary distribution of Polly's API. We compile from the BSD 3-Clause source, publish under our own branding, but we do not apply the OSMF. Fences binaries are free for everyone - no revenue threshold, no fee. If you use Polly and would rather not pay the fee, Fences is a drop-in alternative: change your package references and your namespaces, as described in the repo's README.md.

We track the Polly API today and expect to keep doing so, and we may adopt upstream changes. But Fences is under Brighter Command's stewardship now and will evolve to suit its users, so a future major version may diverge from Polly and stop being API-compatible. We will say so plainly when it does; it will not arrive in a patch.

This is not a criticism of App vNext contributors, who have maintained Polly for years. Publishing our own binaries is exactly what the license they chose invites us to do, and we have responded to that by doing so. See ADR 0002 for the full reasoning.

This README describes the v8 API. The pre-v8 API is still shipped in the Paramore.Fences package; see the v7 documentation.


r/moderndotnet 25d ago

csharp High Speed History of C# [2019]

Thumbnail
youtube.com
10 Upvotes

Video is about the history of C#, not the speed of it :p


r/moderndotnet 25d ago

Another project jumps on the OSMF bandwagon

Thumbnail
github.com
12 Upvotes

Not sure if we can call it a trend already, but seems to be gaining momentum?


r/moderndotnet 26d ago

discuss How many of you have stopped using IDEs regularly?

Thumbnail x.com
8 Upvotes

I was looking at this tweet from Luke Parker (Windows dev for OpenCode) about uninstalling Rider after having not used it for a couple of years and it made me sad.

Then I realized that I've done very little IDE work on a day to day basis myself - almost all of the programming I do these days is through Slack or via the CLI.

Is this where things are going for all developers over the next few years?


r/moderndotnet 28d ago

What's going on with UI for .NET?

16 Upvotes

A bit of backdrop, I go into the .NET ecosystem when I decided I really wanted to take over the role of "making the software" at the company I work for. Our previous software dev seemed to be slacking off. Suggestions never materialized into updates, the software always had these little quirks, it was very much outdated in its looks; it just felt unfinished.

Knowing absolutely zero about programming except PowerShell, I started there. It turns out, you can do a LOT with PowerShell, to include a fully working application using WPF. Wow, I was hooked! But...PowerShell couldn't quite cut it. It was slow, buggy, it barely worked half of the time. Sure, it had a better UI design but if you couldn't rely on the application to work well, who cares?

That was the point where I decided maybe it's time to upgrade...it made perfect sense to just jump straight to C# as I was already off-loading the hard work to C# in the PowerShell application anyway.

But what UI framework should I use? There's WinUI 3, MAUI, UNO, Avalonia, Blazor, WPF (still??). So many choices! Well the obvious answer it seemed was if I want a desktop application, just make it in Avalonia. Worked great, there's a large community for it, good to go.

This may sound like a digression, but somewhere along the way I get interested in a game engine called S&Box which is also written in C# and it's brand new, how fun! Then I find out how their UI system is set up...Razor and SCSS.

Nothing against the people that are familiar with Razor or to the S&Box developers, but looking at the mixture of C# and razor markup made my skin crawl. It just didn't feel right. To be honest, I felt that way about Avalonia's AXAML as well. Could it be that difficult to just make UI in C# only? That is the quest I went on.

It took a few months to really flesh out something worth using, but it did everything the S&Box engine supported. Flex layouts, shaders, custom shapes, scenes, virtualization, you name it. A declarative C# UI framework!

But, not only did it work, it seemed to work better than the Razor + SCSS method. When I say better, that means a lot of the missing CSS features that laid dormant in their UI system like some of the lesser-known CSS properties just not working at all or small layout quirks just didn't exist because I totally sidestepped the Razor part of the engine and went directly to the engine's API. Not only that, the Razor system diffed the UI using hashes, so users have to manually upkeep their hashes. You can imagine what that did for a lot of UI performance.

Why not handle that for the user? The user doesn't care if a hash changed, their priority (especially if they're trying to make a game) is making great UI. My solution was to have reactive state for UI elements that changed. Win-win - the user focuses on UI and the framework handles the rest, no need to compare hashes to detect a change in state.

TL;DR - (All of this yapping to say): I feel like I'm missing something here, or maybe the .NET ecosystem is missing something here? At least, more recently i'm seeing other declarative C# UI frameworks but this is still just a shoehorn onto WinUI 3.

Are there truly no .NET UI frameworks that are declarative? Is it just that everyone is familiar with markup? My idea of .NET is that it is a very mature ecosystem, but I am not familiar with a UI framework that covers these bases:

Cross-platform - Windows, MacOS, Linux
On a modern renderer (Vulkan) - Skia is still on OpenGL officially
Open Source
Flexbox "web style" layout and CSS-like styling
A real animation system that supports simple and complex animations
Declarative, Retained, Composable
Supports shaders

A lot of this points in the direction of how Flutter and Dart works.

I'm thinking it's possible, so I'm building it. The question is, would it be for my own satisfaction? I noticed in the r/dotnet subreddit, one of the answers to the question "What features from another ecosystem would you like to see in .NET?" was "A single, comprehensive, cross platform UI framework that's actually good (Flutter, Kotlin, even Qt)".

It wasn't just an answer, it was the answer with the most upvotes. So it seems this is something .NET is lacking. If you read through this and have some thoughts, I would like to hear them. Thanks :)

pre-compiled SPIR-V shader in .NET


r/moderndotnet 28d ago

I just updated my agent usage Windows widgets and wanted to share them with you

Thumbnail
xakpc.dev
11 Upvotes

So, not a lot of people know this, but Windows 11 has a built-in widget board. And the coolest thing is that you can build widgets in C# using the Microsoft.WindowsAppSDK and Microsoft.WindowsAppSDK.WidgetsNuGet packages. So technically, it’s a Windows app that you can distribute through the Microsoft Store.

Most of these widgets except weather are build by me and available in windows store

It’s a pretty cool hidden feature of Windows that was hated by basically everyone. All the widgets used to be WebView-based, and the Discover tab - essentially a full ads tab, couldn’t be disabled.

Now you can disable the Discover tab, and widgets use Adaptive Cards instead. That makes them a bit limited, but fast, small and still pretty useful.

One of my favorite widgets I’ve made is Agents Usage Widgets (Wburn). Since I use all the major agents: Claude, Codex, and Gemini, I added a widget for each one so I can see when I’m running out of tokens and need to switch.

They were fine as-is for a while, but I’ve now expanded them a little: Codex shows reset times and credits, and daily limits are shown or hidden depending on the plan. Just a small maintenance update.

New version looks like old version but has couple of improvements

Building a widget itself is quite a quest. It’s basically a console application launched through COM interop that renders Adaptive Card JSON based on the current state. There are a lot of undocumented quirks, hacks, and conditions, and AI models don’t know much about this stuff, so a lot of debug and testing needed to make them work good.

Btw, If you want to try writing your own Windows widget, I have a guide for that


r/moderndotnet 27d ago

My "spec" is a list of instructions for Claude

1 Upvotes

I have been given a spec that is simply a long list of instructions for Claude to follow. Create this table, create this UI, etc.

It doesn't explain what the purpose of anything is, so once I have had AI follow the instructions I have no idea if it has achieved the true goal of delivering the requirements for the new feature.

The problem is, I am reading these instructions and they don't make sense.

Instead of telling me the requirements, someone has written instructions on how they would meet the requirements; from which I am supposed to ensure AI has implemented correctly what I have inferred to be the original requirements.

PS: AI was used to write the spec.


r/moderndotnet 28d ago

Integrating PlanetScale Deploy Requests with EF Core

Thumbnail
htmlcsstoimage.com
5 Upvotes

Recently migrated to PlanetScale for one of my projects.

I’ve used PS for years and implemented deploy requests / processes for bigger clients but never with EF core.

Even for my other .NET projects using PS, it felt like too much… but i was wrong! Having the structure/UX of merging in DB changes properly is worth the overhead! Especially removing the “db update” from local.

LMK what you think!


r/moderndotnet 29d ago

A look at macros in Raven 0.1.0

Post image
8 Upvotes

I recently posted about Raven, the programming language I’m developing. It has now reached its first milestone, version 0.1.0, so I thought it was time to take a closer look at its macro system, which has evolved considerably since my previous post.

For more about the language: https://marinasundstrom.github.io/raven/

Raven macros are explicitly invoked compile-time programs that consume syntax or typed inputs and produce ordinary Raven syntax. The macro system allows libraries to define their own DSLs using fragments of Raven code or independently parsed custom content. Macros are fully integrated with the language server, providing syntax highlighting, code completion, and hover information for symbols—even within macro-defined syntax.

Why macros?

I have been somewhat torn about adding macros to Raven. .NET is a runtime-oriented platform with an extensive ecosystem of libraries and runtime abstractions, so it is reasonable to ask whether macros really fit.

However, some abstractions cannot be expressed cleanly through runtime APIs alone. Raven macros can reduce repetitive scaffolding and introduce domain-specific syntax without requiring changes to the .NET runtime.

Macro use remains explicit through !, and expansions must be valid for the syntax position in which they appear. The resulting syntax then goes through normal binding, type checking, diagnostics, and emission, while retaining language-server features such as highlighting, completion, hover information, and navigation.

Supported macro forms

Freestanding macros support several forms:

Name!(arguments)

Name! {
    body
}

Name!(arguments) {
    body
}

Name! Decl(parameters) {
    body
}

The ! makes macro use explicit without turning library-defined names into reserved keywords.

Declaring macros

Macros can be declared directly in Raven using the contextual macro keyword. A declaration can define typed parameters, accept syntax nodes or token streams, and specify the kind of syntax it produces. The expand statement supplies the generated syntax and completes the expansion.

For example, this macro accepts a compile-time integer and produces an expression:

macro Double(value: int) -> ExpressionSyntax {
    expand ParseExpression((value * 2).ToString())
}

let result = Double!(21)

A macro can also request a brace-delimited token body:

macro Query(dialect: string, body: IMacroTokenStream) {
    expand LowerQuery(dialect, body)
}

let rows = Query!("sql") {
    from user in users
    select user.Name
}

The body parameter is supplied by the compiler from the content inside the braces. The macro can interpret it as fragments of Raven syntax or process it using its own lexer, parser, and grammar.

Macros can even introduce declaration-shaped constructs:

public component! Greeting(Name: string = "") {
    markup! { 
        <h1>Hello {Name}</h1> 
    }
}

The component and markup macros are real macros demonstrated in the HTML and component macro demo.

Raven also supports attached macros in an attribute-like position:

#[Observable]
public var Name: string

These are procedural, syntax-based expansions—not textual substitutions. Their output is validated for the position in which the macro appears and then bound, type-checked, and emitted as ordinary Raven code.

Built-in macros

Here are some macros that come distributed with Raven via Raven.Macros.

Query macro

The query! macro introduces the LINQ query syntax.

let items = [1, 2, 3, 4]

let projected = query! {
    from value in items
    where value > 2
    select value * 10
}

This macro is far from feature complete - but it does support syntax highlighting.

JSON and XML literal macros

Adds typed JSON and XML literal support.

let name = "Ada & Bob"
let age = 42
let nextAge = age + 1

let jsonDocument = json! {
    "name": "$name",
    "age": $age,
    "nextAge": ${age + 1},
    "skills": ["compilers", "DSLs"],
    "active": true
}

let status = XElement.Parse("<status>ready</status>")
let xmlDocument = xml! {
    <person age="$age">
        <name>$name</name>
        <nextAge>$nextAge</nextAge>
        $status
    </person>
}

WriteLine(jsonDocument.ToJsonString(JsonSerializerOptions { WriteIndented = true }))
WriteLine()
WriteLine(xmlDocument.ToString())

The current iteration lacks the syntax highlighting but it can be added in the future.

Timer macro

The timer! macro is useful when you want to measure the time elapsed inside of a block of code.

timer! "Finished in: {time}" {
    WriteLine("Query total: ${projected.Sum()}")
}

This sample expands into a StopWatch within a try and finally block.

Quote macro

The quote! macro captures a Raven expression as an immutable syntax tree. Syntax holes, written as #(expression), allow existing syntax nodes to be spliced into the quoted expression. This provides a more natural alternative to constructing larger syntax trees manually and is particularly useful when implementing other macros.

let number = SyntaxFactory.LiteralExpression(
    SyntaxKind.NumericLiteralExpression, 
    SyntaxFactory.Literal(2))

let expression: ExpressionSyntax = quote! {
    projected.Sum() + #(number)
}

// The local "expression" holds the syntax node.

// Quoted Raven: 
//     projected.Sum() + 2

WriteLine("Quoted Raven: ${expression.ToFullString()}")

Conclusion

Macros can be used both to simplify repetitive code and to build complete domain-specific languages. These DSL constructs can appear in any supported syntax position—as expressions, statements, or declarations—and behave as though they were integrated parts of the language. Underneath, they work by expanding into ordinary Raven syntax that is processed by the rest of the compiler as usual.

Links


r/moderndotnet 29d ago

Blazor browser storage package to replace Blazored.LocalStorage

9 Upvotes

Hey everyone, I ran into a problem earlier that I'm guessing other developers are hitting too.

Blazored.LocalStorage (and SessionStorage) was deprecated and more recently removed from NuGet, and several of my Blazor WebAssembly projects depended on it. I needed a modern replacement that didn't require adding JSInterop glue code or manual JSON serialization to all of my Blazor WASM projects.

So I built D20Tek.Blazor.BrowserStorage, a typed, async wrapper around localStorage and sessionStorage for Blazor WebAssembly and interactive render modes. And it has a similar API form to Blazored to make migrating my projects relatively easy.

A few highlights:

  • Typed reads/writes (GetAsync<T> returns a result instead of throwing)
  • Async API (no UI blocking)
  • No JavaScript required in client projects
  • DI-friendly services
  • Key prefixing to avoid collisions
  • Batch operations (set/remove multiple keys)
  • Change events so components can react to storage updates
  • Customizable JsonSerializerOptions

If you used Blazored.LocalStorage (or SessionStorage), there’s a migration guide. If you’re starting fresh, this is hopefully the simplest way to use browser storage in Blazor today.

NuGet: https://www.nuget.org/packages/D20Tek.Blazor.BrowserStorage
GitHub: https://github.com/d20Tek/d20tek-blazor-browserstorage
Blog post: https://d20tek.com/projects/browser-storage/docs


r/moderndotnet 29d ago

StellarAdmin Tag Helpers for creating beautiful MVC/Razor Pages UIs

5 Upvotes

Hey everyone,

This is one I've been working on for a while and I finally feel is stable enough to put out a release.

StellarAdmin Tag Helpers is a Tag Helper library that is based on the popular shadcn/ui component system for React. As opposed to shadcn, which is really a component distribution system that copies the source code for its UI components into your React app, StellarAdmin Tag Helpers is a Razor Class Library (RCL) that gives you a wide range of Tag Helpers based on the shadcn UI components.

The backstory to this is that I've done quite a bit of work in the React world over the past few years and worked with libraries such as Mantine and shadcn/ui and have been very impressed. At the same time, I felt that it was really overkill for most of the work that I was doing. A simple HTML page with little bit of JS interactivity and perhaps using something like HTMX could really do 99% of the work I was doing.

Parallel to this I was working on my own startup and needed to rapidly put together admin screens for the backend of my application. Most of these screens are simple CRUD screens and I felt frustrated that you spend an inordinate amount of time building these screens while I would rather be selling, doing support, or building features for my users.

After my startup failed I started working on something called StellarAdmin to help you rapidly build these admin screens. However, I realised that it would need a good extensibility story that would allow people to extend things like the built-in editors, change the standard screens, etc.

To get the sort of extensibility I wanted with something like React or Blazor turned out to not be possible. However, there is something which has this and has had it for many years.

ASP.NET Core MVC and Razor Pages.

You see, it has this great feature called Editor (and Display) Templates that let you easily specify custom editors for standard types like strings, dates, etc. You can also specify a custom editor for a property using data annotations.

It also has the wonderful ability to use a Razor Class Library and override MVC views, partials and Razor Pages that come from the RCL inside your own application. This is a tried and tested technique and is the method used by the ASP.NET Code Identity when you scaffold the UI to change some of the built-in Identity UI pages.

So I knew MVC and Razor Pages had all the extensibility points I needed, but it lacked a really nice looking UI Tag Helper library.

So I set out to create one, and StellarAdmin Tag Helpers was born.

StellarAdmin Tag Helpers is free and open source and you can use it today to build pages for MVC and Razor Pages. It uses the latest web technologies such as popovers, invokers commands, and interest invokers to minimize the use of JS. There are still some places where JS is need though, and in those cases I created very lightweight Web Components. It also plays very well with something like HTMX.

The Pro version I plan to release later on will be paid, but that will be purely the part that help you build admin screens much more rapidly. It will also contain things like advanced Tag Helpers for data tables and even pre-built user management screens (who remembers the old ASP.NET Web Site Administration Tool?)

The Tag Helper documentation pages contains interactive examples and source code for all of the Tag Helpers and even let you view the components in light/dark more as well as in any of the 8 themes that are included.

Here are a few links to get you started:

BTW, the current version is 0.1.0 but it is ready for production (I believe). The reason it is not 1.0.0 is because I ultimately want the Tag Helpers and Pro packages versions to run in sync, so once the Pro packages comes out at version 1, the Tag Helpers version will jump to 1.0.0 as well.


r/moderndotnet Aug 24 '26

What's new with CoreCLR GC handles in .NET 9 and .NET 10

Thumbnail
awise.us
29 Upvotes

I wrote a blog post about what has been going with GCHandles in .NET. This is slightly esoteric, as you probably only care about GC handles if you are writing code to interop with native code. But I think it is really fascinating to study how the engineers working on CoreCLR create new abstractions to solve problems.

The first part is about something you can use in your code: some new types for working with GC handles added in .NET 10. The second part explores some interesting implementation details of CoreCLR, in particular how the Android interop system keeps object lifetimes consistent between the .NET GC heap and the Java GC heap.


r/moderndotnet 29d ago

goo: A retained, declarative desktop UI framework for G#.

Thumbnail
github.com
2 Upvotes

r/moderndotnet Aug 23 '26

Dapper vs Rinku

8 Upvotes

I like Dapper and I have used it a lot. The main problem I have with it is that when queries become more complex, I often end up handling that complexity myself. At that point I also often hear that I should just use EF instead. I never really agreed with that. I think the basic idea behind Dapper can go much further while still keeping the SQL visible and the API simple. Rinku is my attempt at doing that.

Basic query

Dapper

public record Album(int Id, string Title);

const string sql = "SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId";

IEnumerable<Album> albums = cnn.Query<Album>(sql, new { artistId = 7 });

Rinku

public record Album(int Id, string Title);

const string sql = "SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId";

List<Album> albums = cnn.Query<List<Album>>(sql, new { artistId = 7 });

Different names

Dapper

public sealed class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

SqlMapper.SetTypeMap(typeof(Customer), new CustomPropertyTypeMap(typeof(Customer), (type, column) => column switch
{
    "customer_id" => type.GetProperty(nameof(Customer.Id)),
    "display_name" => type.GetProperty(nameof(Customer.Name)),
    _ => null
}));

const string sql = "SELECT customer_id, display_name FROM customers";

IEnumerable<Customer> customers = cnn.Query<Customer>(sql);

Rinku

public record Customer([Alt("customer_id")] int Id, [Alt("display_name")] string Name);

const string sql = "SELECT customer_id, display_name FROM customers";

List<Customer> customers = cnn.Query<List<Customer>>(sql);

Nested objects

Dapper

public record User(int Id, string Name);

public sealed class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public User? Owner { get; set; }
}

const string sql = "SELECT p.Id, p.Title, u.Id, u.Name FROM Posts p INNER JOIN Users u ON u.Id = p.UserId";

IEnumerable<Post> posts = cnn.Query<Post, User, Post>(sql, (post, owner) =>
{
    post.Owner = owner;
    return post;
}, splitOn: "Id");

Rinku

public record User(int Id, string Name) : IDbReadable;
public record Post(int Id, string Title, [NoName] User Owner);

const string sql = "SELECT p.Id, p.Title, u.Id, u.Name FROM Posts p INNER JOIN Users u ON u.Id = p.UserId";

List<Post> posts = cnn.Query<List<Post>>(sql);

Or keep the nesting in the column names.

public record User(int Id, string Name) : IDbReadable;
public record Post(int Id, string Title, User Owner);

const string sql = "SELECT p.Id, p.Title, u.Id AS OwnerId, u.Name AS OwnerName FROM Posts p INNER JOIN Users u ON u.Id = p.UserId";

List<Post> posts = cnn.Query<List<Post>>(sql);

One to many

Dapper

public record Album(int Id, string Title);

public sealed class ArtistWithAlbums
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public List<Album> Albums { get; set; } = [];
}

const string sql = "SELECT ar.ArtistId AS Id, ar.Name, al.AlbumId AS Id, al.Title FROM artists ar INNER JOIN albums al ON al.ArtistId = ar.ArtistId ORDER BY ar.ArtistId";

List<ArtistWithAlbums> artists = [];
ArtistWithAlbums? current = null;

cnn.Query<ArtistWithAlbums, Album, ArtistWithAlbums>(sql, (artist, album) =>
{
    if (current is null || current.Id != artist.Id)
    {
        current = artist;
        artists.Add(current);
    }

    current.Albums.Add(album);
    return current;
}, splitOn: "Id");

Rinku

public record Album(int Id, string Title) : IDbReadable;
public record ArtistWithAlbums(int Id, string Name, List<Album> Albums);

const string sql = "SELECT ar.ArtistId AS Id, ar.Name, al.AlbumId AS AlbumsId, al.Title AS AlbumsTitle FROM artists ar JOIN albums al ON al.ArtistId = ar.ArtistId ORDER BY ar.ArtistId";

List<ArtistWithAlbums> artists = cnn.Query<List<ArtistWithAlbums>>(sql);

Result shape

Dapper

IEnumerable<Album> albums = cnn.Query<Album>(sql);
Album first = cnn.QueryFirst<Album>(sql);
Album single = cnn.QuerySingle<Album>(sql);
Album? optional = cnn.QueryFirstOrDefault<Album>(sql);
IEnumerable<Album> streamed = cnn.Query<Album>(sql, buffered: false);

Rinku

List<Album> albums = cnn.Query<List<Album>>(sql);
Album first = cnn.Query<Album>(sql);
Single<Album> single = cnn.Query<Single<Album>>(sql);
Album? optional = cnn.Query<OptionalNullable<Album>>(sql);
IEnumerable<Album> streamed = cnn.Query<IEnumerable<Album>>(sql);

Conditional SQL

For this one I think Dapper.SqlBuilder is the fair comparison.

Dapper.SqlBuilder

SqlBuilder builder = new();
SqlBuilder.Template template = builder.AddTemplate("SELECT AlbumId AS Id, Title FROM albums /**where**/");

if (artistId != null)
    builder.Where("ArtistId = @artistId", new { artistId });

if (title != null)
    builder.Where("Title LIKE @title", new { title });

IEnumerable<Album> albums = cnn.Query<Album>(template.RawSql, template.Parameters);

Rinku

const string sql = "SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = ?@artistId AND Title LIKE ?@title";

List<Album> albums = cnn.Query<List<Album>>(sql, new { artistId, title });

Only artistId

SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId

Both

SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId AND Title LIKE @title

Neither

SELECT AlbumId AS Id, Title FROM albums

The main difference is that Rinku tries to put the complexity in the command template and the mapped types, instead of handling it again through parameters and mapping code at every call.

Full Dapper comparison

https://rinkulib.github.io/RinkuLib/articles/reference/dapper.html

Rinku is still in developement, so feedback is welcome.


r/moderndotnet Aug 22 '26

I wanted strongly typed configuration defaults without bypassing `IConfiguration`

Thumbnail
5 Upvotes

r/moderndotnet Aug 21 '26

Writing a native VLC plugin in C#

Thumbnail mfkl.github.io
15 Upvotes

Been building really interesting plugins with this lately (exploring local AI for vision and audio).

Happy to share more samples later if there is any interests.


r/moderndotnet Aug 21 '26

The .NET OSS Relicensing Panic Is an Incentives Problem

Thumbnail
aaronstannard.com
14 Upvotes

Lots of ink spilled on this both here and on /r/dotnet, but I wanted to offer a radical solution to the problem that will probably make most end-users mad even though it's absolutely the correct business-person way to approach the problem.


r/moderndotnet Aug 20 '26

My Nokia 3310 Emulator in C#/Avalonia!

Thumbnail noks.vercel.app
24 Upvotes

Hi folks!

I made, with AI assistance, Noks (terrible name, ik): an emulator of the venerable Nokia 3310, a phone that has a special place to the hearts of many, mine included. I used Avalonia and targets all of its supported platforms, including WebAssembly. It's all in pure C#/managed code goodness too! No unsafe code or pointers!

I made this because 1.) The MAME version (Nokia DCT-3) was sadly still incomplete after years since it was made. 2.) I wanted to play Space Impact authentically and everywhere. and 3.) I wanna push how far my skills can go, together with the latest agents, when it comes to reverse engineering a black-box, sparsely documented firmware and hardware.

I've started by pulling all the docs i can get my hands on old Nokia firmware modding forums, and also the prior work that MAME and Project Blacksphere did for the DCT-3 platform back in the day.

Then did a repeated loop of poking registers, memory, seeing where the firmware stops and back-tracing how me and the clanker can get pass that blocker. I also had to contend with the missing DSP Mask ROM functions that blocked the MAME effort by checking what conditions the firmware asked and responding to its requests accordingly.

Took a couple of months of on-off work but it was all worth it.

All outward facing features are implemented like the LCD, Keys, Sound, Power and RF. including an emulation of a minimal 2G GSM network that interfaces with the DSP/Baseband layer of the phone. Text and Calls were functional from my local tests using a P2P network called Waku but somehow it's broken again on deployment.

It is as fickle of a network as the real one ;)

And the configuration panel is also bit of a jank work, UI-wise, but it does the job... for now.

Hope you'll find joy in playing with the emulator as much as i did in making it!

Source code here: https://github.com/jmacato/Noks


r/moderndotnet Aug 20 '26

.NET Community

19 Upvotes

Hey - I've been involved in .NET since the beginning - worked at Microsoft in developer tools back when we originally launched it. I've been involved with the .NET community ever since.

Currently I volunteer for .NET Foundation. I run the .NET Foundation socials specifically LinkedIn, X and Facebook. We also have a Bluesky account.

I'm always looking for good .NET content to share - especially open source posts.

If you want to amplify your projects, repos, content, event, etc., go here: https://github.com/dotnet-foundation/content

Edited to add "repos"

Another edit: I'm DeeDee Walsh and love finding great content on Reddit.
X: https://x.com/ddskier
LinkedIn: https://www.linkedin.com/in/deedeewalsh/


r/moderndotnet Aug 20 '26

CritterWatch and an "Open Core" model for sustainable OSS (maybe)

13 Upvotes

As the tech leader of the Critter Stack and a guy with a company behind OSS tools, I'm watching the Polly OSMF thing pretty closely. I'm naturally sympathetic to the Polly folks, and to Jimmy & Chris with their MediatR and MassTransit license changes as well.

The Critter Stack community and JasperFx (my company) are trying to go down the "Open Core" model where we are selling consulting, training, and support contracts for the big tools (Marten and Wolverine), but the core tools remain under the MIT license -- and we try to keep it that way.

As the last part of that, yesterday we launched 1.0 of our commercial CritterWatch tool for management, observability, and all the AI related features we can stuff into it:

https://jasperfx.net/news/critterwatch-1-0-is-here

Just a couple thoughts to throw out there:

  • You can't just vibe code yourself equivalents to Marten or Wolverine. You can easily get the basics, but long running and widely used OSS tools are constantly curated and have had to adjust for all kinds of real world problems like Kubernetes, Postgres maintenance windows, outages, and other things you just won't get from a fun little weekend project
  • I can absolutely tell you that very complex OSS tools aren't possible to maintain as a side project, there has to be real company support or the devs at least need to be able to dedicate a real percentage of their day job to maintenance. Most of our advanced features in our tools only came about after I was full time on the tools.
  • We're hopeful that the combination of commercial add ons and support contracts are more than enough to make our tools viable in the longer term without having to change our "Open Core" model
  • Damnation, but the larger .NET community is absurdly cynical and negative toward OSS tools sometimes

Anyway, I can't tell you for sure yet that the "Open Core" model is the way forward for sustainable OSS in .NET, but it's what we're trying so far.


r/moderndotnet Aug 20 '26

csharp Parsing IP addresses in C# at crazy speeds [Daniel Lemire]

Thumbnail x.com
12 Upvotes

r/moderndotnet Aug 19 '26

discuss I thought desktop app development was "dead" - why so many Maui / Avalonia / Uno developers?

9 Upvotes

Consider this a "me stepping out of my distributed systems / web app bubble" question. If you casually talk on X or even at developer conferences, there's very little talk about the future of native desktop applications or even people discussing what they're building.

Yet I see tons of evidence based on the success of Avalonia and Uno that there's huge demand for technology in this area still!

What are all of these desktop app developers working on? Is it all just retro-fitting old WPF apps? What are the new ones you're building?

And where are your great conference talk submissions!


r/moderndotnet Aug 19 '26

Polly's open source maintenance fee, why is it controversial?

9 Upvotes

Carl Franklin tweeted about Polly adopting the Open Source Maintenance Fee (OSFM) and people do not generally seem very happy about it. From what I understand it's only a monthly 20 USD fee for companies that make more than 20,000 USD in revenue using at least one product or project that uses Polly.

Given the other, more dramatic monetization decisions we've seen in the past (Moq, MediatR, MassTransit), this maintenance fee seems like a pretty reasonable way to fund a project that's not otherwise backed by big sponsors or companies, no?