DCTekSolutions.ExternalCallGuard.Abstractions
1.0.0
dotnet add package DCTekSolutions.ExternalCallGuard.Abstractions --version 1.0.0
NuGet\Install-Package DCTekSolutions.ExternalCallGuard.Abstractions -Version 1.0.0
<PackageReference Include="DCTekSolutions.ExternalCallGuard.Abstractions" Version="1.0.0" />
<PackageVersion Include="DCTekSolutions.ExternalCallGuard.Abstractions" Version="1.0.0" />
<PackageReference Include="DCTekSolutions.ExternalCallGuard.Abstractions" />
paket add DCTekSolutions.ExternalCallGuard.Abstractions --version 1.0.0
#r "nuget: DCTekSolutions.ExternalCallGuard.Abstractions, 1.0.0"
#:package DCTekSolutions.ExternalCallGuard.Abstractions@1.0.0
#addin nuget:?package=DCTekSolutions.ExternalCallGuard.Abstractions&version=1.0.0
#tool nuget:?package=DCTekSolutions.ExternalCallGuard.Abstractions&version=1.0.0
ExternalCallGuard.Abstractions
ExternalCallGuard.Abstractions by DC Tek Solutions contains the small, provider-neutral contract layer shared by the ExternalCallGuard package family. Use it when a library needs to describe guarded work, invoke an injected guard, or consume telemetry without taking a dependency on Polly, NLog, or the core resilience implementation.
Free to use in personal and commercial projects. This package is closed-source proprietary software; see LICENSE.md.
Install
dotnet add package DCTekSolutions.ExternalCallGuard.Abstractions --version 1.0.0
Most applications should install DCTekSolutions.ExternalCallGuard, which brings this package in automatically. Install Abstractions directly in reusable integration libraries, shared domain-facing services, and telemetry adapters that should not depend on a specific implementation.
Package family
| Package | Responsibility |
|---|---|
DCTekSolutions.ExternalCallGuard.Abstractions |
Stable interfaces, call descriptors, policy keys, exceptions, and semantic telemetry events. |
DCTekSolutions.ExternalCallGuard |
Dependency injection, retry, timeout, circuit breaker, cancellation, and event dispatch. |
DCTekSolutions.ExternalCallGuard.NLogTelemetrySink |
Optional structured NLog adapter for the semantic events. |
What is included?
| Type | What it does |
|---|---|
IExternalCallGuard |
Executes an asynchronous external operation through the implementation configured by the host application. |
ExternalCallDescriptor |
Gives the operation a stable dependency name, operation name, policy selection, and optional safe metadata. |
ExternalCallPolicyKey |
Identifies a named policy without exposing the resilience library used to implement it. |
ExternalCallPolicyKeys.Default |
Selects the host application's default policy. It is also used when no policy is supplied. |
IExternalCallTelemetrySink |
Receives lifecycle events from guarded calls. Implement this to connect another monitoring system. |
ExternalCallTelemetryEvent and subclasses |
Represent started, retry, succeeded, failed, cancelled, and timed-out outcomes. |
ExternalCallGuardConfigurationException |
Reports invalid guard configuration, such as an unknown policy key. |
Public API reference
IExternalCallGuard
IExternalCallGuard is the execution boundary used by application and library code. The host application supplies an implementation and decides which retry, timeout, and circuit-breaker behavior each policy key selects.
| Method | In simple terms | Returns |
|---|---|---|
ExecuteAsync(call, operation, cancellationToken) |
Runs asynchronous work that has no result value. | A Task that completes when the work completes. |
ExecuteAsync<TResult>(call, operation, cancellationToken) |
Runs asynchronous work and passes its result back to the caller. | A Task<TResult> containing the operation's result. |
Both methods use the same parameters:
| Parameter | Meaning |
|---|---|
call |
An ExternalCallDescriptor containing the stable call name, selected policy, and safe telemetry metadata. |
operation |
The asynchronous delegate to execute. It receives the token that must be passed to the external API or other cancellable work. |
cancellationToken |
The caller's optional request to stop waiting for and cancel the guarded operation. |
Use the non-result overload for commands:
var call = new ExternalCallDescriptor("SearchApi", "RefreshIndex");
await externalCallGuard.ExecuteAsync(
call,
ct => searchClient.RefreshIndexAsync(ct),
cancellationToken);
Use the generic overload for queries:
var call = new ExternalCallDescriptor("WeatherApi", "GetForecast");
string forecast = await externalCallGuard.ExecuteAsync(
call,
ct => weatherClient.GetForecastAsync(ct),
cancellationToken);
IExternalCallTelemetrySink
IExternalCallTelemetrySink receives provider-neutral lifecycle events. Implement it to translate those events into metrics, traces, logs, audit records, or test observations.
Its WriteAsync(telemetry, cancellationToken) method has these parameters:
| Parameter | Meaning |
|---|---|
telemetry |
The started, retry, success, failure, cancellation, or timeout event to process. |
cancellationToken |
A request to stop processing the event promptly. |
The method returns a ValueTask that completes when the sink has finished processing the event. The custom telemetry sink example shows a complete implementation.
ExternalCallDescriptor
ExternalCallDescriptor gives one kind of external operation a stable identity and selects its host-configured policy. Construct it with:
new ExternalCallDescriptor(dependency, operation, policy, metadata)
| Constructor parameter | Meaning |
|---|---|
dependency |
Required stable name for the external system, such as ShippingApi. Null, empty, and whitespace values cause ArgumentException. |
operation |
Required stable name for the action, such as GetRate. Null, empty, and whitespace values cause ArgumentException. |
policy |
Optional ExternalCallPolicyKey. Omitting it or passing the default struct value selects ExternalCallPolicyKeys.Default. |
metadata |
Optional caller-defined, non-sensitive telemetry context. The descriptor makes a read-only, shallow copy of the dictionary; referenced values are not cloned. |
Its read-only properties expose the corresponding values:
| Property | Meaning |
|---|---|
Dependency |
The external system's stable name. |
Operation |
The action's stable name. |
Policy |
The key used by the host to select resilience behavior. |
Metadata |
The copied, read-only telemetry context. |
See Describe calls clearly for a complete construction example and metadata safety guidance.
ExternalCallPolicyKey and ExternalCallPolicyKeys
ExternalCallPolicyKey is a small value object containing a policy name. Construct it with a non-empty value; null, empty, and whitespace values cause ArgumentException. A key selects settings registered by the host—it does not contain those settings.
| Member | Meaning |
|---|---|
Value |
The policy name. The default struct value returns an empty string. |
Equals(ExternalCallPolicyKey) |
Compares two keys by ordinal, case-sensitive name. |
Equals(object) |
Returns true only when the object is an equal policy key. |
GetHashCode() |
Produces a matching ordinal, case-sensitive hash code for dictionaries and sets. |
ToString() |
Returns Value. |
== and != |
Compare two keys using the same case-sensitive rules. |
ExternalCallPolicyKeys.Default |
The built-in "Default" key used when a descriptor does not specify another key. |
var readOnly = new ExternalCallPolicyKey("Weather.ReadOnly");
var sameKey = new ExternalCallPolicyKey("Weather.ReadOnly");
var differentCase = new ExternalCallPolicyKey("weather.readonly");
bool namesMatch = readOnly.Equals(sameKey); // true
bool operatorsMatch = readOnly == sameKey; // true
bool casingIsSignificant = readOnly != differentCase; // true
string policyName = readOnly.ToString(); // Weather.ReadOnly
var registrations = new Dictionary<ExternalCallPolicyKey, string>
{
[readOnly] = "registered"
};
Telemetry models
ExternalCallTelemetryEvent is the abstract base class for all lifecycle events. Its constructor values become three read-only properties shared by every event:
| Constructor parameter / property | Meaning |
|---|---|
call / Call |
The descriptor identifying the operation. A null value causes ArgumentNullException. |
timestamp / Timestamp |
The UTC-aware date and time at which this lifecycle event occurred. |
duration / Duration |
The elapsed time recorded for the call when the event occurred. It is zero for a started event. |
Each sealed event adds details for one lifecycle stage:
| Event type | In simple terms | Constructor-specific parameters and properties |
|---|---|---|
ExternalCallStartedEvent |
The guarded call is about to begin. | Accepts call and timestamp; inherited Duration is always zero. |
ExternalCallRetryEvent |
An attempt failed and another attempt is planned. | attempt / Attempt is the one-based failed attempt number; retryDelay / RetryDelay is the wait before the next attempt; exception / Exception is the failure that caused the retry. |
ExternalCallSucceededEvent |
The call finished successfully. | attemptCount / AttemptCount is the total attempts, including the successful one. |
ExternalCallFailedEvent |
The call ended with an exception and will not be retried. | exception / Exception is the final failure; attemptCount / AttemptCount is the total attempts made. |
ExternalCallCancelledEvent |
The call stopped because cancellation was requested. | exception / Exception is the OperationCanceledException; attemptCount / AttemptCount is the number of attempts started. |
ExternalCallTimedOutEvent |
The call exceeded its configured time limit. | exception / Exception represents the timeout; attemptCount / AttemptCount is the number of attempts started. |
The guard implementation normally creates these models. Tests, guard implementations, and telemetry adapter tests can construct them directly:
var call = new ExternalCallDescriptor("WeatherApi", "GetForecast");
var now = DateTimeOffset.UtcNow;
var elapsed = TimeSpan.FromMilliseconds(250);
var requestFailure = new HttpRequestException("The dependency was unavailable.");
ExternalCallTelemetryEvent[] examples =
{
new ExternalCallStartedEvent(call, now),
new ExternalCallRetryEvent(
call, now, elapsed, attempt: 1,
retryDelay: TimeSpan.FromSeconds(1),
exception: requestFailure),
new ExternalCallSucceededEvent(call, now, elapsed, attemptCount: 2),
new ExternalCallFailedEvent(call, now, elapsed, requestFailure, attemptCount: 3),
new ExternalCallCancelledEvent(
call, now, elapsed,
new OperationCanceledException("The caller cancelled the operation."),
attemptCount: 1),
new ExternalCallTimedOutEvent(
call, now, elapsed,
new TimeoutException("The configured time limit was exceeded."),
attemptCount: 2)
};
The call and exception parameters cannot be null. Event constructors preserve the supplied timestamp, duration, and attempt values; the component creating telemetry is responsible for supplying values with the documented meanings.
ExternalCallGuardConfigurationException
ExternalCallGuardConfigurationException means that the host's guard setup is invalid or incomplete—for example, a descriptor selected a policy key that was never registered. It is not an external dependency failure.
| Constructor | Parameters |
|---|---|
ExternalCallGuardConfigurationException(message) |
message explains the configuration problem. |
ExternalCallGuardConfigurationException(message, innerException) |
message explains the problem and innerException preserves the lower-level cause. |
throw new ExternalCallGuardConfigurationException(
"No external-call policy is registered with the key 'Weather.ReadOnly'.");
// Preserve a lower-level error when it explains why configuration could not load.
throw new ExternalCallGuardConfigurationException(
"The external-call timeout is not a valid duration.",
formatException);
Use the guard from a reusable library
Your library can depend on the interface while the consuming application decides how resilience is implemented:
using DCTekSolutions.ExternalCallGuard.Abstractions.Interfaces;
using DCTekSolutions.ExternalCallGuard.Abstractions.Models;
public sealed class WeatherGateway(
HttpClient httpClient,
IExternalCallGuard externalCallGuard)
{
public Task<string> GetForecastAsync(
string postalCode,
CancellationToken cancellationToken = default)
{
var call = new ExternalCallDescriptor(
dependency: "WeatherApi",
operation: "GetForecast",
policy: WeatherPolicies.ReadOnly);
return externalCallGuard.ExecuteAsync(
call,
async ct =>
{
using var response = await httpClient.GetAsync(
$"forecast/{Uri.EscapeDataString(postalCode)}",
ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
},
cancellationToken);
}
}
The host application installs DCTekSolutions.ExternalCallGuard, registers it with dependency injection, and defines the ReadOnly policy. Your gateway stays independent of those implementation details.
Define policy keys once
Policy keys are value objects. Keep shared keys in one place so names cannot drift between registration and use:
using DCTekSolutions.ExternalCallGuard.Abstractions.Models;
public static class WeatherPolicies
{
public static readonly ExternalCallPolicyKey ReadOnly = new("Weather.ReadOnly");
public static readonly ExternalCallPolicyKey Mutation = new("Weather.Mutation");
}
A policy key only selects configuration. It does not itself contain retry, timeout, or circuit-breaker settings. Those settings belong to the host application's core ExternalCallGuard registration.
Describe calls clearly
var call = new ExternalCallDescriptor(
dependency: "ShippingApi",
operation: "GetRate",
policy: ShippingPolicies.ReadOnly,
metadata: new Dictionary<string, object?>
{
["carrier"] = "ExampleCarrier",
["region"] = "east"
});
Dependencyidentifies the remote system, such asShippingApiorCustomerDatabase.Operationidentifies the stable action, such asGetRateorLoadCustomer.Policyselects behavior registered by the application. Omitting it selectsDefault.Metadataadds low-cardinality context for telemetry. The descriptor makes its own read-only copy.
Use stable names instead of IDs or URLs. Never include credentials, tokens, connection strings, request/response bodies, personal information, or other secrets.
Build a custom telemetry sink
A sink can translate semantic events into metrics, tracing, audit records, another logger, or a test probe:
using DCTekSolutions.ExternalCallGuard.Abstractions.Interfaces;
using DCTekSolutions.ExternalCallGuard.Abstractions.Models;
public sealed class MetricsTelemetrySink(IMetrics metrics)
: IExternalCallTelemetrySink
{
public ValueTask WriteAsync(
ExternalCallTelemetryEvent telemetry,
CancellationToken cancellationToken = default)
{
var tags = new Dictionary<string, string>
{
["dependency"] = telemetry.Call.Dependency,
["operation"] = telemetry.Call.Operation,
["policy"] = telemetry.Call.Policy.Value
};
switch (telemetry)
{
case ExternalCallStartedEvent:
metrics.Increment("external_call_started", tags);
break;
case ExternalCallRetryEvent retry:
metrics.Increment("external_call_retry", tags);
metrics.Record("external_call_retry_delay_ms",
retry.RetryDelay.TotalMilliseconds,
tags);
break;
case ExternalCallSucceededEvent:
metrics.Increment("external_call_success", tags);
break;
case ExternalCallFailedEvent:
metrics.Increment("external_call_failure", tags);
break;
case ExternalCallTimedOutEvent:
metrics.Increment("external_call_timeout", tags);
break;
case ExternalCallCancelledEvent:
metrics.Increment("external_call_cancelled", tags);
break;
}
metrics.Record("external_call_duration_ms",
telemetry.Duration.TotalMilliseconds,
tags);
return ValueTask.CompletedTask;
}
}
Register it in the host application's dependency injection container:
services.AddSingleton<IExternalCallTelemetrySink, MetricsTelemetrySink>();
Multiple sinks are supported by the core package. They run in registration order, and a broken or slow sink does not replace the result or exception from the business operation.
Test code that depends on the abstraction
A simple fake keeps unit tests independent of resilience timing:
public sealed class PassThroughExternalCallGuard : IExternalCallGuard
{
public Task ExecuteAsync(
ExternalCallDescriptor call,
Func<CancellationToken, Task> operation,
CancellationToken cancellationToken = default) =>
operation(cancellationToken);
public Task<TResult> ExecuteAsync<TResult>(
ExternalCallDescriptor call,
Func<CancellationToken, Task<TResult>> operation,
CancellationToken cancellationToken = default) =>
operation(cancellationToken);
}
Use the real core implementation in integration tests that need to verify retry, timeout, circuit-breaker, or telemetry behavior.
Compatibility and design guarantees
- Single-targets .NET Standard 2.1 and can be consumed by runtimes that implement that standard. .NET Framework does not implement .NET Standard 2.1.
- Exposes no Polly, Microsoft resilience, NLog, HTTP-client, SQL-client, or application-specific types.
- Contains no retry engine and performs no network or file I/O by itself.
- Supports both operations that return a result and operations that return only
Task. - Uses framework-neutral telemetry models so adapters can live in separate packages.
License
Free for personal and commercial use under the included DC Tek Solutions license. Package license acceptance is required during installation.
Support / Donate
ExternalCallGuard is developed by DC Tek Solutions. For documentation and support, visit the ExternalCallGuard support wiki.
If this package saves you time, please consider supporting continued development:
Thank you for your support!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on DCTekSolutions.ExternalCallGuard.Abstractions:
| Package | Downloads |
|---|---|
|
DCTekSolutions.ExternalCallGuard.NLogTelemetrySink
Optional structured NLog telemetry for the ExternalCallGuard package family. Record retries, failures, cancellations, timeouts, attempts, duration, dependency, operation, and policy without coupling the core guard to NLog. Free to use; donations appreciated. |
|
|
DCTekSolutions.ExternalCallGuard
A reusable resilience and observability boundary for external operations. Add configurable retry, timeout, circuit-breaker, cancellation, and semantic telemetry behavior around APIs, databases, files, cloud services, and third-party SDK calls. Free to use; donations appreciated. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 96 | 9/21/2026 |
Initial standalone release of the ExternalCallGuard contracts and semantic telemetry models.