|
| 1 | +# Changelog |
| 2 | + |
| 3 | +All notable changes to this project are documented here. |
| 4 | +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## [2.0.0] — 2026-04-01 |
| 9 | + |
| 10 | +### Breaking Changes |
| 11 | + |
| 12 | +- **Target framework** upgraded from `net8.0` to `net10.0`. Consuming projects must target .NET 10. |
| 13 | +- **`Response<T>` removed.** Use `Result<TValue, Error>.ToActionResult()` or `Match` for custom response shapes. |
| 14 | +- **`BaseController` removed.** Remove inheritance; use the `ToActionResult()` extension method directly. |
| 15 | +- **`GlobalErrorHandlerMiddleware` replaced** by `IExceptionHandler`-based `GlobalExceptionHandler`. Register with `AddExceptionHandler<T>()` and `app.UseExceptionHandler()`. |
| 16 | +- **`DbTransactionMiddleware` exception-handler overload removed.** The `Func<IServiceProvider, HttpContext, Exception, Task>` constructor parameter no longer exists. Use `IExceptionHandler` for exception handling. |
| 17 | +- **`PagedRequest.CurrentPage` (1-based) renamed to `PageIndex` (0-based).** Aligns with TanStack Table's server-side pagination contract. `Skip` calculation updated accordingly. |
| 18 | +- **`StringExtensions` deleted.** Methods used `Convert.*` which has inconsistent null vs invalid-input behavior. Use `int.TryParse`, `decimal.TryParse` etc. directly. |
| 19 | +- **`DoubleExtensions` deleted.** `ToInt(this double)` wraps a single `Convert.ToInt32` call with no added value. Use `(int)x` or `Convert.ToInt32(x)` directly. |
| 20 | +- **`ObjectExtensions.ToJson()` removed.** Leaked `Newtonsoft.Json` types into caller APIs. Use `System.Text.Json.JsonSerializer.Serialize(obj)` directly. |
| 21 | +- **`DateTimeExtensions.DateNow()` removed.** Hardcoded UTC+3 offset makes it unsuitable as a general-purpose utility. Use `TimeZoneInfo.ConvertTime()` explicitly. `IsWeekday()`, `IsWeekend()`, and `ToDateOnly()` are retained. |
| 22 | +- **`QueryableExtensions.ToHashSetAsync()` removed.** EF Core 10 provides a native async `ToHashSetAsync()`. |
| 23 | +- **`Newtonsoft.Json` package reference removed.** The library now uses `System.Text.Json` exclusively. |
| 24 | + |
| 25 | +### Added |
| 26 | + |
| 27 | +- **`Result<TValue, TError>`** discriminated union with `Match`, `MatchAsync`, `Handle`, `HandleAsync`, `Map`, `Tap`, `Bind`, and `GetValueOrDefault`. |
| 28 | +- **`Result<TValue>`** shorthand type (equivalent to `Result<TValue, Error>`) for application code that always uses the library's error hierarchy. |
| 29 | +- **Error hierarchy** — `abstract record Error` with concrete subtypes: `NotFoundError`, `AlreadyExistsError`, `ConflictError`, `UnauthorizedError`, `ForbiddenError`. Each carries contextual properties via positional constructor and owns its HTTP representation through virtual `StatusCode`, `Detail`, and `Code` properties. Subclass any type to add domain-specific context or override HTTP semantics without modifying library code. |
| 30 | +- **`Unit`** struct for void operations (`Result<Unit, Error>`). |
| 31 | +- **`ResultExtensions`** — `ToActionResult()`, `ToActionResult(int statusCode)`, `ToActionResultAsync()`, and the full async pipeline (`BindAsync`, `Map`, `Tap` Task overloads). Error-to-HTTP mapping reads `StatusCode`, `Detail`, and `Code` directly from the error instance — no switch expression, extensible without library changes. |
| 32 | +- **`GlobalExceptionHandler`** — default `IExceptionHandler` implementation that logs unhandled exceptions and returns a 500 ProblemDetails response. |
| 33 | +- **`SortColumn`** record — mirrors TanStack Table's `{ id, desc }` sort state shape. |
| 34 | +- **`SortedPagedRequest`** — extends `PagedRequest` with `IReadOnlyList<SortColumn> Sorting`. Multi-column sort applied automatically via expression trees + reflection in `ToPagedAsync`. |
| 35 | +- **`EnumerableExtensions`** — `WhereIf`, `TakeIf`, `SkipIf` for `IEnumerable<T>` (mirrors `QueryableExtensions`). |
| 36 | +- **`ConfigurationExtensions.GetRequired`** — returns a config value or throws `InvalidOperationException` when the key is missing. Fills the gap left by `GetRequiredSection` (sections only). |
| 37 | +- **`HashExtensions.ToMd5`** — hashes a string to a lowercase MD5 hex string using UTF-8. For non-security use (cache keys, checksums, ETags). |
| 38 | +- **`ClaimsPrincipalExtensions`** — `GetId<T>`, `GetRequiredId<T>`, `GetEmail`. Uses `IParsable<T>` constraint so callers choose the ID type (`int`, `Guid`, `long`, etc.) without separate overloads. |
| 39 | +- **`BrotliHelper`** — static helper with `Compress`/`Decompress` overloads for `byte[]` and `string` (UTF-8 + Base64 for string form). |
| 40 | +- **`QueryableExtensions.WhereIf`, `TakeIf`, `SkipIf`** already existed; `ToPagedAsync(SortedPagedRequest)` overload is new. |
| 41 | +- **`ApiCommons.Tests`** project — xUnit + FluentAssertions + coverlet test suite covering all phases. |
| 42 | + |
| 43 | +### Changed |
| 44 | + |
| 45 | +- `DbTransactionMiddleware` updated to primary constructor syntax (C# 13). |
| 46 | +- `QueryableExtensions.ToPagedAsync` verified compatible with EF Core 10. |
| 47 | +- `Microsoft.EntityFrameworkCore` updated from `8.0.22` to `10.0.4`. |
| 48 | + |
| 49 | +### Migration Guide — v1.x → v2.x |
| 50 | + |
| 51 | +| # | What changed | Action required | |
| 52 | +|---|---|---| |
| 53 | +| 1 | `net8.0` no longer targeted | Upgrade consuming projects to .NET 10 | |
| 54 | +| 2 | `BaseController` removed | Remove inheritance; use `ToActionResult()` extension instead | |
| 55 | +| 3 | `Response<T>` removed | Use `Result<TValue, Error>.ToActionResult()` or `Match` for custom shapes | |
| 56 | +| 4 | `GlobalErrorHandlerMiddleware` replaced | Use `AddGlobalExceptionHandler<T>()` + `UseGlobalErrorHandler()`, or register `IExceptionHandler` manually | |
| 57 | +| 5 | `DbTransactionMiddleware` error-handler overload removed | Use `IExceptionHandler` for global exception handling | |
| 58 | +| 6 | `PagedRequest.CurrentPage` (1-based) → `PageIndex` (0-based) | Update all usages; aligns with TanStack Table server-side pagination | |
| 59 | +| 7 | `ObjectExtensions.ToJson()` removed | Use `JsonSerializer.Serialize(obj)` directly | |
| 60 | +| 8 | `DateTimeExtensions.DateNow()` removed | Use `TimeZoneInfo.ConvertTime()` explicitly | |
| 61 | +| 9 | `StringExtensions` removed | Use `int.TryParse`, `decimal.TryParse` etc. directly | |
| 62 | +| 10 | `DoubleExtensions.ToInt()` removed | Use `(int)x` or `Convert.ToInt32(x)` directly | |
| 63 | +| 11 | `QueryableExtensions.ToHashSetAsync()` removed | Use EF Core 10's built-in `.ToHashSetAsync()` | |
| 64 | +| 12 | `Newtonsoft.Json` dependency removed | Use `System.Text.Json` exclusively | |
| 65 | + |
| 66 | +**Before / After:** |
| 67 | + |
| 68 | +```csharp |
| 69 | +// v1 — error handling |
| 70 | +app.UseGlobalErrorHandler(async (sp, ctx, ex) => |
| 71 | +{ |
| 72 | + ctx.Response.StatusCode = 500; |
| 73 | + await ctx.Response.WriteAsync(Response<object>.Fail(...).ToJson()); |
| 74 | +}); |
| 75 | + |
| 76 | +// v2 |
| 77 | +builder.Services.AddGlobalExceptionHandler<MyExceptionHandler>(); |
| 78 | +app.UseGlobalErrorHandler(); |
| 79 | +``` |
| 80 | + |
| 81 | +```csharp |
| 82 | +// v1 — controller response |
| 83 | +Response<string> response = result.Match(s => s, e => string.Empty); |
| 84 | +return response.ToActionResult(); |
| 85 | + |
| 86 | +// v2 |
| 87 | +return await _service.GetAsync(id, ct).ToActionResultAsync(); |
| 88 | +``` |
| 89 | + |
| 90 | +```csharp |
| 91 | +// v1 — pagination (1-based) |
| 92 | +var req = new PagedRequest(currentPage: 1, pageSize: 20); |
| 93 | + |
| 94 | +// v2 — pagination (0-based, aligns with TanStack Table) |
| 95 | +var req = new PagedRequest(pageIndex: 0, pageSize: 20); |
| 96 | +``` |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## [1.1.0] — prior |
| 101 | + |
| 102 | +- Initial release targeting .NET 8. |
0 commit comments