The seven .NET packages

The seven packages below are available on NuGet.org as version 0.2.10. Keep their versions aligned within each application.

The library examples use 0.2.10 in .NET 10 projects and also support .NET 8 and .NET 9. The echo-mock tool requires .NET 10. Installing a package alone does not enable mocks or validate contracts.

Before running a network example, configure GAPFY_ECHO_BASE_ADDRESS with your Echo API base address, including a trailing slash, and GAPFY_ECHO_API_KEY through your environment or secret store. Use the API address, not the public mock URL. Issue the application key from Applications in Maestro: key issuance requires a paid Echo plan and Echo/ManageHosting, which the organization administrator receives by default on that plan. The rule feed is GET api/echo/mock/rules on the configured API base address. Without a key, it returns 401; a valid key without ServeMocks, or an organization without paid hosting, receives 403.

Gapfy.Echo.Contracts

Publishes a version of a contract your application provides. First create the contract and its Provides binding in Maestro, issue a key with PublishOwnedContracts, and set GAPFY_ECHO_CONTRACT_ID. Save an Echo contract document from the editor as orders.echo.json in the process's working directory. It must be the Echo document, not an example response or an unconverted JSON Schema.

dotnet new console -n EchoContractsDemo -f net10.0
dotnet add EchoContractsDemo package Gapfy.Echo.Contracts --version 0.2.10
using System.Net.Http.Headers;
using Gapfy.Echo.Contracts;

string Required(string name) =>
    Environment.GetEnvironmentVariable(name)
    ?? throw new InvalidOperationException($"Set {name}.");

using var http = new HttpClient
{
    BaseAddress = new Uri(Required("GAPFY_ECHO_BASE_ADDRESS"))
};
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Required("GAPFY_ECHO_API_KEY"));

var publisher = new EchoContractPublicationClient(http);
var documentJson = await File.ReadAllTextAsync("orders.echo.json");
var publication = await publisher.PublishAsync(
    Guid.Parse(Required("GAPFY_ECHO_CONTRACT_ID")), documentJson);

Console.WriteLine(publication.AssignedVersion.Version);
Console.WriteLine(publication.Created);

The package also exposes the contract codec, validator, inference engine and evaluator. The publication result includes Changes with consumer and producer verdicts. A breaking verdict describes an already completed publication; it does not roll it back. Identical content can return Created = false with an existing version.

Gapfy.Echo.Testing

Checks contracts consumed by the application, using a key with ReadBoundContracts. The call throws on an incompatible change, so you can put the same call in your existing test framework. This example requires the remote service to answer instead of allowing an offline pass.

dotnet new console -n EchoTestingDemo -f net10.0
dotnet add EchoTestingDemo package Gapfy.Echo.Testing --version 0.2.10
using Gapfy.Echo.Testing;

var echo = GapfyEchoValidation.Create(new GapfyEchoValidationOptions
{
    RequireRemote = true
});

await echo.Contracts.ValidateAsync();

On the first successful local run, the package creates EchoContracts snapshots next to the project. Review and commit them. CI refuses to create missing snapshots. Without RequireRemote = true, an unreachable service can produce an offline pass using committed snapshots; its report explains what could not be checked. This is not verification of today's remote contract or the running API. Accept changed snapshots deliberately on a workstation, never automatically in CI.

Use CheckAsync() when you need the report without an exception, or Only("partner-orders") to check selected bindings. An unknown selection fails instead of checking nothing. After review on a workstation, AcceptAsync("partner-orders") updates one baseline and AcceptAllAsync() updates all; CI refuses acceptance. Set TreatWarningsAsErrors for strict warnings and declare UnmappedMemberHandlingDisallow or ExhaustiveStatusHandling when those conditions apply to your consumer.

Gapfy.Echo.Mock.Client

Intercepts outbound calls only for factory-created HttpClient instances you opt in. This example exposes /probe and calls the real URL in PARTNER_API_URL through the opted-in client. Set DOTNET_ENVIRONMENT=Development for local use and provide a ServeMocks key.

dotnet new web -n EchoMockClientDemo -f net10.0
dotnet add EchoMockClientDemo package Gapfy.Echo.Mock.Client --version 0.2.10
using Gapfy.Echo.Mock.Client;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEchoMock(x =>
{
    x.ApiKey = builder.Configuration["GAPFY_ECHO_API_KEY"];
    x.RuleServiceUri = new Uri(
        builder.Configuration["GAPFY_ECHO_BASE_ADDRESS"]
        ?? throw new InvalidOperationException("Set GAPFY_ECHO_BASE_ADDRESS."));
});
builder.Services.AddHttpClient("partner").AddEchoMockInterception();

var app = builder.Build();

app.MapGet("/probe", async (IHttpClientFactory factory) =>
{
    var target = app.Configuration["PARTNER_API_URL"]
        ?? throw new InvalidOperationException("Set PARTNER_API_URL.");
    return await factory.CreateClient("partner").GetStringAsync(target);
});

app.Run();

Static and contract rules can answer inside your application. Contract responses are generated with Gapfy.Echo.Contracts and support X-Echo-Scenario and X-Echo-Seed. A missing or invalid contract, or failed generation, returns 502 application/problem+json without calling the real service. Unmatched calls and calls made before the first rules load continue to the real service. A manually constructed HttpClient, a gRPC channel or an SDK with its own transport is not intercepted. The package cannot be armed in Production or Prod; other non-development environments require explicit acknowledgement. Mock.Client uses the paid rule feed, not a public hosted mock URL.

Gapfy.Echo.Mock.Server

Adds mock middleware to your own ASP.NET Core API, using a ServeMocks key. In this example /health remains a real endpoint. Routes that your API has not implemented can answer from matching Echo rules. Place the middleware after routing and before endpoints execute, as shown with WebApplication.

dotnet new web -n EchoMockServerDemo -f net10.0
dotnet add EchoMockServerDemo package Gapfy.Echo.Mock.Server --version 0.2.10
using Gapfy.Echo.Mock.Server;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEchoMock(x =>
{
    x.ApiKey = builder.Configuration["GAPFY_ECHO_API_KEY"];
    x.BaseAddress = new Uri(
        builder.Configuration["GAPFY_ECHO_BASE_ADDRESS"]
        ?? throw new InvalidOperationException("Set GAPFY_ECHO_BASE_ADDRESS."));
});

var app = builder.Build();

app.MapEchoMock();
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));

app.Run();

Implemented routes and unmatched requests continue through your API. Static rules return saved bodies; contract rules generate responses from the contract document. Use X-Echo-Scenario to choose a saved scenario and X-Echo-Seed to repeat generated values. An invalid contract or failed generation returns 502 instead of calling the real handler. Mock.Server arms outside Production by default; Production requires the explicit EnableInProduction option. With no rules loaded it passes through, and a failed refresh keeps the last cached rules.

When paid Echo access expires, neither mock package can refresh its rule feed: the API returns 403 and the packages log the failed refresh without stopping your application. With no rules loaded, Mock.Client sends opted-in calls to the real service and Mock.Server leaves routes to your API. Rules already in memory or on disk can still answer until you disarm the package or clear its cache. Do not rely on a plan change to stop an existing process mocking immediately.

Gapfy.Echo.ApiExplorer

Adds Echo API Explorer to your ASP.NET Core application. Browse its HTTP endpoints and OpenAPI descriptions, prepare a request and inspect the response without leaving the application. You can choose real responses or Echo mocks for a controller or an individual endpoint, including endpoints you have already implemented.

dotnet new web -n EchoExplorerDemo -f net10.0
dotnet add EchoExplorerDemo package Gapfy.Echo.ApiExplorer --version 0.2.10
using Gapfy.Echo.ApiExplorer;
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication("Application")
    .AddCookie("Application", x => x.LoginPath = "/account/login");
builder.Services.AddAuthorization(x =>
{
    x.AddPolicy("Echo.View", x => x.RequireAuthenticatedUser()
        .RequireRole("EchoViewer", "EchoDeveloper", "EchoAdministrator"));
    x.AddPolicy("Echo.Execute", x => x.RequireAuthenticatedUser()
        .RequireRole("EchoDeveloper", "EchoAdministrator"));
    x.AddPolicy("Echo.Session", x => x.RequireAuthenticatedUser()
        .RequireRole("EchoDeveloper", "EchoAdministrator"));
    x.AddPolicy("Echo.Instance", x => x.RequireAuthenticatedUser()
        .RequireRole("EchoAdministrator"));
});
builder.Services.AddEchoDeveloperUI(builder.Configuration.GetSection("Echo"), x =>
{
    x.ApplicationName = "Orders.API";
    x.UI.RoutePrefix = "/tools/echo";
    x.UI.ManageSessionPolicy = "Echo.Session";
    x.UI.ManageInstancePolicy = "Echo.Instance";
});

var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEchoDeveloperUI();
app.MapEchoDeveloperUI();
app.MapGet("/api/orders", () => Results.Ok(new[] { new { id = 1 } }))
    .RequireAuthorization()
    .WithEchoMocking();
app.Run();

This is a complete registration example. It expects your application's login route at /account/login to issue the Application cookie with the appropriate roles. Connect it to your existing authentication and permission policies; the package does not create accounts or grant access. Set Echo:ApiBaseAddress and Echo:ApiKey in your protected configuration, using a ServeMocks key. Open /tools/echo on your application's address after signing in. Your host decides which environments enable the panel. Keep business authorization before UseEchoDeveloperUI; Minimal API endpoints need WithEchoMocking().

