Skip to main content
Article complete

Get one like this every Tuesday at 7 PM IST.

codewithmukesh
Back to blog
dotnet claude 17 min read Lesson 26/30 New

Stop Your AI Agent Picking the Wrong .NET Libraries

AI agents default to AutoMapper, MediatR and MassTransit, all now commercial. Verified 2026 licensing, NuGet MCP Server, and guardrails that fail the build.

AI agents default to AutoMapper, MediatR and MassTransit, all now commercial. Verified 2026 licensing, NuGet MCP Server, and guardrails that fail the build.

dotnet claude

ai coding agents claude code github copilot nuget nuget mcp server package management central package management directory.packages.props nuget audit package source mapping slopsquatting supply chain security dotnet 10 automapper mediatr masstransit polly software licensing dependency management aspnet core

Mukesh Murugan
Mukesh Murugan
Solutions Architect · Microsoft MVP
Chapter 26 of 30
View course

Claude Code for .NET Developers

From dotnet new to docker push - REST, EF Core 10, auth, caching, Clean Architecture, observability. 30 hands-on lessons, source on GitHub.

Your AI coding agent picks .NET libraries from a snapshot of roughly 2023, priced at 2023 licensing terms. It does not know that AutoMapper, MediatR, MassTransit and FluentAssertions all went commercial since then, and it cannot know your company’s revenue, which is what decides whether you owe money. That gap is not something you can prompt your way out of. It belongs in your repo.

This article covers what actually changed in .NET library licensing through 2026 with verified versions and dates, the three distinct ways an agent gets a dependency wrong, and four guardrails you can put in a repository today so the build catches the mistake instead of a reviewer or an auditor. Let’s get into it.

Why does an AI agent suggest the wrong .NET library?

Ask any current model to add object mapping to an ASP.NET Core project and you will very likely get an AutoMapper Profile and an injected IMapper. Ask for CQRS and you get MediatR. Ask for a message bus and you get MassTransit.

None of those are the model being broken. They are the model reproducing the most common answer across a decade of tutorials, Stack Overflow threads and sample repositories, back when those genuinely were the default picks. The license changes are between one and two years old. The corpus those answers came from is much older and much larger.

There is a second problem that no amount of retraining fixes. Most of these libraries now have a free tier gated on your organization’s gross annual revenue. That is a fact about your employer, not about your code. The agent has no access to it and no business guessing.

So the failure is not really “the model is out of date.” It is that library selection has become a licensing and budget decision, and we are handing it to a tool that can only see the code.

The three ways an agent gets a dependency wrong

These are genuinely different problems with different fixes, and lumping them together is why most advice on this stops at “review the code.”

Stale but real: the library exists, the license moved

The package is real, widely used, and exactly what the tutorials said to use. It just costs money now, or carries an advisory that was never patched on the free line. This is the most common case and the easiest to miss in review, because the code looks completely normal.

Wrong by omission: .NET already ships it

The agent installs a package for something the framework has provided since .NET 8 or .NET 10. Rate limiting, health checks, JSON serialization, HTTP resilience and OpenAPI document generation are all in the box now. A dependency added here is pure cost with no benefit, and it is invisible in review because a PackageReference looks like work getting done.

I have written up which libraries are still worth installing and which .NET 10 already replaces, so I will not repeat it here.

Read next

Best Libraries for ASP.NET Core in 2026

Which libraries still earn a dependency, what .NET 10 already gives you for free, and a default starter stack.

Not real at all: hallucinated packages and slopsquatting

This one has hard numbers. A study presented at USENIX Security 2025 tested 16 models across 576,000 generated code samples and found that 19.7% of package references pointed at packages that do not exist. Commercial models did considerably better than open-weight ones, averaging around 5.2% against 21.7%.

The shape of those hallucinations matters more than the headline rate. Roughly 51% were pure fabrications, 38% were conflations where the model welds two real package names together, and 13% were typo variants. The conflations and typos are the dangerous ones, because they look exactly like a package you half remember.

Slopsquatting is the attack built on top of that. The term was coined by security researcher Seth Larson, and the method is simple: hallucinated names repeat predictably across runs, so an attacker watches for a plausible name that does not exist yet and registers it. One researcher published a harmless empty package under a name models kept inventing and recorded more than 30,000 downloads in three months.

