> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# .NET SDK - Design decisions and deviations

> Where the .NET SDK departs from what a .NET developer would expect, and the reasoning behind each departure.

This SDK departs from a few things a .NET developer would reasonably expect. Each departure is deliberate; none is an oversight. They are written down here so you can judge them rather than discover them.

## 1. Secrets are not in `IConfiguration` by default

**The most important entry on this page.**

If you know the Azure Key Vault configuration provider, you will expect Nexus secrets to appear in `IConfiguration` the same way. They do not, and the reason is mechanical rather than stylistic.

**.NET has no way to mark a configuration value as secret.** Once a value is in the configuration tree:

* the parameterless `IConfigurationRoot.GetDebugView()` prints it,
* `configuration.AsEnumerable()` walks it,
* the developer exception page displays it.

There is a `GetDebugView(Func<ConfigurationDebugViewContext, string>)` overload that can mask values by originating provider - `ConfigurationDebugViewContext` exposes the `ConfigurationProvider` - but it only helps **if the caller opts into it**, and an SDK cannot force that. On the conventional design, whether your Stripe key ends up in a log line or an error page depends on the discipline of every piece of code that touches the configuration root, including code you did not write.

Our path removes the exposure **structurally** rather than by convention: `IOptions<T>` does not require `IConfiguration`, so [`AddWestyxNexusSecrets<T>()`](/sdks/dotnet/secrets-binding) satisfies it through a factory and the value never enters the configuration tree. You still get constructor injection, strong typing and live reload - you just cannot accidentally dump it.

An opt-in escape hatch exists (`AddWestyx(..., includeSecrets: true)`, default off) with a `GetNexusSafeDebugView()` masking helper. See [Secrets binding](/sdks/dotnet/secrets-binding) for exactly what you accept by enabling it.

## 2. `NexusSecretValue` was considered and rejected

A wrapper type whose `ToString()` returns `"[REDACTED]"` was proposed and turned down. It is a breaking change to `GetSecretAsync`, and in the case that actually matters - a secret bound into a POCO `string` property - it provides no protection whatsoever. A half-measure that creates false confidence is worse than no measure.

## 3. Extension methods live in `Microsoft.Extensions.DependencyInjection`

Microsoft's guidance says extension methods should not be placed in that namespace unless you are authoring an official Microsoft package. We deviate knowingly, because the rest of the ecosystem already does: Serilog, Polly and the AWS and Azure SDKs all register their `Add*` extensions there.

Following the letter of the guidance while every neighbouring package ignores it would mean `AddWestyxNexus()` fails to appear when you type `services.` with your usual usings - which is exactly the discoverability problem this release set out to fix.

## 4. `INexusLogger` is obsolete but retained

It is marked `[Obsolete]`, not removed. Removing it in the same release that introduces `LoggerFactory` would break every existing consumer for no benefit; the bridge costs one small adapter class.

If both `Logger` and `LoggerFactory` are set, `LoggerFactory` wins and the SDK logs a warning once at construction. Removal is a separate, later decision.

## 5. `Microsoft.Extensions.Configuration` left the core package

This is a **breaking change**, taken because a console app or worker service should not carry the configuration pipeline it never touches. `AddWestyx()` now lives in `WestyxNexus.Extensions.Configuration`.

Migration is one line in your project file - add the package reference. The namespace is still `Westyx.Nexus`, so no `using` changes.

## 6. `PooledConnectionLifetime` defaults to 2 minutes

`SocketsHttpHandler.PooledConnectionLifetime` defaults to `InfiniteTimeSpan`. Because `NexusClient` is meant to live for the lifetime of the application, that default means its pooled connections are never recycled and it never observes a DNS change - a real scenario behind an ingress or a load balancer.

Two minutes matches the `IHttpClientFactory` default handler lifetime, so the standalone and dependency-injection paths behave the same. Tune it with `NexusConfig.PooledConnectionLifetime`; it is ignored when you supply your own `HttpClient`, because that client's handler is yours to configure.

## 7. `IHttpClientFactory` handler rotation is safe for SSE

Rotating handlers every two minutes looks fatal for a connection that stays open for hours. It is not: the factory only stops handing an expired handler to **new** requests; a request already in flight keeps its handler until it completes.

Do not "fix" this with `SetHandlerLifetime(Timeout.InfiniteTimeSpan)` - that reintroduces the stale-DNS problem the factory exists to solve.

## 8. FeatureManagement maps boolean state only

Only a flag's `is_active` state reaches `IFeatureManager`. Percentage rollout and cohort targeting are evaluated server-side against a user context and are not routed through the bridge.

A targeting-aware flag exposed through a boolean-only bridge would silently evaluate as its untargeted default, which is worse than being unsupported. See [Feature management](/sdks/dotnet/feature-management) for how to evaluate targeted flags today.

## 9. Both target frameworks, and the `net8.0` clock

The packages target `net8.0` and `net10.0`. .NET 8 goes out of support on **2026-11-10**; .NET 10 is supported to **2028-11-14**. `net8.0` is kept so consumers are not stranded mid-support-window, and will be dropped after that date.

## 10. File-type secrets are owner-readable only

Materialised file secrets are created with mode `0600`, set at creation via `UnixCreateMode` rather than with a `chmod` afterwards - a chmod-after leaves a window in which the file exists with the process umask applied, typically `0644` in a shared temp directory.

Each client instance also gets its own file path. The path used to be derived only from the secret's key and value, so two clients holding the same secret resolved to one file and whichever was disposed first deleted it out from under the other.

On Windows there is no equivalent mode; the file inherits the temp directory ACL.
