Table of Contents

What's new in v3.0

MADE.NET v3.0 targets net8.0 and net10.0, removes all Newtonsoft.Json dependencies in favor of System.Text.Json, and adds new libraries and types across the toolkit.

For breaking changes and upgrade steps, see the migration guide.

New library: MADE.Testing

A test-framework-agnostic assertion library with fluent Should* extension methods. Works with NUnit, xUnit, MSTest, or any other test framework.

result.ShouldNotBeNull();
result.Name.ShouldContain("expected");
result.Count.ShouldBeGreaterThan(0);

action.ShouldThrow<InvalidOperationException>();
await asyncAction.ShouldThrowAsync<ArgumentException>();

Includes assertions for booleans, comparables, strings, objects, collections, and exceptions. See the test assertions guide.

Threading: Debouncer, Throttler, AdaptiveSemaphore, and AsyncLazy

Four new concurrency primitives in MADE.Threading:

Debouncer collapses rapid invocations into a single execution after a quiet period:

var debouncer = new Debouncer { Delay = TimeSpan.FromMilliseconds(300) };
debouncer.Debounce(() => PerformSearch(query));

Throttler limits execution to at most once per interval:

var throttler = new Throttler { Interval = TimeSpan.FromMilliseconds(500) };
throttler.Throttle(() => SubmitForm());

AdaptiveSemaphore dynamically adjusts its concurrency limit at runtime:

var semaphore = new AdaptiveSemaphore(initial: 4, minimum: 1, maximum: 16);
using var permit = await semaphore.WaitAsync();
// Do work

AsyncLazy<T> provides thread-safe lazy initialization for async factories:

var config = new AsyncLazy<Config>(async () => await LoadConfigAsync());
var value = await config;

See the threading guide.

Networking: request factory, multipart uploads, and retry handling

INetworkRequestFactory integrates with IHttpClientFactory for clean request creation:

services.AddNetworkRequestFactory();

// In your service
var profile = await requestFactory.Get("/profile").ExecuteAsync<Profile>();

MultipartFormDataPostNetworkRequest provides fluent file upload support:

var result = await requestFactory.PostMultipart("/upload")
    .AddStreamContent("file", stream, "photo.png", "image/png")
    .AddStringContent("description", "Profile photo")
    .ExecuteAsync<UploadResult>();

RetryDelegatingHandler adds exponential backoff retry for transient HTTP failures:

services.AddHttpClient("Api")
    .AddHttpMessageHandler(() => new RetryDelegatingHandler(maxRetries: 3));

See the HTTP networking guide.

Data converters: new converters and extensions

  • StringToEnumValueConverter<TEnum> converts between strings and enum values with configurable case sensitivity.
  • DateTimeToUnixTimestampValueConverter converts between DateTime and Unix timestamps.
  • FileSizeExtensions formats byte counts as human-readable strings (e.g., "1.50 MB").
  • TimeSpanExtensions provides ToHumanReadableString() and TotalWeeks().

See the data converters guide.

Entity Framework Core: soft-delete and audit tracking

ISoftDeletable adds soft-delete support with automatic query filtering and deletion interception:

public class User : EntityBase, ISoftDeletable
{
    public string Name { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? DeletedDate { get; set; }
}

// In OnModelCreating
modelBuilder.ApplySoftDeleteFilter();

IAuditableEntity tracks who created and last updated each record:

public class Order : EntityBase, IAuditableEntity
{
    public string? CreatedBy { get; set; }
    public string? UpdatedBy { get; set; }
}

See the Entity Framework Core guide.

Data validation: async validators

IAsyncValidator and AsyncValidatorCollection support validation that requires I/O operations:

var validators = new AsyncValidatorCollection
{
    new UniqueEmailValidator(userRepository),
};

await validators.ValidateAsync(emailAddress);

See the data validation guide.

Web MVC: ForbiddenObjectResult

Returns HTTP 403 responses with a body from MVC controllers:

return this.Forbidden(new { message = "Insufficient permissions" });

See the MVC extensions guide.

Code quality improvements

  • All source files use file-scoped namespace declarations.
  • ConfigureAwait(false) added to all await expressions in library code.
  • ArgumentNullException.ThrowIfNull replaces manual null-check patterns.
  • Nullable reference type annotations added across all libraries.
  • FileEventLogger and AppDiagnostics rewritten for proper async patterns.
  • Modern .editorconfig with .NET analysis rules (CA2007, CA1822, CA1849).