Skip to content

feat(dotnet-sdk): per-request custom headers#640

Merged
evansims merged 8 commits intomainfrom
feat/dotnet/per-request-custom-headers
Oct 21, 2025
Merged

feat(dotnet-sdk): per-request custom headers#640
evansims merged 8 commits intomainfrom
feat/dotnet/per-request-custom-headers

Conversation

@evansims
Copy link
Contributor

@evansims evansims commented Oct 14, 2025

Description

This pull request introduces support for per-request custom headers to the .NET SDK.

  • Added DefaultHeaders property to ClientConfiguration that applies to all requests
  • Headers are validated during configuration initialization via EnsureValid()
  • Added Headers property to the ClientRequestOptions interface and all implementing classes (e.g. ClientCheckOptions, ClientWriteOptions, etc.)
  • Per-request headers override default headers when both define the same key

The core changes were implemented in ClientConfiguration.cs, ClientRequestOptions.cs, ApiClient.cs, Client.cs. Options models were also updated to support the new Headers property. Tests were also added for comprehensive coverage of the changes.

Example of Changes

Before

Previously, there was no way to add custom headers to individual requests.

Developers had to use workarounds like wrapping the HttpClient:

// Example, create a custom DelegatingHandler to inject headers
public class TracingHandler : DelegatingHandler {
    private readonly AsyncLocal<Dictionary<string, string>> _headers = new();

    public void SetHeaders(Dictionary<string, string> headers) {
        _headers.Value = headers;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken) {
        if (_headers.Value != null) {
            foreach (var header in _headers.Value) {
                request.Headers.Add(header.Key, header.Value);
            }
        }
        return await base.SendAsync(request, cancellationToken);
    }
}

// Setup (not thread-safe for different headers per request)
var tracingHandler = new TracingHandler();
var httpClient = new HttpClient(tracingHandler);
var config = new ClientConfiguration() {
    ApiUrl = "https://api.fga.example",
    StoreId = "01H0H015178Y2V4CX10C2KGHF4"
};
var client = new OpenFgaClient(config, httpClient);

// Before each request
tracingHandler.SetHeaders(new Dictionary<string, string> {
    { "X-Example-1", "req-12345" },
    { "X-Example-2", "trace-67890" },
    { "X-Example-3", "corr-abc123" }
});

var response = await client.Check(new ClientCheckRequest {
    User = "user:anne",
    Relation = "reader",
    Object = "document:budget"
});

The previous ClientConfiguration implementation of DefaultHeaders only allowed the User-Agent string to be changed:

// Could only set User-Agent via DefaultHeaders, not other custom headers
// No way to vary headers per request
var config = new ClientConfiguration() {
    ApiUrl = "https://api.fga.example",
    StoreId = "01H0H015178Y2V4CX10C2KGHF4"
};
config.DefaultHeaders["User-Agent"] = "MyApp/1.0"; // Only User-Agent was customizable

var client = new OpenFgaClient(config);
// No way to add request-specific headers like X-Request-ID

After

Per-request custom headers are supported on individual requests:

var config = new ClientConfiguration() {
    ApiUrl = "https://api.fga.example",
    StoreId = "01H0H015178Y2V4CX10C2KGHF4"
};
var client = new OpenFgaClient(config);

var options = new ClientCheckOptions {
    Headers = new Dictionary<string, string> {
        { "X-Example-1", "req-12345" },
        { "X-Example-2", "trace-67890" },
        { "X-Example-3", "corr-abc123" }
    }
};

var response = await client.Check(
    new ClientCheckRequest {
        User = "user:anne",
        Relation = "reader",
        Object = "document:budget"
    },
    options
);

DefaultHeaders on ClientConfiguration now allows broad customization:

// Set headers that apply to all requests
var config = new ClientConfiguration() {
    ApiUrl = "https://api.fga.example",
    StoreId = "01H0H015178Y2V4CX10C2KGHF4"
};
config.DefaultHeaders["X-App-Name"] = "MyApp";
config.DefaultHeaders["X-Environment"] = "production";
config.DefaultHeaders["X-Session-ID"] = sessionId;

var client = new OpenFgaClient(config);

