Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ServiceComposer/ServiceComposer.AspNetCore/llms.txt

Use this file to discover all available pages before exploring further.

The Scatter/Gather API in ServiceComposer provides a lightweight way to fan out a single incoming GET request to multiple downstream services simultaneously, collect their responses, and aggregate the results into a unified JSON array. Unlike the full ViewModel Composition pipeline — which relies on event-driven handlers and a shared ViewModel object — Scatter/Gather is oriented toward read-only list endpoints where each downstream service contributes a batch of items and the gateway simply concatenates them. The two setup calls required are:
  1. services.AddScatterGather() — registers the ScatterGatherConfiguration singleton (and its built-in http gatherer factory) in the DI container.
  2. app.MapScatterGather(...) — registers one or more GET endpoints that invoke the configured gatherers in parallel and write the aggregated result to the response.
For full endpoint builder options and the configuration-driven approach, see the ScatterGatherOptions & Endpoint Builder page. For HTTP gatherer details and extension points, see the HttpGatherer page.

ScatterGatherServiceCollectionExtensions

Static class in the ServiceComposer.AspNetCore namespace.

AddScatterGather

public static IServiceCollection AddScatterGather(
    this IServiceCollection services,
    Action<ScatterGatherConfiguration> configure = null)
Registers the Scatter/Gather infrastructure in the DI container. Internally, this creates a ScatterGatherConfiguration instance, pre-registers the built-in "http" gatherer factory (which produces HttpGatherer instances), optionally invokes the configure callback so you can add custom gatherer factories, and registers the configuration as a singleton. AddScatterGather must be called before MapScatterGather(IEndpointRouteBuilder, IConfiguration, ...) — the configuration-driven overload resolves ScatterGatherConfiguration from DI. The simpler MapScatterGather(string, ScatterGatherOptions) overload does not require AddScatterGather.
services
IServiceCollection
required
The application service collection.
configure
Action<ScatterGatherConfiguration>
Optional callback to register additional gatherer factories. When null, only the built-in "http" factory is available.
Returns IServiceCollection for chaining. Minimal configuration-based setup
var builder = WebApplication.CreateBuilder();
builder.Services.AddRouting();
builder.Services.AddHttpClient();
builder.Services.AddScatterGather();

var app = builder.Build();
app.MapScatterGather(builder.Configuration.GetSection("ScatterGather"));
app.Run();
With a custom gatherer factory
builder.Services.AddScatterGather(config =>
{
    config.AddGathererFactory(
        "static",
        (section, _) => new StaticDataGatherer(section["Key"]));
});

ScatterGatherConfiguration

Class in the ServiceComposer.AspNetCore namespace. Holds the registry of gatherer factories used when loading route definitions from IConfiguration.

AddGathererFactory

public void AddGathererFactory(
    string type,
    Func<IConfigurationSection, IServiceProvider, IGatherer> factory)
Registers a factory for a custom gatherer type discriminator. When MapScatterGather reads a gatherers array from IConfiguration, each entry’s Type field is matched (case-insensitively) against registered factories. The "http" type is pre-registered and produces HttpGatherer instances; call AddGathererFactory to support additional types.
type
string
required
The discriminator string that appears in the gatherer configuration entry’s Type field. Matching is case-insensitive.
factory
Func<IConfigurationSection, IServiceProvider, IGatherer>
required
Delegate invoked once at startup. Receives the gatherer’s configuration section and the root IServiceProvider. Do not resolve scoped services here — resolve them inside IGatherer.Gather via HttpContext.RequestServices.
builder.Services.AddScatterGather(config =>
{
    config.AddGathererFactory(
        "inventory",
        (section, provider) => new InventoryGatherer(
            section["Key"],
            section["WarehouseId"]));
});

IGatherer

Interface in the ServiceComposer.AspNetCore namespace. All gatherers must implement this interface.
public interface IGatherer
{
    string Key { get; }
    Task<IEnumerable<object>> Gather(HttpContext context);
}

Key

string Key { get; }
A unique string identifier for this gatherer within a ScatterGatherOptions.Gatherers list. Used in logging, telemetry span names, and as the named HttpClient key when HttpGatherer resolves its IHttpClientFactory client.

Gather

Task<IEnumerable<object>> Gather(HttpContext context)
Called in parallel with all other configured gatherers for a given request. Returns the items this gatherer contributes to the aggregated response.
context
HttpContext
required
The current HTTP context. Use context.RequestServices to resolve scoped services and context.Request to access route values, query strings, and headers.

Gatherer<T>

Abstract base class in the ServiceComposer.AspNetCore namespace. Provides a strongly typed implementation of IGatherer and handles the covariant type bridging to IEnumerable<object>.
public abstract class Gatherer<T> : IGatherer where T : class
{
    protected Gatherer(string key) { }
    public string Key { get; }
    public abstract Task<IEnumerable<T>> Gather(HttpContext context);
}

Constructor

protected Gatherer(string key)
key
string
required
The unique key for this gatherer. Throws ArgumentException if null or whitespace.

Abstract Gather

public abstract Task<IEnumerable<T>> Gather(HttpContext context)
Override this in your subclass to provide the strongly typed items. The base class adapter bridges IEnumerable<T> to IEnumerable<object> automatically. Custom gatherer example
public class FeaturedProductsGatherer(string key) : Gatherer<ProductSummary>(key)
{
    public override async Task<IEnumerable<ProductSummary>> Gather(HttpContext context)
    {
        var repo = context.RequestServices.GetRequiredService<IProductRepository>();
        return await repo.GetFeaturedAsync();
    }
}

// Register
app.MapScatterGather("api/homepage", new ScatterGatherOptions
{
    Gatherers =
    [
        new FeaturedProductsGatherer("FeaturedProducts"),
        new HttpGatherer("Promotions", "https://promo-service/api/active")
    ]
});

IAggregator

Interface in the ServiceComposer.AspNetCore namespace. Combines the results from all gatherers into a single object that is serialized as the final response.
public interface IAggregator
{
    void Add(IEnumerable<object> nodes);
    Task<object> Aggregate();
}

Add

void Add(IEnumerable<object> nodes)
Called once per gatherer (in an unspecified order) with that gatherer’s collected items. The default aggregator accumulates all items into a flat list.
nodes
IEnumerable<object>
required
The items returned by a single gatherer’s Gather call.

Aggregate

Task<object> Aggregate()
Invoked after all gatherers complete. Returns the final aggregated object that will be serialized and written to the response. Custom aggregator example
public class SortedAggregator : IAggregator
{
    private readonly List<JsonNode> _items = [];

    public void Add(IEnumerable<object> nodes)
    {
        foreach (var node in nodes.OfType<JsonNode>())
            _items.Add(node);
    }

    public Task<object> Aggregate()
    {
        var sorted = _items.OrderBy(n => n["name"]?.GetValue<string>()).ToList();
        return Task.FromResult<object>(sorted);
    }
}

// Register as a custom aggregator
builder.Services.AddTransient<SortedAggregator>();

app.MapScatterGather("api/products", new ScatterGatherOptions
{
    CustomAggregator = typeof(SortedAggregator),
    Gatherers = [ new HttpGatherer("Source", "https://catalog/api/products") ]
});

Build docs developers (and LLMs) love