Skip to main content

Mappers

The RA.Utilities.Api.Mapper namespace contains a set of static helper classes designed to standardize and simplify the creation of API responses in ASP.NET Core Minimal APIs.

Their main purpose is to bridge the gap between your application's internal logic (like the Result<T> pattern and custom exceptions) and the IResult objects that ASP.NET Core uses to generate HTTP responses.

Let's break down the purpose of each class in that namespace.​

1. SuccessResult​

  • Purpose: This class is a factory for creating consistent, successful IResult objects.
  • How it works: It provides simple static methods like Ok(), Ok<T>(), Created(), and Accepted() that wrap your data in a standardized SuccessResponse<T> object and return the correct HTTP status code (200, 201, 202, etc.). This keeps your endpoint logic clean and ensures all success responses from your API have a uniform structure.
public static class SuccessResult
{
// Creates an HTTP 200 OK with a body
public static IResult Ok<TResult>(TResult results)
{
return Microsoft.AspNetCore.Http.Results.Ok(new SuccessResponse<TResult>(results));
}

// Creates an HTTP 201 Created
public static IResult Created()
{
return Microsoft.AspNetCore.Http.Results.Created();
}
// ... and other success helpers
}

2. ErrorResultMapper​

  • Purpose: This class is responsible for transforming specific exception types into structured error response bodies (DTOs).
  • How it works: It has methods that take a specific exception (e.g., NotFoundException, ConflictException) and map its properties to a corresponding response model (e.g., NotFoundResponse, ConflictResponse). It deals with the "what" of the error—creating the JSON payload—but does not create the final IResult itself. It's a central helper used by ErrorResultResponse.
public static class ErrorResultMapper
{
// Maps a NotFoundException to a NotFoundResponse object (the response body)
public static NotFoundResponse ToResponse(NotFoundException exception)
{
ArgumentNullException.ThrowIfNull(exception);

return new NotFoundResponse(
new NotFoundResult(
exception.EntityName,
exception.EntityValue,
exception.Message),
responseCode: exception.ErrorCode,
responseMessage: exception.Message);
}
// ... and other exception-to-body mappers
}

3. ErrorResultResponse​

  • Purpose: This is the key class for handling failures. It takes an Exception and creates the final, appropriate IResult with the correct HTTP status code and a standardized error body.
  • How it works: Its Result(Exception) method uses a switch expression to determine the exception type. It then calls the appropriate method from ErrorResultMapper to get the error body and wraps that body in a JsonResult with the correct status code (e.g., 404 Not Found, 409 Conflict).

This class is designed to be used with the Result.Match() method, allowing you to handle all failure cases in a single line of code.

public static class ErrorResultResponse
{
public static IResult Result(Exception exception) => exception switch
{
// ...
NotFoundException notFoundException => Microsoft.AspNetCore.Http.Results.Json(
// It calls ErrorResultMapper to create the body...
ErrorResultMapper.ToResponse(notFoundException),
// ...and sets the correct status code.
statusCode: BaseResponseCode.NotFound
),
// ... and other exception-to-IResult cases
};
}

🚀 Example​

The mapper classes in RA.Utilities.Api.Mapper are designed to work together seamlessly with the Result<T> type from RA.Utilities.Core.Results to create clean and consistent API endpoints.

The core pattern is to have your application/service layer return a Result<T> object, and then use the .Match() method in your Minimal API endpoint to map the success and failure outcomes to the appropriate HTTP response using SuccessResult and ErrorResultResponse.

Here is a complete, step-by-step example based on the documentation you provided.

Step 1: Create a Service That Returns a Result<T>​

First, your business logic should not know about HTTP. It should simply return a Result indicating the outcome of an operation. A success result will contain the value, and a failure result will contain a specific exception (like NotFoundException).

using RA.Utilities.Core.Results;
using RA.Utilities.Core.Exceptions;

// A simple Product record for the example
public record Product(int Id, string Name);

public class ProductService
{
public Result<Product> GetProductById(int id)
{
// Simulate invalid input
if (id <= 0)
{
return new BadRequestException("Invalid product ID.");
}

// Simulate not finding a product
if (id != 1)
{
// Return a failure Result containing a NotFoundException
return new NotFoundException(nameof(Product), id);
}

// Return a success Result with the product
var product = new Product(id, "Sample Product");
return product; // Implicit conversion to Result<Product>
}
}

Step 2: Use Match in Your Minimal API Endpoint​

Now, in your API endpoint, you call the service and use the Match method. This forces you to handle both success and failure cases, which is where the mapper classes come in.

// Example: Program.cs or a dedicated endpoint class

// Make sure to import the mappers
using RA.Utilities.Api.Mapper;

// Assume 'app' is your WebApplication and ProductService is registered in DI
app.MapGet("/products/{id}", (int id, ProductService service) =>
{
Result<Product> result = service.GetProductById(id);

// The Match method maps the outcome to an IResult.
// It's clean, declarative, and handles all cases.
return result.Match<IResult>(
success: product => SuccessResult.Ok(product),
failure: ErrorResultResponse.Result
);
})
.WithTags("Products");

How It Works​

Let's break down the Match call:

1 success: product => SuccessResult.Ok(product):​

  • If the result is a success, this lambda is executed.
  • The product object inside the Result is passed to SuccessResult.Ok().
  • This helper wraps the product in a standard SuccessResponse<Product> body and returns an HTTP 200 OK result.

failure: ErrorResultResponse.Result:​

  • If the result is a failure, this delegate is executed.
  • The Exception from the Result is passed to the ErrorResultResponse.Result method.
  • This method inspects the exception type (NotFoundException, BadRequestException, etc.) and automatically returns the correct HTTP status code (404, 400) with a standardized error body.

Example Outcomes​

✅ Success Request​

A request to /products/1 will execute the success path and return an HTTP 200 OK with this body:

{
"responseCode": 200,
"responseType": 0,
"responseMessage": "Operation completed successfully.",
"result": {
"id": 1,
"name": "Sample Product"
}
}

❌ Failure Request​

A request to /products/99 will execute the failure path. ErrorResultResponse.Result will see the NotFoundException and return an HTTP 404 Not Found with this body:

{
"responseCode": 404,
"responseType": 8,
"responseMessage": "Product with value '99' was not found.",
"result": {
"entity": "Product",
"value": 99,
"errorCode": "Product with value '99' was not found.",
"errorMessage": ""
}
}
Known nested-payload issue

ErrorResultMapper.ToResponse(NotFoundException) currently writes the exception message into NotFoundResult.ErrorCode and leaves ErrorMessage empty. The envelope is correct; see Troubleshooting.

Both examples use the default numeric responseType. Register JsonStringEnumConverter to write "Success" / "NotFound" instead.

This pattern ensures your API endpoints remain clean and consistent, delegating all the response-creation logic to these reusable mapper classes.

🧠 Summary: How They Work Together​

  1. Your application logic returns a Result<T> object.
  2. In your API endpoint, you call .Match() on the result.
  3. If the result is a success, you use SuccessResult.Ok(data) to generate a 200 OK response.
  4. If the result is a failure, you pass the ErrorResultResponse.Result method to the failure delegate. ErrorResultResponse inspects the exception, uses ErrorResultMapper to build a standardized error body, and returns the final IResult with the correct HTTP error code.

Together, these classes create a clean, consistent, and robust pattern for handling API responses that aligns perfectly with a Railway-Oriented Programming style using the Result<T> type.