(bugfix): custom ObjectResult implementation should return camelCase json

This commit is contained in:
Maksym Sadovnychyy 2025-11-02 11:32:42 +01:00
parent 7704163be1
commit f6a7b13326
4 changed files with 180 additions and 145 deletions

View File

@ -14,6 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">

View File

@ -1,137 +1,166 @@
using Xunit;
using System.Net;
using MaksIT.Results;
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);
namespace MaksIT.Results.Tests;
public class ResultTests {
[Fact]
public void Result_Ok_ShouldReturnSuccess() {
// Arrange
var message = "Operation successful";
// Assert
Assert.True(result.IsSuccess);
Assert.Contains(message, result.Messages);
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
// Act
var result = Result.Ok(message);
[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<int>.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<string>.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<int>.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<StatusCodeResult>(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<ObjectResult>(actionResult);
var objectResult = actionResult as ObjectResult;
Assert.NotNull(objectResult);
Assert.Equal((int)HttpStatusCode.BadRequest, objectResult.StatusCode);
Assert.IsType<ProblemDetails>(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<object>.Ok(value);
// Act
var actionResult = result.ToActionResult();
// Assert
Assert.IsType<ObjectResult>(actionResult);
var objectResult = actionResult as ObjectResult;
Assert.NotNull(objectResult);
Assert.Equal((int)HttpStatusCode.OK, objectResult.StatusCode);
Assert.Equal(value, objectResult.Value);
}
// 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<int>.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<string>.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<int>.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<StatusCodeResult>(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<ObjectResult>(actionResult);
var objectResult = actionResult as ObjectResult;
Assert.NotNull(objectResult);
Assert.Equal((int)HttpStatusCode.BadRequest, objectResult.StatusCode);
Assert.IsType<ProblemDetails>(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<object>.Ok(value);
// Act
var actionResult = result.ToActionResult();
// Assert
Assert.IsType<ObjectResult>(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; }
}
}

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata -->
<PackageId>MaksIT.Results</PackageId>
<Version>1.0.9</Version>
<Version>1.1.0</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>MaksIT.Results</Product>

View File

@ -1,29 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
namespace MaksIT.Results.Mvc;
public class ObjectResult : IActionResult {
public object? Value { get; }
public int? StatusCode { get; set; }
public class ObjectResult(object? value) : IActionResult {
private static readonly JsonSerializerOptions _jsonSerializerOptions = new() {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public ObjectResult(object? value) {
Value = value;
}
public object? Value { get; } = value;
public int? StatusCode { get; set; }
public async Task ExecuteResultAsync(ActionContext context) {
var response = context.HttpContext.Response;
if (StatusCode.HasValue) {
response.StatusCode = StatusCode.Value;
}
response.ContentType = "application/json";
if (Value is not null) {
await JsonSerializer.SerializeAsync(response.Body, Value);
await JsonSerializer.SerializeAsync(
response.Body,
Value,
Value?.GetType() ?? typeof(object),
_jsonSerializerOptions
);
}
}
}