BFFConductor 2.2.0

dotnet add package BFFConductor --version 2.2.0
                    
NuGet\Install-Package BFFConductor -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BFFConductor" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BFFConductor" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="BFFConductor" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BFFConductor --version 2.2.0
                    
#r "nuget: BFFConductor, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BFFConductor@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BFFConductor&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=BFFConductor&version=2.2.0
                    
Install as a Cake Tool

BFFConductor — Backend Usage Guide

BFFConductor is a .NET 8 library for ASP.NET Core BFF (Backend for Frontend) APIs. It standardizes error responses, maps business errors and exceptions to HTTP status codes, and sends display-mode hints to the client via response headers — so your frontend always knows how to present errors to users.


Companion Package

A corresponding Angular library is available on npm: bffconductor. It reads the x-handle-message-as header and dispatches error display to the appropriate UI handler on the client side.

npm install bffconductor

Installation

dotnet add package BFFConductor

Quick Start

1. Create error-mapping.json

Place this file in your project root and ensure it is copied to the output directory:

{
  "defaults": {
    "displayMode": "toast"
  },
  "mappings": [
    {
      "errorCode": "VALIDATION_FAILED",
      "httpStatus": 422,
      "displayMode": "inline"
    },
    {
      "errorCode": "NOT_FOUND",
      "httpStatus": 404,
      "displayMode": "toast"
    }
  ],
  "exceptionMappings": [
    {
      "exceptionType": "UnauthorizedAccessException",
      "httpStatus": 401,
      "displayMode": "redirect"
    },
    {
      "exceptionType": "ArgumentNullException",
      "httpStatus": 400,
      "displayMode": "toast",
      "errorCode": "VALIDATION_FAILED"
    }
  ]
}

In your .csproj:

<ItemGroup>
  <Content Include="error-mapping.json">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
</ItemGroup>

2. Register in Program.cs

builder.Services.AddBFFConductor(options =>
{
    options.MappingSpecPath = "error-mapping.json";  // default
    options.FallbackDisplayMode = "toast";           // default
});

3. Decorate Your Controllers

[ApiController]
[UseBffExceptionFilter]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
    [HttpPost]
    public IActionResult CreateOrder([FromBody] CreateOrderRequest request)
    {
        var result = _orderService.Create(request);
        return new OperationActionResult<Order>(result);
    }
}

Core Concepts

OperationResult<T> — Service Layer Return Type

Use OperationResult<T> in your service/application layer to represent success or failure without throwing exceptions.

// Success
return OperationResult<Order>.Ok(order);

// Failure with a single error
return OperationResult<Order>.Fail("Order not found", "NOT_FOUND");

// Failure with multiple errors
return OperationResult<Order>.Fail(new[]
{
    new OperationError { Message = "Name is required", Code = "VALIDATION_FAILED" },
    new OperationError { Message = "Email is invalid", Code = "VALIDATION_FAILED" }
});

// Success with a display mode hint
return OperationResult<Order>.Ok(order, displayMode: "toast");

OperationResult<T> properties:

Property Type Description
Success bool Whether the operation succeeded
Data T? The result payload (null on failure)
Errors List<OperationError> Business errors (message + optional code)
DisplayMode string? Optional client display hint override

OperationActionResult<T> — Controller Return Type

OperationActionResult<T> converts an OperationResult<T> into an HTTP response. It:

  • Returns 200 OK on success with the data payload
  • Looks up the error code in the registry to determine HTTP status on failure
  • Sets the x-handle-message-as response header with the display mode
  • Applies [ErrorDisplay] attribute overrides (action-level beats controller-level beats registry)
[HttpPut("{id}")]
public IActionResult UpdateOrder(int id, [FromBody] UpdateOrderRequest request)
{
    var result = _orderService.Update(id, request);
    return new OperationActionResult<Order>(result);
}

Response Format

All responses use a consistent envelope:

Success (200 OK)

{
  "success": true,
  "data": { "id": 42, "status": "pending" },
  "errors": []
}
x-handle-message-as: toast

Failure (422 Unprocessable Entity)

{
  "success": false,
  "data": null,
  "errors": [
    { "message": "Name is required", "code": "VALIDATION_FAILED" }
  ]
}
x-handle-message-as: inline

Display Modes

The x-handle-message-as header tells the frontend how to display the error or success message. You define your own display mode strings — common conventions include:

Mode Description
silent Suppress UI; handle programmatically
toast Brief notification (auto-dismissing)
snackbar Bottom-of-screen notification bar
inline Show validation errors alongside form fields
modal Blocking modal dialog
page Full-page error display
redirect Redirect user (pair with x-redirect-url header)

Attributes

[UseBffExceptionFilter]

Apply to a controller to enable automatic exception handling. Unhandled exceptions are caught, matched against exceptionMappings in your JSON config, and returned as structured ApiResponse errors.

[UseBffExceptionFilter]
public class MyController : ControllerBase { }

[ErrorDisplay(errorCode, displayMode)]