Choose the scope before applying

  1. Start with My session to try a change through your own panel executor. Other clients continue to use the instance profile.
  2. Choose a controller or endpoint, select a mock server and route, and choose a scenario if the route has one. An endpoint selection takes precedence over its controller; inheritance uses the choice above it.
  3. Apply the profile, then send a request from the executor and inspect the result. Selecting a server or switching tabs does not apply your draft.
  4. Choose Whole instance only when the change should affect all callers of that instance. This requires instance management permission and confirmation of the pending profile.

Personal profiles expire and become invalid when their instance baseline changes. Reload and review the new state when asked. An unavailable selected mock produces an error; it does not silently run the real handler. Authentication and filters before the handler remain in force, but code inside a replaced handler does not run.

The executor supports request parameters, headers, bodies and credentials. Panel login and request authentication are separate. A host can enable browser OpenID Connect login or a short Bearer session using its existing token validation; these are alternatives. Session tokens stay in server memory, expire and are lost on restart. HTTPS is required for browser login. A panel cookie does not authenticate ordinary business calls.

The interface offers seven languages and keeps the browser's choice. API descriptions, JSON field names and payloads remain as authored. A host can restrict endpoint discovery and enable organization isolation; in that mode, instance changes affect only the authenticated organization. The current package supports HTTP. It does not add gRPC API Explorer support.

Gapfy.Echo.Offline

Keeps a reviewed mock configuration in a portable snapshot, so a test run can use it without contacting Echo. The snapshot contains the profile revision, selected HTTP endpoints, complete rules and contracts, scenarios and seed. It does not contain the Echo application key or the executor's credentials.

Export the instance profile from API Explorer with instance management permission, then use echo-mock export below to fetch the selected rules while online. The library can read and validate the resulting file:

dotnet new console -n EchoOfflineDemo -f net10.0
dotnet add EchoOfflineDemo package Gapfy.Echo.Offline --version 0.2.10
using Gapfy.Echo.Offline;

var snapshot = await EchoOfflineSnapshotCodec.ReadAsync("echo.snapshot.json");
EchoOfflineSnapshotCodec.Validate(snapshot);
Console.WriteLine(snapshot.ContentHash);

Validation checks the content hash, contracts and complete route coverage before execution. SHA-256 detects changes to the snapshot; it does not identify its author or grant access. Treat response bodies in the file as test data that may need restricted access. A snapshot with incomplete rules or no mock selection cannot run.

An application importing the snapshot through API Explorer must explicitly configure offline mode and match its application, workspace, profile and endpoint identities. An organization snapshot must also match that organization. Imported profiles are read-only and do not refresh a remote catalog.

Gapfy.Echo.Mock.Runner

Installs the echo-mock command for running prepared HTTP snapshots. It is separate from gapfy-echo, which runs collections and chains.

dotnet tool install --global Gapfy.Echo.Mock.Runner --version 0.2.10
echo-mock export --profile echo.mock.json --server https://api.example.test/ --key-env ECHO_API_KEY --output echo.snapshot.json --seed 17
echo-mock validate --snapshot echo.snapshot.json --report validation.json
echo-mock run --snapshot echo.snapshot.json --report execution.json --timeout 300 -- dotnet Tests.dll --minimum-expected-tests 1

Replace the example API address with your configured Echo API address. Set ECHO_API_KEY through the environment or your CI secret store; the export command accepts the variable's name, not the secret as an argument. The key needs ServeMocks and access to the paid rule feed. Prepare the tool, .NET runtime and compiled tests before the offline phase.

run validates the snapshot, opens a private loopback listener and starts the command after --. Use your project's actual test command; the example expects a compiled Microsoft.Testing.Platform test application and requires at least one test. The child receives ECHO_MOCK_BASE_ADDRESS and the Echo__Offline__* settings. API Explorer consumers use the imported profile; direct HTTP consumers use /servers/{serverId} followed by the rule's path.

A fixed seed stabilizes random values. Date-dependent generators can still change over time; use constant dates when exact repetition matters. Unknown routes return 404 instead of contacting a real service. The runner does not block unrelated network traffic from the tested application.

Use serve --snapshot echo.snapshot.json --port 0 --ready-file ready.json when another process needs to manage the run. The ready file and /_echo/ready identify the validated snapshot. Cancellation or timeout stops the runner's listener and child process. The JSON report records the snapshot hash, route counts and child result without request bodies or credentials. Exit codes are 0 for success, 2 for invalid input, 3 for runtime or unmatched-route failures, and 130 for cancellation; a failing child preserves its own exit code.

Choose the right path