// Make requests with default headers
await client.Check(new ClientCheckRequest {
    User = "user:anne",
    Relation = "reader",
    Object = "document:budget"
});
// ^ Request includes: X-App-Name, X-Environment, X-Session-ID

// Override or add additional headers for specific requests
var options = new ClientCheckOptions {
    Headers = new Dictionary<string, string> {
        { "X-Request-ID", "req-unique-123" },  // Request-specific
        { "X-Environment", "staging" }          // Overrides default
    }
};

await client.Check(
    new ClientCheckRequest {
        User = "user:bob",
        Relation = "writer",
        Object = "document:report"
    },
    options
);
// ^ Request includes: X-App-Name, X-Session-ID, X-Request-ID, X-Environment="staging" (overridden)

References

Generates → openfga/dotnet-sdk#133

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features
    • Add support for custom HTTP headers across the .NET client. Set default headers via configuration and per-request headers via options; per-request values override defaults. Applies to operations like Check, Read/Write, Expand, List*, Assertions, and Store management.
  • API Changes
    • Added optional headers parameter to API methods and Headers properties to request option classes.
  • Validation
    • Enforce header rules: block reserved headers, reject empty names, null values, and newline characters, with clear errors.
  • Tests
    • Expanded tests for header propagation, precedence, and validation.

@evansims evansims requested a review from a team as a code owner October 14, 2025 20:03
@evansims evansims added enhancement New feature or request dotnet-sdk Affects the C#/DotNet SDK labels Oct 14, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds per-request custom header support across the .NET client templates: new Headers properties on request option classes, header validation in ClientConfiguration, extraction in Client, propagation through generated API methods, and merged header handling in ApiClient. Updates tests to cover validation, precedence, and propagation across operations.

Changes

Cohort / File(s) Summary
Client header extraction and wiring
config/clients/dotnet/template/Client/Client.mustache
Adds private static ExtractHeaders helper (validates via ClientConfiguration.ValidateHeaders) and passes extracted headers into API calls across read/write/list/check operations. Minor formatting and tuple key formatting fixes.
Header validation in configuration
config/clients/dotnet/template/Client/ClientConfiguration.mustache
Introduces ReservedHeaders and internal ValidateHeaders; EnsureValid now validates DefaultHeaders. Blocks empty names, null values, CR/LF in values, and reserved headers; updates XML docs.
API client header plumbing and merging
config/clients/dotnet/template/Client_ApiClient.mustache, config/clients/dotnet/template/api.mustache
Adds perRequestHeaders parameter to SendRequestAsync overloads and generated API methods; introduces BuildHeaders to merge OAuth token with per-request headers; updates call sites and docs.
Request options: add Headers property
config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache, .../ClientCheckOptions.mustache, .../ClientCreateStoreOptions.mustache, .../ClientExpandOptions.mustache, .../ClientListObjectsOptions.mustache, .../ClientListRelationsOptions.mustache, .../ClientListStoresOptions.mustache, .../ClientListUsersOptions.mustache, .../ClientReadAssertionsOptions.mustache, .../ClientReadAuthorizaionModelOptions.mustache, .../ClientReadAuthorizaionModelsOptions.mustache, .../ClientReadChangesOptions.mustache, .../ClientReadOptions.mustache, .../ClientWriteAssertionsOptions.mustache, .../ClientWriteOptions.mustache
Adds public IDictionary<string,string>? Headers to concrete request option classes; updates usings to include System.Collections.Generic and templated model namespace where applicable.
Request options interface: add Headers
config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache
Adds Headers to the ClientRequestOptions interface with docs; adds System.Collections.Generic using.
Namespace/import adjustments
config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache
Switches model namespace import to templated package; no API changes.
Tests for headers
config/clients/dotnet/template/OpenFgaClientTests.mustache
Adds helpers and extensive tests for header validation, precedence (per-request > default), OAuth coexistence, reserved headers rejection, CR/LF injection checks, and propagation across multiple endpoints.

Sequence Diagram(s)