Override the display mode for a specific error code at the controller or action level. The most specific match wins: action-level overrides controller-level, which overrides the global registry.

// Controller-level: all VALIDATION_FAILED errors use "toast" by default for this controller
[ErrorDisplay("VALIDATION_FAILED", "toast")]
public class MyController : ControllerBase
{
    // Action-level: overrides the controller to use "inline" for this endpoint
    [ErrorDisplay("VALIDATION_FAILED", "inline")]
    [HttpPost]
    public IActionResult Create([FromBody] CreateRequest req) { ... }
}

[ExceptionDisplay(exceptionType, displayMode)]

Override the display mode for a specific exception type at the controller or action level.

[ExceptionDisplay(typeof(UnauthorizedAccessException), "redirect")]
[HttpDelete("{id}")]
public IActionResult Delete(int id) { ... }

error-mapping.json Reference

{
  "defaults": {
    "displayMode": "toast"          // Fallback display mode for unmapped errors
  },
  "mappings": [
    {
      "errorCode": "VALIDATION_FAILED",    // Business error code string
      "httpStatus": 422,                   // HTTP status to return
      "displayMode": "inline",             // x-handle-message-as value
      "additionalHeaders": {               // Optional: extra response headers
        "x-my-header": "my-value"
      }
    }
  ],
  "exceptionMappings": [
    {
      "exceptionType": "ArgumentNullException",  // Exception class name
      "httpStatus": 400,
      "displayMode": "toast",
      "errorCode": "VALIDATION_FAILED",          // Optional: included in error response
      "additionalHeaders": {}
    }
  ]
}

Exception type matching uses inheritance: if you map Exception, it will match any unhandled exception not matched by a more specific type.


DI Registration Reference

AddBFFConductor registers the following services:

Service Lifetime Description
BffResponseOptions Singleton Configuration options
ErrorMappingRegistry Singleton Error code → HTTP status + display mode
ExceptionMappingRegistry Singleton Exception type → HTTP status + display mode
BffExceptionFilter Transient Exception filter (used by [UseBffExceptionFilter])

ErrorMappingRegistry is resolved automatically by OperationActionResult<T> — no need to inject it into your controllers.


Full Example

error-mapping.json

{
  "defaults": { "displayMode": "toast" },
  "mappings": [
    { "errorCode": "VALIDATION_FAILED", "httpStatus": 422, "displayMode": "inline" },
    { "errorCode": "NOT_FOUND",          "httpStatus": 404, "displayMode": "toast"   },
    { "errorCode": "CONFLICT",           "httpStatus": 409, "displayMode": "modal"   }
  ],
  "exceptionMappings": [
    { "exceptionType": "UnauthorizedAccessException", "httpStatus": 401, "displayMode": "redirect" }
  ]
}

Program.cs

builder.Services.AddBFFConductor(options =>
{
    options.MappingSpecPath = "error-mapping.json";
    options.FallbackDisplayMode = "toast";
});

app.UseCors();
app.UseHttpsRedirection();
app.MapControllers();

ProductsController.cs

[ApiController]
[UseBffExceptionFilter]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;

    public ProductsController(IProductService service)
    {
        _service = service;
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var result = _service.GetById(id);
        return new OperationActionResult<Product>(result);
    }

    [HttpPost]
    [ErrorDisplay("VALIDATION_FAILED", "inline")]   // override global mapping for this action
    public IActionResult Create([FromBody] CreateProductRequest request)
    {
        var result = _service.Create(request);
        return new OperationActionResult<Product>(result);
    }
}

ProductService.cs

public class ProductService : IProductService
{
    public OperationResult<Product> GetById(int id)
    {
        var product = _repo.Find(id);
        if (product is null)
            return OperationResult<Product>.Fail("Product not found", "NOT_FOUND");

        return OperationResult<Product>.Ok(product);
    }

    public OperationResult<Product> Create(CreateProductRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Name))
            return OperationResult<Product>.Fail("Name is required", "VALIDATION_FAILED");

        var product = _repo.Save(new Product(request.Name));
        return OperationResult<Product>.Ok(product);
    }
}

Interfaces

If you need to abstract over response or result types, BFFConductor exposes:

// For API responses (returned to clients)
public interface IApiResponse<out T>
{
    bool Success { get; }
    T? Data { get; }
    IReadOnlyList<ApiError> Errors { get; }
}

// For service/application layer results
public interface IOperationResult
{
    bool Success { get; }
    IReadOnlyList<OperationError> Errors { get; }
    string? DisplayMode { get; }
    object? GetData();
}

public interface IOperationResult<out T> : IOperationResult
{
    T? Data { get; }
}
Product 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 was computed.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 99 5/29/2026
2.1.0 96 5/29/2026
2.0.0 96 5/23/2026
1.5.1 104 5/23/2026
1.5.0 110 3/31/2026
1.4.0 104 3/11/2026
1.3.0 103 3/11/2026
1.2.0 109 3/11/2026
1.1.0 117 3/10/2026
1.0.0 106 3/10/2026