Almost everything written about slopsquatting uses npm or PyPI examples, so it is worth being precise about the .NET situation. NuGet’s ID namespace is flat, global and first-come. There is no equivalent to npm’s @scope/ prefix that ties a package name to an owner, so “does the name look official” is a weaker signal on NuGet than developers assume. Prefix reservation exists and gives verified owners a badge, but it does not stop an unreserved plausible-sounding ID from being registered by anyone.

What actually changed in .NET library licensing

Here is the current state, verified against the NuGet registration API on 29 August 2026. If you are reading this much later, re-check before acting on it, because this table is exactly the kind of thing that goes stale quietly.

PackageLast free versionCurrent versionCurrent license
AutoMapper14.0.0 (14 Feb 2025, MIT)16.2.0RPL-1.5 + commercial
MediatR12.5.0 (1 Apr 2025, Apache-2.0)14.2.0RPL-1.5 + commercial
MassTransitv8 line (Apache-2.0)9.2.1Commercial (Massient)
FluentAssertions7.x (Apache-2.0)8.10.0Xceed commercial
PollyStill BSD-3-Clause8.7.0BSD-3, plus a maintenance fee

The details that decide whether this costs you anything:

  • AutoMapper and MediatR are both Lucky Penny Software now, on the same terms. Free Community tier for organizations under $5,000,000 gross annual revenue, non-profits under $5M budget, education, and non-production use. Above that, commercial pricing starts around $489 per year for the Standard tier covering 1-10 developers. The source is on GitHub under RPL-1.5, which is reciprocal and incompatible with most closed-source products.
  • MassTransit v9 shipped in Q1 2026 under a commercial license. Announced pricing was around $400/month for SMBs and $1,200/month for large enterprises, with a 100% discount available under roughly $1M revenue. The Apache-licensed v8 line receives security patches through at least the end of 2026, so “stay on v8” buys you time rather than settles the question.
  • FluentAssertions 8 is free for open source and non-commercial use only. AwesomeAssertions 9.6.0 is a community fork of the v7 line that stays Apache-2.0 and is API-compatible, which makes it a near-zero-cost migration if you need out.
  • AutoMapper 14.0.0, the last MIT release, carries CVE-2026-32933 / GHSA-rvv3-g6hj-g44x, a high severity denial of service rated CVSS 7.5. It was fixed in the commercial 15.1.1 and 16.1.1 and never backported. Restoring it now raises an NU1903 warning.

The Polly trap worth knowing about

Polly adopted the Open Source Maintenance Fee, announced 14 July 2026 and enforced from 16 November 2026. It is $20 per month per organization, for organizations earning at least $20,000 from a product that uses Polly. The license itself does not change and stays BSD-3-Clause. Individuals, hobbyists, students, non-profits and organizations under the threshold owe nothing.

The part that catches people out: the standard advice is “use the first-party Microsoft.Extensions.Http.Resilience package instead of raw Polly.” That package is MIT, but it pulls Polly.Core, Polly.Extensions and Polly.RateLimiting as transitive dependencies. Reaching for the Microsoft package does not remove Polly from your dependency graph. The OSMF announcement does not explicitly address transitive consumers, so I am not going to tell you whether the fee applies in that case. I am telling you that if you assumed the first-party package sidestepped it, that assumption needs checking.

Still free, no change, no action needed: FluentValidation 12.1.1 (Apache-2.0), Serilog 4.4.0 (Apache-2.0), Dapper 2.1.79 (Apache-2.0), Riok.Mapperly 4.3.1 (Apache-2.0), Mapster 10.0.12 (MIT), WolverineFx 6.30.3 (MIT), Rebus 8.9.3 (MIT), xunit.v3 4.0.0 (Apache-2.0), Shouldly 4.3.0 (BSD-3).

One correction I see repeated constantly: FluentAssertions went commercial. FluentValidation did not. Different projects, different maintainers, similar names. Do not let an agent, or a reviewer skimming quickly, conflate them.

The question to ask before any of this: do you need the package at all?

Everything above assumes the dependency is justified and the only question is which one. Most of the time that assumption is wrong, and this is the part I care about more than the licensing table.

