Route store integrations using trusted configuration
Choose an integration provider from trusted Store settings, respect an explicit disabled route, and reject unsupported destinations before dispatch.
In this guide
- Authorized business object
- Trusted store
- Owned route policy
- Registered handler
- Bounded transport
Two stores, one integration boundary
A commerce platform serves two stores that submit orders to different operational systems. Reusing the integration boundary is sensible. Allowing a request to name the destination URL is not. A caller who can change routing can send a valid order to the wrong business system even when the payload itself passes validation.
Make store context authoritative and routing configuration server-owned. The request describes the business operation. The application determines where that operation is allowed to go. Keep those responsibilities separate from the first adapter method through the eventual provider transport.
Resolve the store from the business object
For an order operation, resolve the stored order through an authorized supported API and derive its store from that record. Do not assume the current browser store is the order's store. Background processing may have no browser context at all, and an operator may work across several stores.
For other operations, document the trusted source of store identity in the target contract. If the source is missing or ambiguous, stop rather than selecting a convenient default. Mixed-store batches need to be rejected or partitioned deliberately before provider dispatch, with each item retaining its own authorized context.
Resolve a finite registered provider
Call StoreRoutePolicy.Resolve with the operation name, an ordered list of server-owned settings, and the registered provider/operation pairs. It skips blank settings and uses the first nonblank setting. Disabled stops routing explicitly; an unregistered pair fails rather than falling through to another destination.
Build the setting list in your documented precedence order, such as operation-specific, integration-family, then default. Resolve the Store from the authorized business object before loading it. Register pairs with ordinal comparison so provider and operation names match exactly.
Routing/StoreRoutePolicy.cs csharp
using System;
using System.Collections.Generic;
namespace Gcg.DeveloperExamples.Routing
{
public sealed class RouteResult
{
public string Provider { get; }
public bool Disabled { get; }
public RouteResult(string provider, bool disabled)
{
Provider = provider;
Disabled = disabled;
}
}
public static class StoreRoutePolicy
{
public static RouteResult Resolve(string operation, IEnumerable<string> orderedSettings,
ISet<string> registeredProviderOperations)
{
if (string.IsNullOrWhiteSpace(operation))
throw new ArgumentException("Operation required.");
foreach (var setting in orderedSettings)
{
if (string.IsNullOrWhiteSpace(setting))
continue;
if (string.Equals(setting, "Disabled", StringComparison.Ordinal))
return new RouteResult(null, true);
if (!registeredProviderOperations.Contains(setting + ":" + operation))
throw new InvalidOperationException("Provider does not support this operation.");
return new RouteResult(setting, false);
}
throw new InvalidOperationException("No integration route is configured.");
}
}
}
// Example server-owned configuration:
// Resolve("orders.create", new[] { null, "North", "Fallback" },
// new HashSet<string>(StringComparer.Ordinal) { "North:orders.create" });
// Read settings for the store resolved from the authorized order, never request fields.
For operation 'orders.create' and settings [null, 'North', 'Fallback'], a registration for 'North:orders.create' returns Provider 'North' with Disabled false. If North does not support that operation, the method throws and never chooses Fallback. With 'Disabled' as the first nonblank value, it returns Disabled true. Call this policy from the actual configured integration handler; legacy touchpoints and Data Exchange processors have different invocation contracts.
Separate routing from delivery mechanics
Routing chooses the provider. Delivery decides when to attempt the command and how to recover from failure. Record the resolved Store, operation, and provider with the durable delivery so operators can explain the original decision.
Choose whether an authorized replay keeps that route or uses a reviewed replacement. Do not accept a destination URL from the caller, and do not turn a narrowly configured provider adapter into a general HTTP proxy.
Test the negative configuration cases
Create two fake providers that record only sanitized metadata. Send equivalent operations for two fictional stores and verify the intended provider receives each. Attempt to override the provider through a request body field, header, or query parameter; the route must remain server-owned.
Then test explicit Disabled, no configuration, unknown provider, unsupported operation, a cross-store order lookup, and an unavailable configuration source. Exercise a mixed-store batch if it is supported. Record the expected outcome for every case, including whether work is rejected before enqueueing or held for an operational correction.
- Bound configuration reads and response sizes.
- Cache successful configuration only under a documented freshness policy.
- Never put credentials in store attributes or public diagnostics.
Make route changes reviewable
Expose a safe operational summary of store, operation, configured provider identifier, and enabled state. Keep actual destinations and secrets restricted. Log a correlation reference and sanitized routing failure category so support can identify a configuration problem without reading the order payload.
A store-aware integration becomes maintainable when its decisions are deterministic and explainable. The strongest design has few route levels, exact provider registration, and a deliberate answer for missing or disabled configuration. That prevents a helpful-looking fallback from turning into a shipment, invoice, or order in the wrong system.
Configure one reusable HTTP client per integration
Create one client for a provider URI selected from server configuration, then reuse it for that provider's lifetime. The .NET 8 factory requires an HTTPS base URI ending in '/', disables redirects and cookies, and sets connection and request timeouts. Use the provider's approved relative request paths and add credentials from server secrets. The base URI check does not authorize arbitrary destinations or impose a response-size cap; those remain responsibilities of the fixed provider adapter.
Routing/IntegrationHttpClient.cs csharp
using System;
using System.Net.Http;
public static class IntegrationHttpClient
{
public static HttpClient Create(Uri configuredBaseUri)
{
ArgumentNullException.ThrowIfNull(configuredBaseUri);
if (!configuredBaseUri.IsAbsoluteUri || configuredBaseUri.Scheme != Uri.UriSchemeHttps ||
configuredBaseUri.UserInfo.Length != 0 || configuredBaseUri.Query.Length != 0 ||
configuredBaseUri.Fragment.Length != 0 || !configuredBaseUri.AbsolutePath.EndsWith("/"))
throw new ArgumentException("Use a configured HTTPS base URI ending in '/'.");
var handler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
UseCookies = false,
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
ConnectTimeout = TimeSpan.FromSeconds(2),
MaxConnectionsPerServer = 8
};
return new HttpClient(handler, disposeHandler: true)
{
BaseAddress = configuredBaseUri,
Timeout = TimeSpan.FromSeconds(5)
};
}
}
References and further reading
Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.