Mitc.Support.Results 1.2.0

Mitc.Support.Results

Generic result types with monadic composition for explicit error handling. Services return results instead of throwing exceptions, making failure a first-class part of the method signature.

Core Types

Type Purpose
Result<TValue> Operation that returns a value or a string error
Outcome Operation that succeeds or fails with no return value
StructuredResult<TValue> Like Result<TValue> but with structured errors (ResultErrors)
Result<TValue, TError> Bring your own error type
Outcome<TError> Bring your own error type, no return value

All result types carry a ResultStatus that classifies the outcome:

public enum ResultStatus
{
    Success,
    NotFound,
    Invalid,
    Unauthorized,
    Forbidden,
    Conflict,
    Ambiguous,
}

Basic Usage

Returning results from services

The Result static class provides shorthand factory methods. The method's return type drives the implicit conversion:

public async Task<Result<User>> GetByIdAsync(UserId id)
{
    var user = await context.Users.FindAsync(id);

    if (user is null)
        return Result.NotFound("User not found.");

    return user; // implicit Success
}

public async Task<Outcome> DeleteAsync(UserId id)
{
    var user = await context.Users.FirstOrDefaultAsync(u => u.Id == id);

    if (user is null)
        return Result.NotFound("User not found.");

    context.Users.Remove(user);
    await context.SaveChangesAsync();

    return Outcome.Success();
}

The same Result.NotFound(...) call works whether the method returns Result<User>, Outcome, or StructuredResult<User>.

Implicit conversions

For Result<TValue> where TValue is not string, implicit conversions keep service code clean:

Result<int> success = 42;           // implicit Success
Result<int> failure = "Not found."; // implicit Invalid
Outcome failure = "Bad request.";   // implicit Invalid

When TValue is string, use explicit factory methods to avoid ambiguity:

var result = Result<string>.Success("Alice");
var error = Result.NotFound("User not found.");

Match — handle both outcomes

// Returns a value
var response = result.Match(
    success: user => Ok(user),
    failure: error => NotFound(error));

// Outcome (no value)
var response = outcome.Match(
    success: () => NoContent(),
    failure: error => BadRequest(error));

Switch — side effects

result.Switch(
    success: user => logger.LogInformation("Found {User}", user.Name),
    failure: error => logger.LogWarning("Failed: {Error}", error));

Composition

Map — transform the success value

Result<string> userName = result.Map(user => user.Name);
// Success -> transformed value; Failure -> passes through unchanged

Bind — chain result-returning operations

Result<Order> order = await GetUser(id)
    .Bind(user => GetActiveOrder(user));
// Short-circuits on first failure

OnSuccess — side effect on success

result.OnSuccess(user => cache.Set(user.Id, user));
// Executes only on success, returns self for chaining

Async chaining

All composition methods work directly on Task<Result<T>>:

var name = await userService.GetByIdAsync(id)
    .Map(user => user.Name);

Structured Errors

When you need more than a string error — machine-readable codes, field-level validation errors — use StructuredResult<TValue> with ResultError:

public async Task<StructuredResult<User>> CreateAsync(CreateRequest request)
{
    if (await EmailExists(request.Email))
        return Result.Conflict(new ResultError("Already in use.", "Email", "DUPLICATE_EMAIL"));

    // ...
    return user;
}

ResultError constructors

new ResultError("Something went wrong.")                        // message only
new ResultError("Already in use.", "Email")                     // message + property
new ResultError("Already in use.", "Email", "DUPLICATE_EMAIL")  // message + property + code

Multiple errors

return Result.Invalid(ResultErrors.From(
    new ResultError("Name is required.", "Name"),
    new ResultError("Email is invalid.", "Email")));

ResultErrors helper factories

ResultErrors.Single("Something went wrong.")
ResultErrors.Single("Email", "Already in use.")
ResultErrors.Single(new ResultError("...", "Email", "DUPLICATE"))
ResultErrors.From(error1, error2, error3)

Failure Shorthand

The non-generic Result class returns lightweight Failure / StructuredFailure structs that implicitly convert to the method's return type:

// String errors — converts to Result<T>, Outcome, or StructuredResult<T>
Result.NotFound("User not found.")
Result.Invalid("Bad request.")
Result.Unauthorized("No access.")
Result.Forbidden("Not allowed.")
Result.Conflict("Duplicate detected.")
Result.Ambiguous("More than one match.")

// Structured errors — converts to StructuredResult<T>
Result.NotFound(new ResultError("Not found.", "Id", "NOT_FOUND"))
Result.Invalid(ResultErrors.From(error1, error2))

Custom Error Types

Use Result<TValue, TError> and Outcome<TError> with any error type:

Result<User, AppError> result = await GetUser(id);
Outcome<AppError> outcome = await DeleteUser(id);

Status Inspection

Every result exposes its status:

result.IsSuccess  // true if Status == ResultStatus.Success
result.Status     // the ResultStatus enum value
result.Value      // the success value (check IsSuccess first)
result.Error      // the error (check IsSuccess first)

Match is the recommended way to access values — it forces you to handle both cases.

Type Hierarchy

Result<TStatus, TValue, TError>         abstract base
├── Result<TValue, TError>              custom error type, ResultStatus
│   ├── Result<TValue>                  string errors
│   └── StructuredResult<TValue>        ResultErrors
└── Outcome<TError>                     no return value, custom error type
    └── Outcome                         no return value, string errors

Result (static)                          shorthand factory methods
├── returns Failure                      converts to Result<T>, Outcome, StructuredResult<T>
└── returns StructuredFailure            converts to StructuredResult<T>

Target Frameworks

netstandard2.0, net5.0, net6.0, net7.0, net8.0, net9.0, net10.0

Showing the top 20 packages that depend on Mitc.Support.Results.

Packages Downloads
Mitc.Integrations.CensusGeocoder
A lightweight client over the US Census Bureau geocoder's Find Locations service: supply one or more addresses, get back coordinates. Supports single-address lookups and batch processing (up to 10,000 addresses) keyed by a caller-supplied id.
0
Mitc.Integrations.Stripe
Turnkey Stripe integration for ASP.NET Core. Handles webhook processing, product/price catalog mirroring, missed-event backfill, subscription management, checkout sessions, and metered usage reporting.
0
Mitc.Integrations.Stripe
Turnkey Stripe integration for ASP.NET Core. Handles webhook processing, product/price catalog syncing, subscription management, checkout sessions, and metered usage reporting.
0

.NET 10.0

  • No dependencies.

.NET 5.0

  • No dependencies.

.NET 6.0

  • No dependencies.

.NET 7.0

  • No dependencies.

.NET 8.0

  • No dependencies.

.NET 9.0

  • No dependencies.

.NET Standard 2.0

  • No dependencies.

Version Downloads Last updated
2.1.1 174 7/7/2026
2.1.0 0 7/7/2026
2.0.0 1 7/7/2026
1.3.0 1 7/6/2026
1.2.0 84 6/23/2026
1.1.0 0 4/13/2026
1.0.0 35 3/31/2026