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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DCTekSolutions.ExternalCallGuard.Abstractions" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DCTekSolutions.ExternalCallGuard.Abstractions" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DCTekSolutions.ExternalCallGuard.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DCTekSolutions.ExternalCallGuard.Abstractions --version 1.0.0
                    
#r "nuget: DCTekSolutions.ExternalCallGuard.Abstractions, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DCTekSolutions.ExternalCallGuard.Abstractions@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DCTekSolutions.ExternalCallGuard.Abstractions&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DCTekSolutions.ExternalCallGuard.Abstractions&version=1.0.0
                    
Install as a Cake Tool

ExternalCallGuard.Abstractions

NuGet NuGet Downloads Target

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"
    });
  • Dependency identifies the remote system, such as ShippingApi or CustomerDatabase.
  • Operation identifies the stable action, such as GetRate or LoadCustomer.
  • Policy selects behavior registered by the application. Omitting it selects Default.
  • Metadata adds 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:

Buy Me A Coffee

Thank you for your support!

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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.