An agent adds dependencies with no sense of their cost. Nothing in its loop prices the transitive graph you just inherited, the upgrade tax at the next major version, the CVE surface, or the twenty minutes a new developer spends working out why a mapping happens in a file nobody wrote. A PackageReference line looks like progress. It reads as work getting done. It costs almost nothing to add and a great deal to remove three years later.

The filter I actually apply, in order:

  1. Does the framework already do this? .NET 10 covers far more than most package habits assume. Check first.
  2. Can I write it in under 50 lines I would be happy to own? A mapping extension method, a small retry wrapper, or a dispatcher interface is often less code than the configuration the library needs.
  3. Does it earn its place at the boundary? Libraries that talk to the outside world, meaning serializers, database drivers, message transports, and cryptography, are worth depending on. You do not want to hand-roll those. Libraries that only rearrange your own objects in memory usually are not.
  4. What happens when it goes commercial or unmaintained? This used to be a paranoid question. Four libraries on the table above answered it in eighteen months.

That fourth question is the one 2025 and 2026 changed. Dependency risk in .NET used to mean abandonment. It now also means a license change landing on a library that is already load-bearing in your system, and the migration cost being highest at exactly the moment you have least choice.

Read next

Build Your Own CQRS Dispatcher in .NET 10

What replacing MediatR actually looks like: a dispatcher with pipeline behaviors, in code you own.

Read next

AutoMapper vs Mapster vs Manual Mapping in .NET 10

The same decision worked through end to end for mapping, with benchmarks on the current release of every library.

Guardrail 1: give the agent live package data

The single highest-value fix is removing the guesswork entirely. Microsoft ships a NuGet MCP Server, where MCP (Model Context Protocol) is the open standard agents use to call external tools. It gives an agent live access to package versions and vulnerability data instead of leaving it to recall.

It is built into Visual Studio 2026 behind a toggle in the Copilot Chat tools menu. Everywhere else you configure it yourself. It requires the .NET 10 SDK, which is what provides the dnx command used to fetch and run it.

For VS Code, Visual Studio 2022 17.14 or later, or anything else reading an mcp.json:

{
"servers": {
"nuget": {
"type": "stdio",
"command": "dnx",
"args": [ "NuGet.Mcp.Server", "--source", "https://api.nuget.org/v3/index.json", "--yes" ]
}
}
}

With that wired up, prompts like “fix my package vulnerabilities” or “update all my packages to the latest compatible versions” resolve against the real feed and your actual target framework, rather than against training data. It will not solve the licensing problem, because a license is not vulnerability metadata, but it removes the entire class of “the agent suggested a version that does not exist or was superseded a year ago.”

Read next

Build an MCP Server in C#

How the Model Context Protocol works from the server side, if you want to build one for your own tooling.

Guardrail 2: write the decision down in the repo

If you correct the library choice by hand in each session, you will forget in the session that matters. Put it where every session reads it: CLAUDE.md for Claude Code, AGENTS.md, or .github/copilot-instructions.md for Copilot.

## Dependencies
Do NOT add a NuGet package without asking first. Default answer is no.
Already decided:
- Mapping: Mapperly (Riok.Mapperly). Never AutoMapper.
- CQRS: the hand-rolled dispatcher in src/Shared/Dispatch. Never MediatR.
- Assertions: AwesomeAssertions. Never FluentAssertions v8+.
- Resilience: Microsoft.Extensions.Http.Resilience.
- Validation: FluentValidation (still Apache-2.0, not the same as FluentAssertions).
Before proposing any new package, state: license, latest stable version,
and what in .NET 10 you ruled out first.

That last line does most of the work. Forcing the agent to state the license and the in-box alternative turns an invisible decision into something written in the chat that you can disagree with. This is a far better use of an instruction file than generic style rules the compiler already enforces.

Read next

CLAUDE.md Mastery for .NET Developers

How to structure the instruction file so an agent actually follows it, and what belongs in it versus a skill.

Guardrail 3: make the build enforce it

Instruction files are guidance and guidance gets ignored, by people as much as by agents. The build is the thing that cannot be talked out of its position.

Fail on vulnerable packages

NuGet audit is already on. NuGetAudit defaults to true, and NuGetAuditMode defaults to all for any project targeting net10.0 or higher, so transitive packages are covered without configuration. On older target frameworks it defaults to direct, meaning only your top-level references get checked. What is not automatic is failing the build. Add this to Directory.Build.props:

