Megaraz.ResultPattern.AspNetCore
0.1.3
dotnet add package Megaraz.ResultPattern.AspNetCore --version 0.1.3
NuGet\Install-Package Megaraz.ResultPattern.AspNetCore -Version 0.1.3
<PackageReference Include="Megaraz.ResultPattern.AspNetCore" Version="0.1.3" />
<PackageVersion Include="Megaraz.ResultPattern.AspNetCore" Version="0.1.3" />
<PackageReference Include="Megaraz.ResultPattern.AspNetCore" />
paket add Megaraz.ResultPattern.AspNetCore --version 0.1.3
#r "nuget: Megaraz.ResultPattern.AspNetCore, 0.1.3"
#:package Megaraz.ResultPattern.AspNetCore@0.1.3
#addin nuget:?package=Megaraz.ResultPattern.AspNetCore&version=0.1.3
#tool nuget:?package=Megaraz.ResultPattern.AspNetCore&version=0.1.3
Megaraz.ResultPattern.AspNetCore
HTTP and ASP.NET Core extensions for
Megaraz.ResultPattern.
The package maps core results to predictable HTTP response descriptors and
provides optional MVC and minimal-API adapters.
The framework-agnostic mapper does not create endpoints, choose application messages, or impose a remote service's error schema. Applications own routing, authentication, localization, and any public API contract that differs from the documented defaults.
Supported frameworks
- .NET 8
- .NET 9
- .NET 10
Installation
dotnet add package Megaraz.ResultPattern.AspNetCore
Mapping outbound results
Use the pure mapper when the application needs to control how the response is written:
var response = HttpResultMapper.ToHttpResponse(result);
return Results.Json(
response.Body,
statusCode: response.StatusCode,
headers: response.Location is null
? null
: new HeaderDictionary { ["Location"] = response.Location });
Successful typed results use 200 OK and place the value in the body.
Successful non-generic results use 200 OK without a body. Created results use
201 Created and preserve an optional Location header. Commands can use
ToNoContentResponse for 204 No Content.
Minimal APIs
app.MapGet("/accounts/{id}", (Guid id) =>
{
Result<Account> result = service.Get(id);
return result.ToHttpResult();
});
app.MapPost("/accounts", (CreateAccount request) =>
{
Result<Account> result = service.Create(request);
return result.ToCreatedHttpResult($"/accounts/{result.Value.Id}");
});
Use ToNoContentHttpResult for successful commands. These adapters write the
status, body, and Location header while preserving the default failure body.
MVC controllers
[HttpGet("{id}")]
public ActionResult<Account> Get(Guid id)
{
Result<Account> result = service.Get(id);
return this.ToActionResult(result);
}
[HttpPost]
public ActionResult<Account> Create(CreateAccount request)
{
Result<Account> result = service.Create(request);
return this.ToCreatedResult(result, $"/accounts/{result.Value.Id}");
}
When an MVC controller has a route for the created resource, use the
route-aware overload to produce a CreatedAtActionResult:
return this.ToCreatedResult(
result,
nameof(GetAccountById),
account => new { id = account.Id });
Default response contract
Failure responses use immutable DTOs with camel-case JSON names. Codes are machine-readable public API identifiers; applications should version them deliberately and avoid changing a code's meaning after publishing.
Non-validation failure:
{
"message": "Account was not found.",
"code": "account.not_found"
}
Validation failure:
{
"message": "One or more validation errors occurred.",
"code": "validation.failed",
"validationErrors": [
{
"field": "email",
"message": "Email is required."
},
{
"field": null,
"message": "The request is invalid."
}
]
}
validationErrors is a materialized list and is omitted for other failures.
MappedHttpResponse.Body remains object? because successful values and custom
failure factories may use application-defined DTOs.
Customizing mappings
Start with HttpResultMappingPolicy.Default and override only application
specific rules:
var policy = HttpResultMappingPolicy.Default with
{
SuccessStatusCode = StatusCodes.Status202Accepted,
ErrorTypeStatusCode = errorType => errorType switch
{
ErrorType.Validation => StatusCodes.Status400BadRequest,
_ => HttpResultMappingPolicy.Default.ErrorTypeStatusCode(errorType)
},
FailureBodyFactory = result =>
new { error = result.Message, code = result.PrimaryError.Code }
};
return result.ToHttpResult(policy);
The policy supports separate selectors for core ErrorType and
HttpErrorType, success statuses for ordinary, no-content, and created
responses, and a failure-body factory. Supplying a custom failure factory makes
the application responsible for that response contract.
Mapping inbound HTTP responses
HttpResponseMessage can be converted to a typed or non-generic result:
var result = await response.MapToResultAsync<Account>(context);
Successful JSON bodies are deserialized with web-default
JsonSerializerOptions. Non-success responses become HttpError values.
HttpError retains the core package's ErrorType.External classification and
adds HTTP-specific categories such as TransportFailure,
MalformedResponse, and UnexpectedStatusCode.
Response content is consumed. Both the default reader and streaming deserializer
read at most 64 KiB by default; configure
HttpResponseMappingOptions.MaxResponseBodyBytes for another limit. Use
MapToResultWithDeserializerAsync when a successful payload should be streamed
without buffering. Cancellation is propagated and is never converted to a failure.
Upstream response text is kept in the technical Error.Description; it is not
copied to Error.UserMessage by default. Applications that intentionally proxy
a vetted upstream message can opt in:
var options = new HttpResponseMappingOptions
{
UserMessageFactory = message => IsSafeForClients(message) ? message : null
};
var result = await response.MapToResultAsync<Account>(context, options);
Pass JsonSerializerOptions to control successful-body deserialization and
ErrorMessageExtractor when an upstream service uses a different error schema.
HttpClient request, DNS, connection, and timeout exceptions are not caught by
the response mapper; map selected exceptions explicitly in the surrounding
catch block with MapTransportExceptionToResult.
Pagination
Normalize endpoint query values with an application-configured maximum:
var pagination = PaginationParameters.Normalize(page, pageSize, maxPageSize);
Page numbers are at least 1 and page sizes are clamped between 1 and the
provided maximum. A maximum below 1 throws ArgumentOutOfRangeException.
Versioning
The package follows semantic versioning. Public response fields, error codes, and documented mapping defaults are compatibility-sensitive API. Breaking changes require a major version; new backward-compatible APIs and behavior use minor versions. Patch releases contain backward-compatible fixes.
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Megaraz.ResultPattern (>= 0.2.2)
-
net8.0
- Megaraz.ResultPattern (>= 0.2.2)
-
net9.0
- Megaraz.ResultPattern (>= 0.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
0.1.3: Hardens the release supply chain with CodeQL, dependency review, NuGet Dependabot updates, secret scanning push protection, and protected release checks.