Skip to content

Support multi-service C# clients without @client decorator#10438

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/add-support-for-multiservice-clients
Draft

Support multi-service C# clients without @client decorator#10438
Copilot wants to merge 4 commits intomainfrom
copilot/add-support-for-multiservice-clients

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 21, 2026

When a TypeSpec library declares multiple @service-decorated namespaces without an explicit @client (the service/multiple-services spector scenario), TCGC emits one root client per service. The C# emitter's library-level logic didn't recognize this shape and resolved the package namespace to the first service's namespace instead of the shared parent, and the generator incorrectly shared a single ClientOptions instance across the per-service root clients.

Emitter changes (emitter/src/lib/utils.ts)

  • getClientNamespaceString — when there are multiple @service namespaces (i.e. serviceNamespaces.length > 1), resolves the library namespace to the TypeSpec parent Namespace shared by those services (via firstNamespace.namespace from listAllServiceNamespaces) and passes it through the existing namespace-based helper path. The existing combined-@client({ service: [...] }) path is unchanged.
  • containsMultiServiceClient — simplified to rootClients.some(isMultiServiceClient) so it accurately reflects its name (any root client is itself a combined multi-service client). The multi-root-multi-service case is handled directly in getClientNamespaceString via the serviceNamespaces.length > 1 check.

For the spector spec, the library namespace now resolves to Service.MultipleServices instead of Service.MultipleServices.ServiceA, with ServiceAClient and ServiceBClient as sibling root clients each exposing their own Operations / SubNamespace sub-clients and per-service API version enums.

Generator changes (ClientOptionsProvider.cs)

  • UseSingletonInstance — also returns false when the input library contains more than one ApiVersionEnum (i.e. spans multiple services). Previously, when multiple per-service single-service root clients existed, both would share the singleton ClientOptionsProvider, producing a single options class with only the first service's api version. Each root client now gets its own ClientOptions class (e.g. ServiceAClientOptions, ServiceBClientOptions) with its own service-specific api version property.

Tests

  • emitter/test/Unit/client-converter.test.ts — new describe("multiple services without @client decorator", ...) block mirroring the spector scenario, asserting root count/names/namespaces, sub-client shape, per-service apiVersions, and that the library spans multiple distinct services.
  • generator/.../ClientOptionsProviderTests.cs — new MultipleRootClients_WithoutClientDecorator_EachGeneratesItsOwnClientOptions test verifying that two per-service root clients each produce their own distinct ClientOptions type with the expected names.

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/10b8061a-9ce0-4c76-ab95-d30a73d2a6df

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Apr 21, 2026
Copilot AI changed the title [WIP] Add support for multiService clients with no client decorator Support multi-service C# clients without @client decorator Apr 21, 2026
Copilot AI requested a review from jorgerangel-msft April 21, 2026 17:54
Comment thread packages/http-client-csharp/emitter/test/Unit/client-converter.test.ts Outdated
Comment thread packages/http-client-csharp/emitter/src/lib/utils.ts Outdated
…ant test comment

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/4bd1ac3b-75cd-410c-bd84-0700b628702e

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@10438

commit: ccade61

// Multiple root clients (one per @service namespace): use their common parent
// namespace so shared library artifacts are grouped together. Otherwise (a single
// combined multi-service root, e.g. via `@client({ service: [...] })`), use the
// root client's namespace.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot remove this comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed in c41d3e6.

const services = new Set<unknown>();
for (const client of rootClients) {
for (const service of client.__raw.services) {
services.add(service);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot shouldn't we be calling isMultiServiceClient for each client and simply returning true when isMultiServiceClient returns true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c41d3e6. containsMultiServiceClient is now just rootClients.some(isMultiServiceClient). The multi-root-multi-service case (multiple @service namespaces, each a separate single-service root) is handled directly in getClientNamespaceString via serviceNamespaces.length > 1, which is the actual signal we care about for picking the library namespace.

it("produces a separate root client per service with expected names, namespaces, and sub-clients", async () => {
const program = await typeSpecCompile(
`
namespace Service.MultipleServices;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@copilot lets add a unit test in the csharp generator to validate that a client shape like this correctly generates two client option classes. It is likely that we don't and we'll have to fix that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added MultipleRootClients_WithoutClientDecorator_EachGeneratesItsOwnClientOptions in ClientOptionsProviderTests.cs (c41d3e6). Your suspicion was correct — the test initially failed because both root clients received the same singleton ClientOptionsProvider instance. Fixed ClientOptionsProvider.UseSingletonInstance to also return false when the library contains more than one ApiVersionEnum (i.e. it spans multiple services), so each per-service root client now gets its own ServiceAClientOptions / ServiceBClientOptions class with its own api version property. All 36 ClientOptions tests and 264 ClientProvider tests still pass.

…for multi-service libraries

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/b533b9d9-348c-43f4-bd92-bb9ad5af287f

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Support For MultiService Clients with No Client Decorator

2 participants