<Project>
<PropertyGroup>
<WarningsAsErrors>$(WarningsAsErrors);NU1903;NU1904</WarningsAsErrors>
</PropertyGroup>
</Project>

NU1903 is high severity and NU1904 is critical, so low and moderate advisories stay warnings. Keep the $(WarningsAsErrors); prefix, because dropping it replaces whatever the property already held instead of adding to it.

If failing local builds on newly published advisories is too disruptive, the documented pattern is to make it CI-only with an MSBuild condition, so developers see warnings and the pipeline enforces them.

Make adding a package a visible diff

Central Package Management moves every version into one Directory.Packages.props at the repo root. Set it up once:

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Riok.Mapperly" Version="4.3.1" />
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
</ItemGroup>
</Project>

The security value is not the versioning. It is that an agent can no longer add a dependency by quietly editing one .csproj buried in a feature diff. It has to touch a root-level file that reviewers watch, and any new package shows up as a line in a file whose entire purpose is listing what you depend on.

Pin packages to sources

Package Source Mapping is the .NET answer to both dependency confusion and slopsquatting. Declaring which source each package pattern may restore from means a plausible-looking package ID appearing on a public feed cannot satisfy a reference that is mapped to your internal feed.

<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="internal" value="https://pkgs.mycompany.com/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="internal">
<package pattern="MyCompany.*" />
</packageSource>
</packageSourceMapping>
</configuration>

Once packageSourceMapping exists, every package including transitive ones must match a pattern, so restore fails loudly on anything unmapped rather than silently reaching for a public feed.

Two caveats before you lean on this as slopsquatting protection. The * pattern above makes nuget.org a catch-all default, and a package matching only * restores without error - so if you want an unrecognised ID to fail, map your real prefixes explicitly and drop the wildcard. And mappings are skipped entirely when a package is already in the global packages folder, which is why the docs recommend declaring a repo-local one to get the full benefit.

Guardrail 4: read the dependency diff

This is the one step that does not automate, and it is short enough that there is no excuse for skipping it.

When you review agent-written code, read Directory.Packages.props and any .csproj changes first, before the logic. A new package is a decision with a multi-year tail. A wrong if statement is a bug you fix in a minute. They deserve wildly different amounts of review attention, and the natural instinct is to give them the opposite.

Two commands worth knowing when something unexpected shows up:

Terminal window
dotnet nuget why <project> <package> # why is this in my graph at all
dotnet package list --vulnerable --include-transitive

dotnet nuget why answers the question that used to take ten minutes of clicking through Solution Explorer, and it is the fastest way to find out that the harmless-looking package an agent added drags in eleven others.

What I actually do

Not every project needs all four guardrails, and pretending otherwise is how good advice gets ignored.

For a personal project or a prototype, I write the decisions into the instruction file and stop there. The blast radius is me.

For anything with a team or a customer, I add Central Package Management and the two audit warnings as errors. That is maybe fifteen minutes of setup and it covers the two failure modes that actually cost money: a license change nobody noticed, and a known advisory shipping to production.

Package Source Mapping I add only when there is a private feed in play. Without one there is no dependency confusion risk worth the configuration overhead, and adding config you do not need is the same mistake as adding packages you do not need.

The NuGet MCP Server I would turn on everywhere. It has no downside I have found, and it removes an entire category of wrong answers rather than catching them after the fact.

Read next

dotnet-claude-kit: An Opinionated Claude Code Setup for .NET

The agent configuration I use day to day, including how the dependency rules above are wired in.

Read next

Advanced Claude Code Tips for .NET Developers

Sharper workflows once the basics are in place, including review habits that catch this class of problem.

Read next

Plan Mode in Claude Code

Getting the agent to state its plan, including what it intends to install, before it touches the repo.

Read next

20+ Tips from a Senior .NET Developer

More opinionated calls on the small decisions that compound over a codebase's life.

Read next

Swagger Alternatives and OpenAPI in .NET 10

A worked example of the in-box-first principle: what the framework now ships for API documentation.

Troubleshooting the guardrails

Turning these on for the first time in an existing repo is where the friction is. Six things you will probably hit:

NU1008: The following PackageReference items cannot define a value for Version - you enabled Central Package Management but a .csproj still carries a Version attribute. Remove the Version from the PackageReference and add a matching PackageVersion to Directory.Packages.props. Keep other metadata like PrivateAssets and IncludeAssets on the PackageReference, because only the version moves.

NU1507: There are N package sources defined in your configuration - Central Package Management warns when more than one source is configured, because that is the exact ambiguity dependency confusion exploits. Fix it with Package Source Mapping as above, or drop to a single source. This is the two halves of Guardrail 3 pointing at each other.

NU1109 - you enabled transitive pinning and tried to pin a package to a lower version than a dependency asks for. Downgrades are not allowed through pinning, so either upgrade the parent package or pin higher.

An NU1903 you cannot fix - the advisory is real, the build now fails, and no patched version exists. Last resort is NuGetAuditSuppress with the advisory URL, which suppresses it globally rather than for one package, so treat it as a dated decision and not a fix:

<ItemGroup>
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-xxxx-xxxx-xxxx" />
</ItemGroup>

dnx is not recognized - the NuGet MCP Server runs through dnx, which ships with the .NET 10 SDK. Run dotnet --info and check the SDK version before assuming the MCP config is wrong. In a GitHub Actions runner for Copilot coding agent, you also need a setup step installing .NET 10 or dnx will not exist.

Restore suddenly fails on a package you never referenced - once packageSourceMapping exists, every package including transitive ones must match a pattern. Use dotnet nuget why <project> <package> to find which top-level dependency drags it in, then map its prefix.

Key Takeaways

  • An agent’s library defaults reflect roughly 2023, and free tiers now depend on your organization’s revenue, which the agent cannot see. Treat library selection as a licensing decision, not a coding one.
  • AutoMapper (last free 14.0.0), MediatR (last free 12.5.0), MassTransit (v8) and FluentAssertions (v7) all went commercial between 2025 and 2026. Polly stays BSD-3 but adds a $20/month maintenance fee from 16 November 2026.
  • Microsoft.Extensions.Http.Resilience pulls Polly transitively, so choosing the first-party package does not by itself remove Polly from your graph.
  • Roughly 19.7% of AI-generated package references do not exist (USENIX Security 2025, 16 models, 576k samples; ~5.2% for commercial models). Slopsquatting turns that into a supply chain attack, and NuGet’s flat global namespace offers no scoping equivalent to npm’s @org/.
  • The highest-value fix is the NuGet MCP Server, which gives the agent live version and vulnerability data. Requires the .NET 10 SDK for dnx.
  • Back guidance with enforcement: WarningsAsErrors on NU1903/NU1904, Central Package Management so new packages appear in a watched root file, and Package Source Mapping when a private feed exists.
  • Before choosing between libraries, ask whether you need one. .NET 10 ships most of what agents reach for, and a dependency is cheap to add and expensive to remove.

Frequently Asked Questions

Why does my AI coding agent keep suggesting outdated NuGet packages?

Because it is reproducing the most common answer in its training data, and for most of the last decade that answer was AutoMapper, MediatR or MassTransit. Those libraries changed license between 2025 and 2026, which is recent relative to the corpus the model learned from. The fix is not a better prompt. Give the agent live package data through the NuGet MCP Server, and record your library decisions in a repository instruction file so every session inherits them.

Is MediatR still free in 2026?

Only up to version 12.5.0, released 1 April 2025 under Apache-2.0. MediatR 13.0.0 and later, including the current 14.2.0, are dual-licensed under the Reciprocal Public License 1.5 and a Lucky Penny Software commercial license. There is a free Community tier for organizations under 5 million US dollars in gross annual revenue, non-profits under 5 million in budget, education and non-production use. Above that, commercial pricing starts around 489 US dollars per year for the Standard tier covering 1 to 10 developers.

Which .NET libraries went commercial in 2025 and 2026?

AutoMapper and MediatR both moved to a Reciprocal Public License 1.5 plus commercial model under Lucky Penny Software, with AutoMapper 14.0.0 and MediatR 12.5.0 as the last free versions. MassTransit v9 shipped commercially under Massient in Q1 2026, with the Apache-licensed v8 line receiving security patches through at least the end of 2026. FluentAssertions version 8 moved to an Xceed commercial license and is free only for open-source and non-commercial use. Polly did not change license but adopted the Open Source Maintenance Fee starting 16 November 2026.