sequenceDiagram
  participant App as SDK Consumer
  participant Client as OpenFGA Client
  participant GenApi as Generated API Method
  participant ApiClient as ApiClient.SendRequestAsync
  participant Auth as OAuth Provider
  participant Server as OpenFGA Service

  App->>Client: Call Operation(options with Headers)
  Client->>Client: ExtractHeaders(options)<br/>(ValidateHeaders)
  Client->>GenApi: Operation(..., headers, cancellationToken)
  GenApi->>ApiClient: SendRequestAsync(..., perRequestHeaders=headers)

  alt OAuth configured
    ApiClient->>Auth: Get access token (if needed)
    Auth-->>ApiClient: oauthToken
  else No OAuth
    ApiClient-->>ApiClient: oauthToken = null
  end

  ApiClient->>ApiClient: BuildHeaders(oauthToken, perRequestHeaders)
  ApiClient->>Server: HTTP request with merged headers
  Server-->>ApiClient: Response
  ApiClient-->>GenApi: Response
  GenApi-->>Client: Result
  Client-->>App: Result

  note over ApiClient: Per-request headers take precedence over auth/default.
Loading
sequenceDiagram
  participant App as SDK Consumer
  participant Client as OpenFGA Client

  App->>Client: Call with invalid Headers<br/>(e.g., reserved name or CR/LF)
  Client->>Client: ExtractHeaders -> ValidateHeaders
  Client-->>App: Throw ArgumentException
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • ewanharris
  • jimmyjames
  • sergiught

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately describes the main enhancement—adding support for per-request custom headers to the .NET SDK—using a clear, conventional format without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@evansims
Copy link
Contributor Author

I will have a follow-up PR with documentation coverage for this feature.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache (1)

1-23: Critical: Filename typo - "Authorizaion" should be "Authorization".

The filename ClientReadAuthorizaionModelOptions.mustache contains a typo ("Authorizaion"), but the class name inside is correctly spelled ClientReadAuthorizationModelOptions ("Authorization"). This mismatch could cause template generation issues, confusion, or build problems.

Rename the file from:

ClientReadAuthorizaionModelOptions.mustache

to:

ClientReadAuthorizationModelOptions.mustache
♻️ Duplicate comments (4)
config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache (1)

20-22: Same interface inheritance concern as ClientExpandOptions.

Verify that the base interface defines the Headers property to ensure the /// <inheritdoc /> comment is valid.

config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache (1)

23-25: Same interface inheritance verification needed.

Verify that the base interface defines the Headers property for the /// <inheritdoc /> comment to be valid.

config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache (1)

20-22: Same interface inheritance verification needed.

Verify that the base interface defines the Headers property for the /// <inheritdoc /> comment to be valid.

config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache (1)

25-27: Same interface inheritance verification needed.

Verify that the base interface defines the Headers property for the /// <inheritdoc /> comment to be valid.

🧹 Nitpick comments (2)
config/clients/dotnet/template/Client/ClientConfiguration.mustache (1)

110-140: Solid header validation with good security practices.

The validation logic correctly handles:

  • Empty/null key prevention
  • Null value detection
  • CR/LF injection prevention (important security measure)
  • Reserved header protection with clear error messages

Consider whether empty string header values (non-null but empty) should also be rejected for clarity:

 if (header.Value == null) {
     throw new ArgumentException($"Header '{header.Key}' has a null value. Header values cannot be null.", paramName);
 }
+
+if (string.IsNullOrWhiteSpace(header.Value)) {
+    throw new ArgumentException($"Header '{header.Key}' has an empty or whitespace-only value. Header values should contain meaningful data.", paramName);
+}
config/clients/dotnet/template/api.mustache (1)

48-48: Minor: Unnecessary explicit empty dictionary initialization.

The initialization new Dictionary<string, string> { } can be simplified to new Dictionary<string, string>() or use implicit initialization. However, this is a very minor style preference.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad10a02 and 932fbdf.

📒 Files selected for processing (22)
  • config/clients/dotnet/template/Client/Client.mustache (25 hunks)
  • config/clients/dotnet/template/Client/ClientConfiguration.mustache (4 hunks)
  • config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientCheckOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientCreateStoreOptions.mustache (1 hunks)
  • config/clients/dotnet/template/Client/Model/ClientExpandOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientListStoresOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientListUsersOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelsOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientReadOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache (1 hunks)
  • config/clients/dotnet/template/Client/Model/ClientWriteAssertionsOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client/Model/ClientWriteOptions.mustache (2 hunks)
  • config/clients/dotnet/template/Client_ApiClient.mustache (4 hunks)
  • config/clients/dotnet/template/OpenFgaClientTests.mustache (6 hunks)
  • config/clients/dotnet/template/api.mustache (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
config/**/*.mustache

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Validate mustache syntax and variable references across all template files, including CHANGELOG.md.mustache

Files:

  • config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientExpandOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListUsersOptions.mustache
  • config/clients/dotnet/template/api.mustache
  • config/clients/dotnet/template/Client/Model/ClientWriteOptions.mustache
  • config/clients/dotnet/template/Client/ClientConfiguration.mustache
  • config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListStoresOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientCreateStoreOptions.mustache
  • config/clients/dotnet/template/Client_ApiClient.mustache
  • config/clients/dotnet/template/Client/Model/ClientWriteAssertionsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientCheckOptions.mustache
  • config/clients/dotnet/template/Client/Client.mustache
  • config/clients/dotnet/template/OpenFgaClientTests.mustache
  • config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache
config/**/*.{json,mustache}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Never hardcode API keys or credentials in configuration or template files

Files:

  • config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientExpandOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListUsersOptions.mustache
  • config/clients/dotnet/template/api.mustache
  • config/clients/dotnet/template/Client/Model/ClientWriteOptions.mustache
  • config/clients/dotnet/template/Client/ClientConfiguration.mustache
  • config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListStoresOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientReadOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientCreateStoreOptions.mustache
  • config/clients/dotnet/template/Client_ApiClient.mustache
  • config/clients/dotnet/template/Client/Model/ClientWriteAssertionsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache
  • config/clients/dotnet/template/Client/Model/ClientCheckOptions.mustache
  • config/clients/dotnet/template/Client/Client.mustache
  • config/clients/dotnet/template/OpenFgaClientTests.mustache
  • config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache
🪛 Gitleaks (8.28.0)
config/clients/dotnet/template/OpenFgaClientTests.mustache

[high] 2373-2373: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-and-test-java-sdk
  • GitHub Check: build-and-test-dotnet-sdk
  • GitHub Check: build-and-test-go-sdk
🔇 Additional comments (40)
config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache (1)

3-3: LGTM! Headers property correctly added.

The addition of the System.Collections.Generic using directive and the Headers property follows the consistent pattern established across all option model templates in this PR. The inheritdoc comment is appropriate since Headers is defined in the parent IClientRequestOptions interface (transitively inherited via IClientRequestOptionsWithStoreId).

Also applies to: 20-22

config/clients/dotnet/template/Client/Model/ClientListUsersOptions.mustache (2)

3-4: LGTM! Using directives are correctly updated.

The template-driven namespace {{packageName}}.Model and the addition of System.Collections.Generic for IDictionary support are appropriate for the new Headers property.


20-22: LGTM! Headers property correctly added.

The IDictionary<string, string>? type is appropriate for custom headers, and the nullable annotation allows optional per-request headers. The <inheritdoc /> comment correctly indicates that the property is inherited from a parent interface in the hierarchy, consistent with the pattern used for other properties in this class.

config/clients/dotnet/template/Client/Model/ClientListStoresOptions.mustache (1)

1-27: LGTM! Implementation correctly follows the PR pattern.

The changes properly add per-request header support to ClientListStoresOptions:

  • The using System.Collections.Generic; directive is correctly added for the IDictionary type
  • The nullable Headers property appropriately implements the interface contract from ClientRequestOptions
  • Mustache template syntax is valid
  • No hardcoded credentials present

The implementation is consistent with the broader PR changes described in the objectives and AI summary.

config/clients/dotnet/template/Client/Model/ClientWriteOptions.mustache (2)

3-4: LGTM!

The using directive is correctly added to support the IDictionary<string, string> type used in the Headers property.


59-61: Correctly implement Headers property The IClientRequestOptions interface (in ClientRequestOptions.mustache) declares IDictionary<string, string>? Headers { get; set; }, so <inheritdoc /> is valid here.

config/clients/dotnet/template/Client/Model/ClientReadOptions.mustache (3)

3-4: LGTM: Improved template consistency.

The change from hardcoded using OpenFga.Sdk.Model to using {{packageName}}.Model aligns with the templated namespace pattern used elsewhere (line 6). The addition of using System.Collections.Generic; is necessary for the new IDictionary<string, string> Headers property.


26-27: LGTM: Minor formatting adjustment.

The formatting change to the Consistency property is consistent with the overall code style.


28-29: Inheritdoc valid: Headers declared in base interface
The Headers property is defined in the ClientRequestOptions interface (extended by IClientRequestOptionsWithStoreId), so the <inheritdoc /> on ClientReadOptions.Headers is correct.

config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache (1)

3-3: LGTM!

The namespace import correctly uses the {{packageName}} template variable for consistency with the broader template structure.

config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelsOptions.mustache (2)

24-25: LGTM!

The Headers property addition follows the established pattern across all option classes and correctly implements the nullable IDictionary type.


3-4: Verify 'Authorization' spelling in template filenames
The typo "Authorizaion" appears in two config/clients/dotnet template files; if pre-existing, file a separate issue to rename them for consistency.

config/clients/dotnet/template/Client/Model/ClientCreateStoreOptions.mustache (1)

3-4: LGTM!

The addition of the Headers property and required using directive follows the consistent pattern established across all option classes.

Also applies to: 10-11

config/clients/dotnet/template/Client/Model/ClientWriteAssertionsOptions.mustache (1)

3-4: LGTM!

The Headers property implementation is consistent with other option classes and correctly uses /// <inheritdoc /> for documentation.

Also applies to: 22-23

config/clients/dotnet/template/Client/ClientConfiguration.mustache (2)

3-4: LGTM!

The integration of header validation into EnsureValid() ensures that invalid default headers are caught early during configuration initialization. The updated XML documentation correctly documents the new exception type.

Also applies to: 63-63, 76-77


95-102: LGTM!

The reserved headers list appropriately protects critical HTTP headers from being overridden. The explicit note about User-Agent exclusion is helpful, as it allows SDK users to customize the user agent while preventing override of authentication and content-related headers.

config/clients/dotnet/template/Client/Model/ClientCheckOptions.mustache (1)

3-4: LGTM!

The namespace import correctly uses the template variable, and the Headers property implementation is consistent with the pattern across all option classes.

Also applies to: 21-22

config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache (1)

3-4: LGTM! Clear documentation of merge semantics.

The base interface definition is well-documented with clear precedence rules: per-request headers override default headers when keys conflict. This establishes the contract that all implementing classes follow.

Also applies to: 11-16

config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache (1)

3-4: LGTM!

Consistent implementation of the Headers property following the established pattern. The namespace import correctly uses the template variable.

Also applies to: 30-31

config/clients/dotnet/template/Client/Model/ClientExpandOptions.mustache (2)

3-4: LGTM! Clean template updates.

The using directives are correctly templated and System.Collections.Generic is properly added to support the new Headers property.


20-22: No action — base interface already defines Headers.
ClientRequestOptions declares IDictionary<string, string>? Headers (config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache); inheritdoc is correct.

config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache (1)

3-4: LGTM! Consistent with other option models.

The using directives follow the same pattern as other updated option models.

config/clients/dotnet/template/api.mustache (3)

3-5: LGTM! Necessary using directives added.

The new using directives support the updated API method signatures and exception handling.


37-37: Clean API surface updates for header support.

The headers parameter is correctly positioned before cancellationToken and properly documented. The nullable type with default null allows for optional header specification.

Also applies to: 41-41, 47-47


91-91: LGTM! Headers properly propagated to SendRequestAsync.

The headers parameter is correctly passed through to the underlying API client method.

config/clients/dotnet/template/Client_ApiClient.mustache (3)

65-72: LGTM! Consistent parameter additions.

The perRequestHeaders parameter is consistently added to both SendRequestAsync overloads with appropriate documentation.

Also applies to: 104-109


75-85: Clean refactoring to support header merging.

The OAuth token is now stored in a variable and passed to BuildHeaders, which enables proper merging of OAuth and per-request headers. This is a good separation of concerns.

Also applies to: 112-122


175-195: Ensure reserved headers (e.g., Authorization) cannot be overridden by per-request headers
Confirm that ClientConfiguration.ValidateHeaders (invoked in Client.mustache line 65) enforces blocking or removing reserved headers before BuildHeaders merges them.

config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache (1)

3-4: LGTM! Consistent pattern.

config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache (1)

3-4: LGTM! Consistent pattern.

config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache (1)

3-4: LGTM! Consistent pattern.

config/clients/dotnet/template/Client/Client.mustache (3)

57-67: LGTM! Clean helper method for header extraction and validation.

The ExtractHeaders method provides a centralized way to extract and validate headers from options objects. The cast to ClientRequestOptions interface is appropriate since all option classes implement it (or should, per the /// <inheritdoc /> comments).


236-236: LGTM! Consistent header propagation across all API operations.

The ExtractHeaders helper is consistently used across all API calls (except CreateStore, flagged separately), ensuring uniform header validation and extraction.

Also applies to: 250-250, 256-256, 268-268, 276-276, 289-289, 318-318, 336-336, 362-362, 438-438, 472-472, 492-492, 548-548, 564-564, 593-593


299-299: Correct header propagation in derived operations.

The headers are properly extracted and passed through to derived operations (ReadLatestAuthorizationModel, clientWriteOpts), ensuring that per-request headers are preserved across nested calls.

Also applies to: 375-375

config/clients/dotnet/template/OpenFgaClientTests.mustache (6)

5-5: LGTM: Required import for LINQ operations.

The System.Linq import is necessary for the new helper methods and test assertions that use .First().


31-38: LGTM: Clean test data organization.

Using a static class for test header constants improves maintainability and reduces magic strings throughout the tests.


61-91: LGTM: Well-designed test helpers.

The helper methods CreateTestClientForHeaders and AssertHeaderPresent provide clean abstractions for:

  • Setting up test clients with configurable request validation
  • Verifying header presence and values in requests

This reduces duplication across the extensive header tests that follow.


144-291: LGTM: Comprehensive DefaultHeaders validation coverage.

Excellent test coverage for configuration-time validation:

  • Reserved headers (Content-Type, Authorization, etc.) with case-insensitive checking
  • Empty header names and null values
  • CRLF injection prevention
  • Valid custom headers

The case-insensitivity tests (lines 259-272) correctly align with HTTP header semantics.


2014-2860: LGTM: Outstanding comprehensive test coverage for custom headers feature.

This test region provides exceptional coverage of the per-request custom headers functionality:

Strengths:

  • Tests all major API methods (Check, Write, Read, Expand, ListObjects, ListUsers, CreateStore, etc.)
  • Validates header propagation end-to-end
  • Tests null headers gracefully (lines 2086-2103)
  • Validates CRLF injection prevention (lines 2108-2131)
  • Tests reserved header protection (lines 2484-2515)
  • Verifies header precedence across all layers: per-request > OAuth > default (lines 2565-2734)
  • Includes concurrent request testing (lines 2520-2560)
  • Comprehensive integration tests ensuring reserved headers are blocked in all paths (lines 2768-2858)

Note on concurrent test (lines 2520-2560):
The test validates that concurrent requests with different headers complete successfully, which is the primary goal. Individual header isolation per request is already validated in other tests, so the current approach is appropriate.

The extensive test suite gives high confidence that the feature works correctly across all scenarios.


2373-2373: Static analysis false positive - not a real secret.

The value 01GXSA8YR785C4FYS3C0RTG7B1 flagged by static analysis is a test authorization model ID in ULID format, not an actual API key. This is used consistently throughout the test file as test data.

@evansims evansims force-pushed the feat/dotnet/per-request-custom-headers branch from d5392a7 to 3a2488d Compare October 16, 2025 03:23
Copilot AI review requested due to automatic review settings October 17, 2025 03:22
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds per-request custom headers support to the .NET SDK templates, including validation and precedence handling between default, OAuth, and per-request headers.

  • Introduces IRequestOptions/IClientRequestOptions with Headers and wires options through API and client layers
  • Adds header validation (reserved headers, null/CRLF) and case-insensitive merge with defined precedence
  • Updates docs and adds comprehensive tests for propagation, precedence, and validation

Reviewed Changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
config/clients/dotnet/template/modelRequestOptions.mustache Adds IRequestOptions and RequestOptions for API-level headers.
config/clients/dotnet/template/api.mustache Adds IRequestOptions? options parameter to API methods and forwards to ApiClient.
config/clients/dotnet/template/README_initializing.mustache Documents default and per-request custom headers usage.
config/clients/dotnet/template/OpenFgaClientTests.mustache Adds tests for headers validation, propagation, and precedence.
config/clients/dotnet/template/Configuration_Configuration.mustache Adds reserved headers list and ValidateHeaders; validates DefaultHeaders in EnsureValid.
config/clients/dotnet/template/Client_ApiClient.mustache Builds merged headers (default, OAuth, per-request) and forwards to BaseClient.
config/clients/dotnet/template/Client/Model/ClientWriteOptions.mustache Adds Headers property to write options.
config/clients/dotnet/template/Client/Model/ClientWriteAssertionsOptions.mustache Adds Headers property to write assertions options.
config/clients/dotnet/template/Client/Model/ClientRequestOptsWithStoreId.mustache Switches to IClientRequestOptions base interface.
config/clients/dotnet/template/Client/Model/ClientRequestOptions.mustache Introduces IClientRequestOptions and implementation with Headers.
config/clients/dotnet/template/Client/Model/ClientReadOptions.mustache Adds Headers property to read options.
config/clients/dotnet/template/Client/Model/ClientReadChangesOptions.mustache Adds Headers property to read changes options.
config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelsOptions.mustache Adds Headers property to read auth models options.
config/clients/dotnet/template/Client/Model/ClientReadAuthorizaionModelOptions.mustache Adds Headers property to read auth model options.
config/clients/dotnet/template/Client/Model/ClientReadAssertionsOptions.mustache Adds Headers property to read assertions options.
config/clients/dotnet/template/Client/Model/ClientListUsersOptions.mustache Adds Headers property to list users options.
config/clients/dotnet/template/Client/Model/ClientListStoresOptions.mustache Adds Headers property and updates base interface.
config/clients/dotnet/template/Client/Model/ClientListRelationsOptions.mustache Adds Headers property to list relations options.
config/clients/dotnet/template/Client/Model/ClientListObjectsOptions.mustache Adds Headers property to list objects options.
config/clients/dotnet/template/Client/Model/ClientExpandOptions.mustache Adds Headers property to expand options.
config/clients/dotnet/template/Client/Model/ClientCreateStoreOptions.mustache Adds Headers property to create store options.
config/clients/dotnet/template/Client/Model/ClientConsistencyOptions.mustache Switches to package variable for model namespace.
config/clients/dotnet/template/Client/Model/ClientCheckOptions.mustache Adds Headers property to check options.
config/clients/dotnet/template/Client/Model/ClientBatchCheckOptions.mustache Adds Headers property to batch check options.
config/clients/dotnet/template/Client/ClientConfiguration.mustache Adds usings needed for headers dictionary usage.
config/clients/dotnet/template/Client/Client.mustache Threads options through high-level client calls and preserves Headers in chunked writes.
config/clients/dotnet/config.overrides.json Maps new RequestOptions template into generated Model folder.
config/clients/dotnet/CHANGELOG.md.mustache Documents breaking changes and new headers functionality.

@evansims evansims added this pull request to the merge queue Oct 21, 2025
Merged via the queue into main with commit c4dc587 Oct 21, 2025
15 checks passed
@evansims evansims deleted the feat/dotnet/per-request-custom-headers branch October 21, 2025 15:02
@dyeam0 dyeam0 linked an issue Oct 23, 2025 that may be closed by this pull request
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dotnet-sdk Affects the C#/DotNet SDK enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Add support for per-request custom HTTP headers in SDK API calls

2 participants

Comments