diff --git a/CHANGELOG.md b/CHANGELOG.md index 85a4443..bd7b773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,20 @@ -# Changelog +# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v2.0.1 - 2026-03-08 + +### Added +- Tests for `AddJsonOptions` integration: `ObjectResult` respects `JsonSerializerOptions.DefaultIgnoreCondition` (e.g. `WhenWritingNull`) when configured via `AddControllers().AddJsonOptions(...)`. +- Test project reorganized to mirror library structure: `ResultTests`, `ResultToActionResultTests`, and `Mvc/ObjectResultTests`. + +### Fixed +- `ObjectResult` now uses the app-configured JSON options from `IOptions` when serializing response bodies; previously it always used internal defaults. +- `ObjectResult` no longer throws when `HttpContext.RequestServices` is null (e.g. in unit tests without a service provider). + ## v2.0.0 - 2026-02-22 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 268d486..179c11b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,31 +1,30 @@ # Contributing to MaksIT.Results -Thank you for your interest in contributing to `MaksIT.Results`. +Thank you for your interest in contributing to MaksIT.Results! This document provides guidelines for contributing to the project. ## Getting Started -1. Fork the repository. -2. Clone your fork locally. -3. Create a feature branch. -4. Implement and test your changes. -5. Submit a pull request to `main`. +1. Fork the repository +2. Clone your fork locally +3. Create a new branch for your changes +4. Make your changes +5. Submit a pull request ## Development Setup ### Prerequisites -- .NET 8 SDK or later +- .NET 10 SDK or later - Git -- PowerShell 7+ (recommended for utility scripts) -### Build +### Building the Project ```bash cd src -dotnet build MaksIT.Results.sln +dotnet build MaksIT.Results.slnx ``` -### Test +### Running Tests ```bash cd src @@ -34,9 +33,9 @@ dotnet test MaksIT.Results.Tests ## Commit Message Format -Use: +This project uses the following commit message format: -```text +``` (type): description ``` @@ -46,71 +45,176 @@ Use: |------|-------------| | `(feature):` | New feature or enhancement | | `(bugfix):` | Bug fix | -| `(refactor):` | Refactoring without behavior change | -| `(chore):` | Maintenance tasks (dependencies, tooling, docs) | +| `(refactor):` | Code refactoring without functional changes | +| `(perf):` | Performance improvement without changing behavior | +| `(test):` | Add or update tests | +| `(docs):` | Documentation-only changes | +| `(build):` | Build system, dependencies, packaging, or project file changes | +| `(ci):` | CI/CD pipeline or automation changes | +| `(style):` | Formatting or non-functional code style changes | +| `(revert):` | Revert a previous commit | +| `(chore):` | General maintenance tasks that do not fit the types above | + +### Examples + +``` +(feature): add support for custom json options in object result +(bugfix): fix objectresult using app json options when request services null +(refactor): simplify result to action result conversion +(perf): reduce allocations in problem details serialization +(test): add coverage for addjsonoptions whenwritingnull +(docs): clarify json options in readme +(build): update package metadata in MaksIT.Results.csproj +(ci): update GitHub Actions workflow for .NET 10 +(style): normalize using directives in mvc tests +(revert): revert breaking change in toactionresult behavior +(chore): update copyright year to 2026 +``` ### Guidelines -- Use lowercase in the description. -- Keep it concise and specific. -- Do not end with a period. +- Use lowercase for the description +- Keep the description concise but descriptive +- No period at the end of the description -## Pull Request Checklist +## Code Style -1. Ensure build and tests pass. -2. Update `README.md` if behavior or usage changed. -3. Update `CHANGELOG.md` under the target version. -4. Keep changes scoped and explain rationale in the PR description. +- Follow standard C# naming conventions +- Use XML documentation comments for public APIs +- Keep methods focused and single-purpose +- Write unit tests for new functionality + +## Pull Request Process + +1. Ensure all tests pass +2. Update documentation if needed +3. Update CHANGELOG.md with your changes under the appropriate version section +4. Submit your pull request against the `main` branch ## Versioning This project follows [Semantic Versioning](https://semver.org/): -- **MAJOR**: breaking API changes -- **MINOR**: backward-compatible features -- **PATCH**: backward-compatible fixes +- **MAJOR** - Breaking changes +- **MINOR** - New features (backward compatible) +- **PATCH** - Bug fixes (backward compatible) -## Utility Scripts +## Release Process -Scripts are located under `utils/`. +The release process is automated via PowerShell scripts in the `utils/` directory. -### Generate Coverage Badges +### Prerequisites + +- Docker Desktop running (for Linux tests) +- GitHub CLI (`gh`) installed +- Environment variables configured: + - `NUGET_MAKS_IT` - NuGet.org API key + - `GITHUB_MAKS_IT_COM` - GitHub Personal Access Token (needs `repo` scope) + +### Release Scripts Overview + +| Script | Purpose | +|--------|---------| +| `Generate-CoverageBadges.ps1` | Runs tests with coverage and generates SVG badges in `assets/badges/` | +| `Release-Package.ps1` | Build, test, and publish to NuGet.org and GitHub | +| `Force-AmendTaggedCommit.ps1` | Fix mistakes in tagged commits | + +### Release Workflow + +1. **Update version** in `MaksIT.Results/MaksIT.Results.csproj` + +2. **Update CHANGELOG.md** with your changes under the target version + +3. **Review and commit** all changes: + ```bash + git add -A + git commit -m "(chore): release v2.x.x" + ``` + +4. **Create version tag**: + ```bash + git tag v2.x.x + ``` + +5. **Run release script**: + ```powershell + .\utils\Release-Package\Release-Package.ps1 # Full release + .\utils\Release-Package\Release-Package.ps1 -DryRun # Test without publishing + ``` + +--- + +### Generate-CoverageBadges.ps1 Runs tests with coverage and generates SVG badges in `assets/badges/`. +**Usage:** ```powershell .\utils\Generate-CoverageBadges\Generate-CoverageBadges.ps1 ``` -Configuration: `utils/Generate-CoverageBadges/scriptsettings.json` +**Configuration:** `utils/Generate-CoverageBadges/scriptsettings.json` -### Release NuGet Package +--- -Builds, tests, packs, and publishes to NuGet and GitHub release flows. +### Release-Package.ps1 +Builds, tests, packs, and publishes the package to NuGet.org and GitHub. + +**What it does:** +1. Validates prerequisites and environment +2. Builds and tests the project +3. Creates NuGet package (.nupkg and .snupkg) +4. Pushes to NuGet.org +5. Creates GitHub release with assets + +**Usage:** ```powershell -.\utils\Release-NuGetPackage\Release-NuGetPackage.ps1 +.\utils\Release-Package\Release-Package.ps1 # Full release +.\utils\Release-Package\Release-Package.ps1 -DryRun # Test without publishing ``` -Prerequisites: +**Configuration:** `utils/Release-Package/scriptsettings.json` -- Docker Desktop (for Linux test validation) -- GitHub CLI (`gh`) -- environment variable `NUGET_MAKS_IT` -- environment variable `GITHUB_MAKS_IT_COM` +--- -Configuration: `utils/Release-NuGetPackage/scriptsettings.json` +### Force-AmendTaggedCommit.ps1 -### Force Amend Tagged Commit +Fixes mistakes in the last tagged commit by amending it and force-pushing. -Amends the latest tagged commit and force-pushes updated branch and tag. +**When to use:** +- You noticed an error after committing and tagging +- Need to add forgotten files to the release commit +- Need to fix a typo in the release +**What it does:** +1. Verifies the last commit has an associated tag +2. Stages all pending changes +3. Amends the latest commit (keeps existing message) +4. Deletes and recreates the tag on the amended commit +5. Force pushes the branch and tag to origin + +**Usage:** ```powershell -.\utils\Force-AmendTaggedCommit\Force-AmendTaggedCommit.ps1 -.\utils\Force-AmendTaggedCommit\Force-AmendTaggedCommit.ps1 -DryRun +.\utils\Force-AmendTaggedCommit\Force-AmendTaggedCommit.ps1 # Amend and force push +.\utils\Force-AmendTaggedCommit\Force-AmendTaggedCommit.ps1 -DryRun # Preview without changes ``` -Warning: this rewrites git history. +**Warning:** This rewrites history. Only use on commits that haven't been pulled by others. + +--- + +### Fixing a Failed Release + +If the release partially failed (e.g., NuGet succeeded but GitHub failed): + +1. **Re-run the release script** if it supports skipping already-completed steps +2. **If you need to fix the commit content:** + ```powershell + # Make your fixes, then: + .\utils\Force-AmendTaggedCommit\Force-AmendTaggedCommit.ps1 + .\utils\Release-Package\Release-Package.ps1 + ``` ## License diff --git a/README.md b/README.md index 91b1416..40cfeaa 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Static factory methods for common and extended HTTP status codes (1xx, 2xx, 3xx, 4xx, 5xx). - Built-in conversion to `IActionResult` via `ToActionResult()`. - RFC 7807-style error payloads for failures (`application/problem+json`). -- Camel-case JSON serialization for response bodies. +- Camel-case JSON serialization for response bodies; respects app-configured `JsonSerializerOptions` (e.g. `AddJsonOptions` with `DefaultIgnoreCondition.WhenWritingNull`). ## Installation @@ -88,6 +88,19 @@ public sealed record UserDto(Guid Id, string Name); - `detail` = joined `Messages` - content type `application/problem+json` +## JSON options + +`ObjectResult` uses the same `JsonSerializerOptions` as your app when you configure them with `AddJsonOptions`: + +```csharp +builder.Services.AddControllers() + .AddJsonOptions(options => { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + }); +``` + +If no options are registered, a default (camel-case) serializer is used. + ## Status Code Factories - Informational: `Result.Continue(...)`, `Result.SwitchingProtocols(...)`, `Result.Processing(...)`, etc. diff --git a/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj b/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj index 17768d2..6fabfa6 100644 --- a/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj +++ b/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -15,7 +15,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MaksIT.Results.Tests/Mvc/ObjectResultTests.cs b/src/MaksIT.Results.Tests/Mvc/ObjectResultTests.cs new file mode 100644 index 0000000..d162eb5 --- /dev/null +++ b/src/MaksIT.Results.Tests/Mvc/ObjectResultTests.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MaksIT.Results.Mvc; + +namespace MaksIT.Results.Tests.Mvc; + +public class ObjectResultTests { + [Fact] + public async Task ExecuteResultAsync_SerializesToCamelCaseJson() { + var testObject = new TestPascalCase { FirstName = "John", LastName = "Doe" }; + var objectResult = new ObjectResult(testObject); + var context = new DefaultHttpContext(); + var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + var actionContext = new ActionContext { HttpContext = context }; + + await objectResult.ExecuteResultAsync(actionContext); + + memoryStream.Seek(0, SeekOrigin.Begin); + var json = await new StreamReader(memoryStream).ReadToEndAsync(TestContext.Current.CancellationToken); + Assert.Contains("\"firstName\"", json); + Assert.Contains("\"lastName\"", json); + Assert.DoesNotContain("\"FirstName\"", json); + Assert.DoesNotContain("\"LastName\"", json); + } + + [Fact] + public async Task ExecuteResultAsync_WhenJsonOptionsWhenWritingNull_OmitsNullProperties() { + var services = new ServiceCollection(); + services.AddOptions().Configure(o => { + o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }); + var serviceProvider = services.BuildServiceProvider(); + + var testObject = new TestWithNulls { Id = 1, Name = "Test", Optional = null }; + var objectResult = new ObjectResult(testObject); + var context = new DefaultHttpContext { RequestServices = serviceProvider }; + var memoryStream = new MemoryStream(); + context.Response.Body = memoryStream; + var actionContext = new ActionContext { HttpContext = context }; + + await objectResult.ExecuteResultAsync(actionContext); + + memoryStream.Seek(0, SeekOrigin.Begin); + var json = await new StreamReader(memoryStream).ReadToEndAsync(TestContext.Current.CancellationToken); + Assert.Contains("\"id\"", json); + Assert.Contains("\"name\"", json); + Assert.Contains("\"Test\"", json); + Assert.DoesNotContain("\"optional\"", json); + } + + private class TestPascalCase { + public required string FirstName { get; set; } + public required string LastName { get; set; } + } + + private class TestWithNulls { + public int Id { get; set; } + public string Name { get; set; } = ""; + public string? Optional { get; set; } + } +} diff --git a/src/MaksIT.Results.Tests/ResultTests.cs b/src/MaksIT.Results.Tests/ResultTests.cs new file mode 100644 index 0000000..67a524d --- /dev/null +++ b/src/MaksIT.Results.Tests/ResultTests.cs @@ -0,0 +1,65 @@ +using System.Net; + +namespace MaksIT.Results.Tests; + +public class ResultTests { + [Fact] + public void Ok_ShouldReturnSuccess() { + var message = "Operation successful"; + + var result = Result.Ok(message); + + Assert.True(result.IsSuccess); + Assert.Contains(message, result.Messages); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + } + + [Fact] + public void BadRequest_ShouldReturnFailure() { + var message = "Invalid request"; + + var result = Result.BadRequest(message); + + Assert.False(result.IsSuccess); + Assert.Contains(message, result.Messages); + Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); + } + + [Fact] + public void Generic_Ok_ShouldReturnSuccessWithValue() { + var value = 42; + var message = "Operation successful"; + + var result = Result.Ok(value, message); + + Assert.True(result.IsSuccess); + Assert.Equal(value, result.Value); + Assert.Contains(message, result.Messages); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + } + + [Fact] + public void Generic_NotFound_ShouldReturnFailureWithNullValue() { + var message = "Resource not found"; + + var result = Result.NotFound(null, message); + + Assert.False(result.IsSuccess); + Assert.Null(result.Value); + Assert.Contains(message, result.Messages); + Assert.Equal(HttpStatusCode.NotFound, result.StatusCode); + } + + [Fact] + public void ToResultOfType_ShouldTransformValue() { + var initialValue = 42; + var transformedValue = "42"; + var result = Result.Ok(initialValue); + + var transformedResult = result.ToResultOfType(value => value.ToString()); + + Assert.True(transformedResult.IsSuccess); + Assert.Equal(transformedValue, transformedResult.Value); + Assert.Equal(result.StatusCode, transformedResult.StatusCode); + } +} diff --git a/src/MaksIT.Results.Tests/ResultToActionResultTests.cs b/src/MaksIT.Results.Tests/ResultToActionResultTests.cs new file mode 100644 index 0000000..266d323 --- /dev/null +++ b/src/MaksIT.Results.Tests/ResultToActionResultTests.cs @@ -0,0 +1,48 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using MaksIT.Results.Mvc; + +namespace MaksIT.Results.Tests; + +public class ResultToActionResultTests { + [Fact] + public void ToActionResult_WhenSuccess_ReturnsStatusCodeResult() { + var result = Result.Ok("Operation successful"); + + var actionResult = result.ToActionResult(); + + Assert.IsType(actionResult); + var statusCodeResult = (StatusCodeResult)actionResult; + Assert.Equal((int)HttpStatusCode.OK, statusCodeResult.StatusCode); + } + + [Fact] + public void ToActionResult_WhenFailure_ReturnsObjectResultWithProblemDetails() { + var errorMessage = "An error occurred"; + var result = Result.BadRequest(errorMessage); + + var actionResult = result.ToActionResult(); + + Assert.IsType(actionResult); + var objectResult = (ObjectResult)actionResult; + Assert.Equal((int)HttpStatusCode.BadRequest, objectResult.StatusCode); + Assert.IsType(objectResult.Value); + var problemDetails = (ProblemDetails)objectResult.Value!; + Assert.Equal((int)HttpStatusCode.BadRequest, problemDetails.Status); + Assert.Equal("An error occurred", problemDetails.Title); + Assert.Equal(errorMessage, problemDetails.Detail); + } + + [Fact] + public void ToActionResult_WhenGenericSuccessWithValue_ReturnsObjectResultWithValue() { + var value = new { Id = 1, Name = "Test" }; + var result = Result.Ok(value); + + var actionResult = result.ToActionResult(); + + Assert.IsType(actionResult); + var objectResult = (ObjectResult)actionResult; + Assert.Equal((int)HttpStatusCode.OK, objectResult.StatusCode); + Assert.Equal(value, objectResult.Value); + } +} diff --git a/src/MaksIT.Results.Tests/UnitTest1.cs b/src/MaksIT.Results.Tests/UnitTest1.cs deleted file mode 100644 index eb00ce3..0000000 --- a/src/MaksIT.Results.Tests/UnitTest1.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Net; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Http; -using MaksIT.Results.Mvc; - - -namespace MaksIT.Results.Tests; -public class ResultTests { - [Fact] - public void Result_Ok_ShouldReturnSuccess() { - // Arrange - var message = "Operation successful"; - - // Act - var result = Result.Ok(message); - - // Assert - Assert.True(result.IsSuccess); - Assert.Contains(message, result.Messages); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); - } - - [Fact] - public void Result_BadRequest_ShouldReturnFailure() { - // Arrange - var message = "Invalid request"; - - // Act - var result = Result.BadRequest(message); - - // Assert - Assert.False(result.IsSuccess); - Assert.Contains(message, result.Messages); - Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); - } - - [Fact] - public void Result_Generic_Ok_ShouldReturnSuccessWithValue() { - // Arrange - var value = 42; - var message = "Operation successful"; - - // Act - var result = Result.Ok(value, message); - - // Assert - Assert.True(result.IsSuccess); - Assert.Equal(value, result.Value); - Assert.Contains(message, result.Messages); - Assert.Equal(HttpStatusCode.OK, result.StatusCode); - } - - [Fact] - public void Result_Generic_NotFound_ShouldReturnFailureWithNullValue() { - // Arrange - var message = "Resource not found"; - - // Act - var result = Result.NotFound(null, message); - - // Assert - Assert.False(result.IsSuccess); - Assert.Null(result.Value); - Assert.Contains(message, result.Messages); - Assert.Equal(HttpStatusCode.NotFound, result.StatusCode); - } - - [Fact] - public void Result_ToResultOfType_ShouldTransformValue() { - // Arrange - var initialValue = 42; - var transformedValue = "42"; - var result = Result.Ok(initialValue); - - // Act - var transformedResult = result.ToResultOfType(value => value.ToString()); - - // Assert - Assert.True(transformedResult.IsSuccess); - Assert.Equal(transformedValue, transformedResult.Value); - Assert.Equal(result.StatusCode, transformedResult.StatusCode); - } - - [Fact] - public void Result_ToActionResult_ShouldReturnStatusCodeResult() { - // Arrange - var result = Result.Ok("Operation successful"); - - // Act - var actionResult = result.ToActionResult(); - - // Assert - Assert.IsType(actionResult); - var statusCodeResult = actionResult as StatusCodeResult; - Assert.NotNull(statusCodeResult); - Assert.Equal((int)HttpStatusCode.OK, statusCodeResult.StatusCode); - } - - [Fact] - public void Result_ToActionResult_ShouldReturnObjectResultForFailure() { - // Arrange - var errorMessage = "An error occurred"; - var result = Result.BadRequest(errorMessage); - - // Act - var actionResult = result.ToActionResult(); - - // Assert - Assert.IsType(actionResult); - var objectResult = actionResult as ObjectResult; - Assert.NotNull(objectResult); - Assert.Equal((int)HttpStatusCode.BadRequest, objectResult.StatusCode); - Assert.IsType(objectResult.Value); - var problemDetails = objectResult.Value as ProblemDetails; - Assert.NotNull(problemDetails); - Assert.Equal((int)HttpStatusCode.BadRequest, problemDetails.Status); - Assert.Equal("An error occurred", problemDetails.Title); - Assert.Equal(errorMessage, problemDetails.Detail); - } - - [Fact] - public void Result_Generic_ToActionResult_ShouldReturnObjectResultWithValue() { - // Arrange - var value = new { Id = 1, Name = "Test" }; - var result = Result.Ok(value); - - // Act - var actionResult = result.ToActionResult(); - - // Assert - Assert.IsType(actionResult); - var objectResult = actionResult as ObjectResult; - Assert.NotNull(objectResult); - Assert.Equal((int)HttpStatusCode.OK, objectResult.StatusCode); - Assert.Equal(value, objectResult.Value); - } - - [Fact] - public async Task ObjectResult_ShouldSerializeToCamelCaseJson() { - // Arrange - var testObject = new TestPascalCase { FirstName = "John", LastName = "Doe" }; - var objectResult = new ObjectResult(testObject); - var context = new DefaultHttpContext(); - var memoryStream = new MemoryStream(); - context.Response.Body = memoryStream; - var actionContext = new ActionContext { - HttpContext = context - }; - - // Act - await objectResult.ExecuteResultAsync(actionContext); - - // Assert - memoryStream.Seek(0, SeekOrigin.Begin); - var json = await new StreamReader(memoryStream).ReadToEndAsync(); - Assert.Contains("\"firstName\"", json); - Assert.Contains("\"lastName\"", json); - Assert.DoesNotContain("\"FirstName\"", json); - Assert.DoesNotContain("\"LastName\"", json); - } - - private class TestPascalCase { - public string FirstName { get; set; } - public string LastName { get; set; } - } -} diff --git a/src/MaksIT.Results/MaksIT.Results.csproj b/src/MaksIT.Results/MaksIT.Results.csproj index d9d01b6..e12491f 100644 --- a/src/MaksIT.Results/MaksIT.Results.csproj +++ b/src/MaksIT.Results/MaksIT.Results.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -8,7 +8,7 @@ MaksIT.Results - 2.0.0 + 2.0.1 Maksym Sadovnychyy MAKS-IT MaksIT.Results @@ -23,11 +23,13 @@ + + - + diff --git a/src/MaksIT.Results/Mvc/JsonOptions.cs b/src/MaksIT.Results/Mvc/JsonOptions.cs new file mode 100644 index 0000000..462d11a --- /dev/null +++ b/src/MaksIT.Results/Mvc/JsonOptions.cs @@ -0,0 +1,36 @@ + +using System.Text.Json; + +namespace MaksIT.Results.Mvc; + +// +// Summary: +// Options to configure Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter +// and Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter. +public class JsonOptions { + public JsonOptions() { + JsonSerializerOptions = new JsonSerializerOptions(); + } + + // + // Summary: + // Gets or sets a flag to determine whether error messages from JSON deserialization + // by the Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter will + // be added to the Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary. If + // false, a generic error message will be used instead. + // + // Value: + // The default value is true. + // + // Remarks: + // Error messages in the Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary + // are often communicated to clients, either in HTML or using Microsoft.AspNetCore.Mvc.BadRequestObjectResult. + // In effect, this setting controls whether clients can receive detailed error messages + // about submitted JSON data. + public bool AllowInputFormatterExceptionMessages { get; set; } + // + // Summary: + // Gets the System.Text.Json.JsonSerializerOptions used by Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter + // and Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter. + public JsonSerializerOptions JsonSerializerOptions { get; } +} \ No newline at end of file diff --git a/src/MaksIT.Results/Mvc/ObjectResult.cs b/src/MaksIT.Results/Mvc/ObjectResult.cs index f320eaf..16d3cbd 100644 --- a/src/MaksIT.Results/Mvc/ObjectResult.cs +++ b/src/MaksIT.Results/Mvc/ObjectResult.cs @@ -1,5 +1,7 @@ -using System.Text.Json; +using System.Text.Json; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace MaksIT.Results.Mvc; @@ -14,6 +16,10 @@ public class ObjectResult(object? value) : IActionResult { public async Task ExecuteResultAsync(ActionContext context) { var response = context.HttpContext.Response; + // Prefer app-configured JSON options (from AddJsonOptions), fall back to default + var jsonOptions = context.HttpContext.RequestServices?.GetService>()?.Value?.JsonSerializerOptions + ?? _jsonSerializerOptions; + if (StatusCode.HasValue) { response.StatusCode = StatusCode.Value; } @@ -31,7 +37,7 @@ public class ObjectResult(object? value) : IActionResult { response.Body, Value, Value?.GetType() ?? typeof(object), - _jsonSerializerOptions + jsonOptions ); } }