What is slopsquatting and does it affect NuGet?

Slopsquatting is a supply chain attack where an attacker registers a package name that AI models repeatedly hallucinate, so that developers who paste generated code install malicious code. Research presented at USENIX Security 2025 across 16 models and 576,000 code samples found 19.7% of generated package references pointed at packages that did not exist, with 38% being conflations of two real names and 13% typo variants. It affects NuGet as much as npm or PyPI, and arguably the naming signal is weaker on NuGet because its ID namespace is flat, global and first-come, with no equivalent to npm scopes. Package Source Mapping is the main .NET mitigation.

How do I give Claude Code or Copilot current NuGet version data?

Use the NuGet MCP Server, published by Microsoft as the NuGet.Mcp.Server package. It is built into Visual Studio 2026 behind a toggle in the Copilot Chat tools menu, and configured manually elsewhere through an mcp.json entry that runs it via the dnx command. It requires the .NET 10 SDK, since dnx ships with that SDK. Once connected, the agent resolves package versions and vulnerability data against the live feed and your project's target framework instead of relying on training data.

How do I make my build fail when a package has a known vulnerability?

NuGet audit is already enabled by default, and on projects targeting net10.0 or later it covers transitive packages by default too. To turn the warnings into build failures, add WarningsAsErrors with NU1903 and NU1904 to a Directory.Build.props at your repository root, keeping the existing property value in the expansion so you append rather than replace. NU1903 is high severity and NU1904 is critical, so low and moderate advisories remain warnings. If failing local builds is disruptive, use an MSBuild condition to apply it only in your CI pipeline.

Can I restrict which NuGet packages an AI agent can install?

Not directly through a package allowlist, but you can make it difficult and visible. Central Package Management moves every version into a single Directory.Packages.props at the repository root, so a new dependency cannot be hidden inside a feature diff and must appear in a file reviewers watch. Package Source Mapping restricts which source each package pattern may restore from, so unmapped or unexpected package IDs fail restore. Combined with an instruction file that sets the default answer to no, that covers most of the risk.

Does using Microsoft.Extensions.Http.Resilience avoid the Polly maintenance fee?

It does not remove Polly from your dependency graph. The Microsoft.Extensions.Http.Resilience package is MIT licensed, but it pulls Polly.Core, Polly.Extensions and Polly.RateLimiting as transitive dependencies, so Polly is still present in your build. The Open Source Maintenance Fee announcement does not explicitly address transitive consumers, so whether the fee applies in that situation is not something the published terms answer. If your team assumed the first-party package sidestepped the fee, that assumption is worth verifying rather than relying on.

Wrapping Up

The uncomfortable part of this is that none of it is really about AI. Every one of these guardrails is something a .NET team should have had anyway. Agents did not create the licensing churn, the hallucinated package names or the habit of installing a library for something the framework already does. They increased the volume and the speed, which turned a slow-moving problem into one you notice.

The good news is that the fixes are cheap and they are all one-time. Fifteen minutes of Directory.Build.props and Directory.Packages.props covers the failure modes that cost real money. The NuGet MCP Server takes about two minutes. Writing your library decisions into an instruction file takes as long as it takes to decide them, which you should be doing regardless.

What does not automate is the judgment about whether to add a dependency at all. That one stays yours, and it is worth defending. A package always looks like progress in the moment. The bill arrives later, and by then it is load-bearing.

If you want a five-minute version of this, run dotnet nuget why on the three packages you would be most annoyed to have to remove, then check what license each one is on today rather than what it was on when you added it. If that takes longer than ten minutes, that is the finding. Drop a comment and tell me which one surprised you - I suspect the answers cluster harder than people expect.

Happy Coding :)

View all articles

What's your take?

Push back, share a war story, or ask the obvious question someone else is wondering. I read every comment.

View on GitHub

Weekly .NET tips · free

Newsletter

stay ahead in .NET

One email every Tuesday at 7 PM IST. One topic, deep. The week's articles. No filler.

Tutorials Architecture DevOps AI
Join 9,735 developers · Delivered every Tuesday
Privacy notice 30s read

Cookies, but only the useful ones.

I use cookies to understand which articles get read and which CTAs actually work. No third-party advertising trackers, ever. Read the privacy policy →