Repository navigation
Configuration Handlers
How FxMap answers a request on the data-provider side, and the extension points that replaced custom handlers: pipeline behaviors and custom expressions.
On the service that owns the data, the last step of every request is an IQueryOfHandler<TModel, TDistributedKey>. It fetches the entities for the requested ids and expressions. You do not write one for normal use: the EF Core and MongoDB data providers register it for every entity you configure.
The IMappableRequestHandler<TKey> interface from FxMap 1.x has been removed, and handlers are no longer registered by assembly scanning. To customize how requests are processed, use pipeline behaviors (below). To compute values that do not map to a model property, use custom expressions.
Run on the data-provider side before data is fetched and returned.
✅ cfg.AddReceivedPipelines(...)
Intercept incoming mapping requests
Run on the consumer side before a mapping request is dispatched.
✅ cfg.AddSendPipelines(...)
Intercept outgoing mapping requests
builder.Services.AddFxMap(cfg =>
{
cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
// Register pipeline behaviors
cfg.AddReceivedPipelines(c => c.OfType(typeof(LoggingReceivedPipeline<>)));
cfg.AddSendPipelines(c => c.OfType(typeof(LoggingSendPipeline<>)));
});v2.0 Breaking Change: AddHandlersFromNamespaceContaining has been removed. Pipelines are now the only handler registration path. Remove any calls to this method when migrating from 1.x.
- The consuming service resolves a profile and builds one request per distributed key. Send pipelines run first.
- The request travels over the transport (or is answered locally when the owner is the same service).
- On the owning service, received pipelines run in registration order, each calling
nextto continue. - The last step is the
IQueryOfHandlerof the data provider, which reads the data. Custom expressions are answered by their own behaviors and merged into the result.
An entity needs exactly one handler. If a service owns an entity but no data provider is registered, FxMap throws CannotFindHandlerForDistributedKey; if two handlers answer for the same model and key it throws DistributedKeyHasBeenConfiguredForModel.
A received pipeline wraps the handler. It can log, authorize, validate, change the request, or return early without calling next:
using FxMap.Abstractions;
using FxMap.Delegates;
using FxMap.Responses;
public sealed class LoggingReceivedPipeline<TKey>(ILogger<LoggingReceivedPipeline<TKey>> logger)
: IReceivedPipelineBehavior<TKey> where TKey : IDistributedKey
{
public async Task<ItemsResponse<DataResponse>> HandleAsync(
RequestContext<TKey> requestContext,
ReceivedHandlerDelegate next)
{
logger.LogInformation("Request for {Key}: {Count} ids",
typeof(TKey).Name, requestContext.Query.SelectorIds.Length);
return await next.Invoke(); // continue to the next pipeline, then the handler
}
}
// Program.cs
cfg.AddReceivedPipelines(c => c.OfType(typeof(LoggingReceivedPipeline<>)));RequestContext<TKey> carries Query.SelectorIds (the ids to fetch), Query.Expressions (the expressions from the profile), the headers and the cancellation token. Send pipelines work the same way with ISendPipelineBehavior<TKey>. See Pipelines for the full guide.
When a profile asks for an expression that is not a property of the entity, the owning service can answer it with an ICustomExpressionBehavior<TKey>. It returns one value for each requested id:
public sealed class GreetingExpression : ICustomExpressionBehavior<UserDistributedKey>
{
public string CustomExpression() => "Greeting";
public Task<Dictionary<string, object>> HandleAsync(RequestContext<UserDistributedKey> requestContext)
{
var result = requestContext.Query.SelectorIds
.ToDictionary(id => id, object (id) => $"Hello, {id}");
return Task.FromResult(result);
}
}
// Owning service: Program.cs
cfg.AddCustomExpressionPipelines(c => c.OfType<GreetingExpression>());
// Consuming service: profile
UseDistributedKey<UserDistributedKey>()
.Of(x => x.UserId)
.For(x => x.Greeting, "Greeting");| You want to | Use |
|---|---|
| Log, authorize, validate or cache requests on the owning service | IReceivedPipelineBehavior |
| Add headers, trace or change requests before they leave the consuming service | ISendPipelineBehavior |
| Return a computed value that is not a property of the entity | ICustomExpressionBehavior |
| Read from a store that has no FxMap data provider | Write a data provider (an IQueryOfHandler). This is advanced: see the data provider notes in the v4.0.0 changelog. |
- Prefer a pipeline over a new data provider; most needs are cross-cutting concerns.
- Always call
nextunless you mean to short-circuit the request. - Pipelines can run for many requests at once, so keep them stateless and use dependency injection for what they need.
Source: https://fxmapper.net/docs/configuration/handlers/ — the website is always the most up to date version.
Introduction
Getting Started
Configuration
- Service Registration
- Entities & Profiles
- Handlers
- Pipelines
- Strongly Typed IDs
- Error Handling
- Retry Policy
- Supervisor
- Telemetry
- Kestrel (HTTP/2)
- Analyzers
Expressions
- Overview
- Property Access
- Filters, Sorting & Paging
- Functions
- Aggregations
- Projections
- Coalesce & Ternary
- GroupBy
Data Providers
Transports
Integrations
Quality
AI Assistants
Migration
Release Notes