DotNetCode, part 1: One codebase, four frameworks — the multi-targeting idea
DotNetCode is my public collection of
sample .NET libraries and Azure Functions — and this article begins a series walking through
it. Before the individual projects, the most educational thing about the repository is a
single line hidden in every .csproj:
<TargetFrameworks>net48;net6.0;net8.0;net10.0</TargetFrameworks>
That is multi-targeting: one codebase compiled four times, once per framework, producing four binaries from the same source. The .NET Framework 4.8 binary runs on old Windows servers that will never see .NET 10; the .NET 10 binary uses the newest runtime. If you write the code within the intersection of what all targets support, everyone can use your library.
What lives in the repository
- Core libraries — ResilientSqlAccess (SQL with retry), CleanValidation (input cleaning), IdentityContext (current-user/request helpers), AppDiagnostics (logging facade).
- Azure Functions relays — email via Microsoft Graph, address validation via SmartyStreets, Shopify CRUD, Dataverse/CRM CRUD — covered in part 4; plus a generic Stripe webhook receiver in part 5.
- MCP servers — Model Context Protocol tools exposed from three different hosts, in part 6.
- Full apps — a React + ASP.NET Core SPA (part 7), a Blazor SignalR realtime app (its own article here), and Terraform infrastructure (part 3).
- The big one — UnifiedShopping, a full-stack e-commerce sample with web, admin and MAUI mobile front ends, in part 8.
Rules that make multi-targeting work
The README states the discipline plainly, and it is worth internalising: everything in a
multi-targeted library must only need dependencies available on every target. When a
dependency forces a split (the MCP SDK needs .NET 8+; isolated-worker Azure Functions are
.NET 10-only), the repository ships separate class libraries per runtime instead of pretending.
Conditional compilation (#if NET48) exists, but the goal is to need it rarely.
Why sample code at all?
Because patterns beat recipes. Copy-paste code rots; understanding a pattern (retry with backoff, graceful parsing, one relay per SaaS API) lets you rebuild it in any language. Every article in this series links its repository at github.com/bobhuang1/DotNetCode and picks out the pattern, not the lines.