MaksIT.Results is a powerful library designed to streamline the creation and management of result objects in your ASP.NET Core applications.
Go to file
2026-10-28 19:07:37 +01:00
.github/workflows (build): bump to 2.0.6; fix NuGet publish actions and login user 2026-10-27 20:01:27 +01:00
src (feature): add FromActionResult and FromHttpResponseAsync; preserve redirect status 2026-10-28 19:07:37 +01:00
utils (feature): add FromActionResult and FromHttpResponseAsync; preserve redirect status 2026-10-28 19:07:37 +01:00
.editorconfig (feature): add redirect handling in Result<T>.ToActionResult 2026-07-28 22:27:22 +02:00
.gitattributes (feature): init 2024-08-25 04:05:09 +02:00
.gitignore (feature): add redirect handling in Result<T>.ToActionResult 2026-07-28 22:27:22 +02:00
CHANGELOG.md (feature): add FromActionResult and FromHttpResponseAsync; preserve redirect status 2026-10-28 19:07:37 +01:00
CONTRIBUTING.md (feature): add FromActionResult and FromHttpResponseAsync; preserve redirect status 2026-10-28 19:07:37 +01:00
LICENSE.md (chore): migrate to .slnx and refine release scripts/docs 2026-02-27 20:27:01 +01:00
README.md (feature): add FromActionResult and FromHttpResponseAsync; preserve redirect status 2026-10-28 19:07:37 +01:00

MaksIT.Results

NuGet License: MIT

Line Coverage Branch Coverage Method Coverage

HTTP-aware Result / Result<T> for .NET - model success and failure once, then map to ASP.NET Core IActionResult or parse the same wire shape from an HttpResponseMessage.

Target: .NET 10 (net10.0)

Why

Most Result libraries give you success/failure (and sometimes a typed error), then leave HTTP as a second mapping in the controller: domain NotFound -> 404, Conflict -> 409, and so on. That remapping is where APIs drift.

MaksIT.Results uses HTTP status codes as the outcome vocabulary. One type carries success/failure, messages, and a shared classification (NotFound, Conflict, TooManyRequests, ...). The service already decided the status; the controller calls ToActionResult().

That is not "leaking HTTP into the domain." Status codes are a mature taxonomy of outcomes that apply beyond the wire - including desktop, workers, and CLI:

Status idea Same idea outside the browser
404 entity / file / setting not found
409 concurrent edit / state conflict
401 / 403 not signed in / not allowed
422 validation failed
429 rate limit / throttle
503 dependency down / retry later
202 accepted, still processing

Compared to typical Result libs

Typical Result lib MaksIT.Results
Outcome model Success / Failure (+ custom errors) Success / Failure + HttpStatusCode
Controller mapping Invent codes, map again to HTTP Status already set; ToActionResult()
Failure body Often ad hoc RFC 7807 ProblemDetails
Client parse Rare / out of scope FromHttpResponseAsync / FromActionResult
Status factories Usually Ok / Fail (+ a few) Named 1xx-5xx factories

Bottom line: most Result packages stop at "return from a method." This one is a service <-> API <-> client result model - one contract from domain to wire and back.

Install

dotnet add package MaksIT.Results
Install-Package MaksIT.Results

Quick start

Create results

using MaksIT.Results;

Result ok = Result.Ok("Operation completed");
Result failed = Result.BadRequest("Validation failed");

Result<int> answer = Result<int>.Ok(42, "Answer generated");
Result<UserDto?> missing = Result<UserDto?>.NotFound(null, "Entity not found");

Map between result shapes

Result<int> source = Result<int>.Ok(42, "Value loaded");

Result<string?> asString = source.ToResultOfType(v => v?.ToString());
Result nonGeneric = source.ToResult();

Controllers

using MaksIT.Results;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("users")]
public sealed class UsersController : ControllerBase {
  [HttpGet("{id:guid}")]
  public IActionResult Get(Guid id) {
    Result<UserDto?> result = id == Guid.Empty
      ? Result<UserDto?>.BadRequest(null, "Invalid id")
      : Result<UserDto?>.Ok(new UserDto(id, "maks"), "User loaded");

    return result.ToActionResult();
  }
}

public sealed record UserDto(Guid Id, string Name);

HTTP clients

Parse responses produced in the same contract (JSON body, redirect Location, or problem+json):

using HttpResponseMessage response = await http.GetAsync(url);

Result bare = await Result.FromHttpResponseAsync(response);
Result<UserDto> typed = await Result<UserDto>.FromHttpResponseAsync(response);

Or reverse an IActionResult you already built:

IActionResult action = result.ToActionResult();

Result parsed = Result.FromActionResult(action);
Result<UserDto> typed = Result<UserDto>.FromActionResult(action);

ToActionResult() contract

Outcome Response
Result success Status code only
Result<T> success, non-null Value JSON body + status
Result<T> success, redirect status (300-303, 307-308) and non-empty string Value Redirect; status preserved on RedirectResult
Any failure ProblemDetails: status, title = "An error occurred", detail = joined Messages, content type application/problem+json

FromActionResult / FromHttpResponseAsync

Wire shape Result
Success, no body Success Result / empty Result<T>
Success JSON body Deserialized into Result<T>.Value
Redirect + Location Result<string> with that URL and redirect status
application/problem+json (or JSON with detail / title) Failure; messages from problem details

JSON options

ObjectResult uses the app's JsonSerializerOptions when configured via AddJsonOptions:

builder.Services.AddControllers()
  .AddJsonOptions(options => {
    options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
  });

If none are registered, responses fall back to camel case.

Status code factories

Family Examples
Informational Continue, SwitchingProtocols, Processing, ...
Success Ok, Created, NoContent, ...
Redirection Found, MovedPermanently, PermanentRedirect, ...
Client error BadRequest, NotFound, Conflict, TooManyRequests, ...
Server error InternalServerError, ServiceUnavailable, ...

Use Result.* or Result<T>.* (e.g. Result<MyDto>.Ok(value, "message")).

Contributing

See CONTRIBUTING.md. Changelog: CHANGELOG.md.

License

MIT - Maksym Sadovnychyy (MAKS-IT)

Contact: maksym.sadovnychyy@gmail.com