Almost nothing on a real .NET project is written from scratch. You reach for a mapper, a validator, a logger, a scheduler, a message bus, and the interesting decisions are which one and how far to let it into your code.
Where two libraries do the same job we have usually compared them head to head instead of picking a favourite. The right answer moves with licence terms, with maintenance, and with what the rest of your stack already uses.
The sections group by the job, not by the package, so a mapper sits next to the other mapper and a message broker next to the other message broker.
Object Mapping
Copying values between a domain type and a DTO, and the argument about whether you should.
- AutoMapper vs Mapster
- IncludeMembers and Custom Projections with AutoMapper
- Getting Started With Mapster
- Ignore Null Values With AutoMapper
- Getting Started with AutoMapper
Validation
Rules that live beside the type instead of inside the controller.
- Deep Dive Into Different Validators with FluentValidation
- CQRS Validation Pipeline with MediatR and FluentValidation
- FluentValidation
MediatR, CQRS and In-Process Messaging
Sending a message to something in the same process, which is a smaller idea than the pattern names around it suggest.
- CQRS and MediatR
- Introduction to the Wolverine Library
- Publish MediatR Notifications in Parallel
- Introduction to Brighter
- How to Solve The type 'TRequest' Cannot be Used as Type Parameter in MediatR
Message Brokers and Distributed Messaging
Sending a message to another process, where delivery, ordering and failure all become your problem.
- Comparison of Rebus, NServiceBus, and MassTransit
- Rebus in .NET – Service Bus Implementation
- Implementing the Saga Pattern Using Rebus and RabbitMQ
- Producer-Consumer Applications With .NET Channels
- Long-Running Tasks in a Microservices Architecture
- Using MassTransit with RabbitMQ
- Using Kafka in an ASP.NET Core Web API
- How to Use RabbitMQ
- Creating Resilient Microservices in .NET with Polly
- Using the API Gateway Pattern In .NET to Encapsulate Microservices
HTTP Client Libraries
Wrappers over HttpClient that trade a little control for a lot less boilerplate.
- Consuming GitHub API (REST) With Flurl
- HttpClient vs RestSharp – Which One to Use
- Using RestSharp To Consume APIs
Dependency Injection Containers
The built-in container covers most cases. These are what you reach for when it does not.
JSON
Json.NET is still everywhere, and these are the jobs System.Text.Json makes you think about.
- How to Create a Custom JsonConverter in Json.NET
- How to Serialize a PascalCase JSON.NET JObject to camelCase
Code Quality and Documentation
Keeping a codebase consistent without arguing about it in every review.
Logging
Structured logging is the one thing here that pays for itself on the first incident.
- Different Log Levels in Serilog
- Best Practices for Logging With Serilog
- ILogger, ILoggerFactory, and ILoggerProvider
- Log4net Appenders Introduction With C# Examples
- Structured Logging in ASP.NET Core With log4net
- Alternative Way of Logging With OpenTelemetry Logging
- Structured Logging in ASP.NET Core With Serilog
- How to Configure Rolling File Logging With Serilog
- How to Properly Create Message Templates for Logging
- How to Log a Class and Method Names Using Serilog
- How to Implement Log4net
- Using Serilog with Microsoft's ILogger API
- How to Write Logs From Startup.cs/Program.cs
- Writing Logs to SQL Server Using NLog
OpenTelemetry and Observability
Traces, metrics and the vendor-neutral plumbing that carries them.
- Health Checks
- Tracing .NET Applications Easily With OpenTelemetry
- Tracking Application Health With OpenTelemetry Metrics
- OpenTelemetry in .NET – Basic Usage With Examples
- Vendor-Agnostic Telemetry Using OpenTelemetry Collector
Data Access Libraries
Talking to a database without an ORM, and the smaller packages that sit beside one.
- Easy Sorting, Filtering and Pagination in .NET With Sieve Package
- Introduction to Firebase
- Using Dapper with ASP.NET Core Web API
- Handling CommandTimeout With Dapper
- How to Implement Lucene.NET
- How to Get a Database Row as JSON Using Dapper
GraphQL
Both ends of it: serving a schema from ASP.NET Core, and consuming one.
- Introduction to Strawberry Shake and GraphQL
- Consuming a GraphQL API with ASP.NET Core
- Multi-Source Data Integration With Strawberry Shake Subscriptions
- Advanced GraphQL Queries, Error Handling, Data Loader
- GraphQL Mutations
- Getting Started with GraphQL
Resilience and Scheduling
Retries, circuit breakers and jobs that need to run at three in the morning whether anyone is watching or not.
- Schedule Jobs With Quartz.NET
- The Differences Between Quartz.NET and Hangfire
- Retrying Failed HttpClient Requests in .NET Using Polly
- Hangfire with ASP.NET Core
- How to Prevent Concurrent Execution of a Hangfire Job
Images, PDFs and Documents
Producing a file a human will open, which is a surprisingly awkward corner of .NET.
- Genearate PDFs With iText in C#/.NET (Formerly iTextSharp)
- Generate Images in C# Using SkiaSharp
- Generate Images in C# Using ImageMagick
- How to Insert Text into an Existing PDF Using the iText Library
- Merging Multiple PDFs Using the iText Library
- Generate Images in C# Using ImageSharp
- Adding Header and Footer to a PDF Using the iText Library
Machine Learning and AI
ML.NET and the local-model experiments, for the times you would rather not call somebody else’s API.
- ML.NET – Introduction to Machine Learning
- What Is Model Builder and How to Use It in ML.NET
- Probabilistic Programming With Infer.NET
- How to Use the Automated Machine Learning API With ML.NET
- Run a Large Language Model(LLM) Like ChatGPT Locally
- Using ML.NET CLI To Automate Model Training
Small Single-Job Packages
One package, one problem, usually solved in an afternoon and then never thought about again.
- Sending Email With FluentEmail
- Extending LINQ to Objects With MoreLINQ Library
- Introduction to the StronglyTypedId Package
- Handling Dates With Noda Time
- Generate Sortable Unique IDs With the NewId Library
Where to Go Next
Where these libraries usually end up being wired in:
Before you let a library spread through the codebase, work out how you would take it out again. The ones worth adopting everywhere are the few whose absence you would notice on the first morning without them.
