Skip to content

[http-client-csharp] Support dynamic loading of ExternalTypes from NuGet as a fallback after CreateFrameworkType #10665

@JoshLove-msft

Description

@JoshLove-msft

Clear and concise description of the problem

Today, TypeFactory.CreateExternalType in the C# generator can only resolve an InputExternalTypeMetadata if the type is one the generator already knows how to map to a framework type via CreateFrameworkType (e.g. System.Uri, System.Net.IPAddress once the BCL has it). For any other identity — a type that lives in a NuGet package the generated project depends on but the generator process itself doesn't reference — we fall through to:

CodeModelGenerator.Instance.Emitter.ReportDiagnostic(
    "unsupported-external-type",
    $"External type '{externalProperties.Identity}' is not currently supported.");
return null;

…and the property is silently dropped. This is the path covered by the existing UnsupportedExternalTypeEmitsDiagnostic test (e.g. Azure.Core.Expressions.DataFactoryExpression).

This is unfortunate because InputExternalTypeMetadata already carries everything we'd need to load such a type dynamically:

public sealed class InputExternalTypeMetadata
{
    public InputExternalTypeMetadata(string identity, string? package, string? minVersion)
    // Identity, Package, MinVersion
}

Proposal

Add dynamic NuGet-based loading of external types as a fallback in TypeFactory.CreateExternalType, reusing the mechanism introduced by #10229 (GeneratedCodeWorkspace.AddPackageReferencesFromProject / FindPackageAssembly / ResolveLatestPackageVersion / NugetPackageDownloader).

Resolution order should be:

  1. CreateFrameworkType(identity) — current behavior; cheapest and the source of truth for BCL types. If it returns non-null, we're done.
  2. Dynamic load via NuGet (new fallback) — only if step 1 returns null and InputExternalTypeMetadata.Package is provided:
    • Resolve the assembly for Package (honoring MinVersion when present) by first probing the NuGet global packages cache (à la FindPackageAssembly), then falling back to a feed download (ResolveLatestPackageVersion + NugetPackageDownloader) that PR fix(http-client-csharp): resolve PackageReference assemblies for cust… #10229 already wired up.
    • Load the resolved assembly via Roslyn MetadataReference.CreateFromFile and add it to CodeModelGenerator.Instance.AdditionalMetadataReferences so customization compilation also sees it (PR fix(http-client-csharp): resolve PackageReference assemblies for cust… #10229's de-dup against existingRefs already covers this nicely).
    • Use reflection (Assembly.LoadFrom / MetadataLoadContext) on the same DLL to find the Type matching Identity and produce a CSharpType from it, exactly like the framework-type path does.
  3. Diagnostic — only if both 1 and 2 fail, emit the existing unsupported-external-type diagnostic. The diagnostic message should be expanded to mention the package/version that was attempted (or that no Package was supplied) so users have something actionable.

Why a fallback (not a replacement)

Keeping CreateFrameworkType as the first attempt:

  • Avoids any I/O / NuGet hit for the common BCL cases.
  • Preserves existing behavior and test expectations for System.* types whose Package is null.
  • Makes the new code path opt-in via the emitter / TCGC supplying Package + (optionally) MinVersion.

Out of scope / open questions

  • Whether we should require MinVersion to be present before attempting a feed download, or fall back to "latest stable" the way ResolveLatestPackageVersion does today.
  • How (or whether) to surface a warning when the resolved assembly's version is lower than MinVersion.
  • Whether to share a single resolution helper between AddPackageReferencesFromProject and the new external-type path so caching and diagnostics stay consistent.

Acceptance criteria

  • A InputExternalTypeMetadata whose Identity is not a known framework type but whose Package resolves via the NuGet cache or feed produces a valid CSharpType (and no unsupported-external-type diagnostic).
  • The existing ExternalTypeModelUsedAsProperty / ExternalTypePropertyIsResolved tests continue to pass unchanged (framework-type path is hit first).
  • A new test covers the NuGet fallback using the same fake-cache pattern as AddPackageReferencesFromProject_AddsReferencesFromCsproj in GeneratedCodeWorkspaceTests.
  • A negative test covers the case where neither framework lookup nor NuGet resolution succeeds, asserting the (improved) diagnostic.

Checklist

  • Follow our Code of Conduct
  • Read the docs.
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.

Metadata

Metadata

Assignees

Labels

emitter:client:csharpIssue for the C# client emitter: @typespec/http-client-csharpfeatureNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions