Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/specs/polyglot-apphost.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ public class RedisResource : ContainerResource { }
// Type ID = Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder
```

Assembly-level exports are the mechanism for exposing framework or third-party types that live in a different assembly than the capabilities that use them. Even in that case, the ATS type ID still comes from the exported CLR type's assembly and full type name, not from the assembly that declares the attribute.

### Type Categories

ATS categorizes types for serialization and code generation using `AtsTypeCategory`:
Expand Down Expand Up @@ -398,6 +400,25 @@ public static class ResourceExtensions

Because `RedisResource` implements `IResourceWithEnvironment` (via `ContainerResource`), the scanner adds `withEnvironment` to `RedisResource`'s capability list.

#### Cross-Assembly Type Exports

Use `[assembly: AspireExport(typeof(T))]` when a capability assembly needs to expose a type defined in another assembly:

```csharp
// In Aspire.Hosting/Ats/AtsTypeMappings.cs
[assembly: AspireExport(typeof(IConfiguration))]
[assembly: AspireExport(typeof(IHostEnvironment), ExposeProperties = true)]
```

At startup, the remote host creates one ATS context by scanning all loaded assemblies together. That shared scan is what lets one assembly export a type while another assembly contributes capabilities that consume or return it.

- **Type identity still comes from the exported CLR type.** For example, `[assembly: AspireExport(typeof(IConfiguration))]` produces the ATS type ID `Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfiguration`, even though the attribute is declared in `Aspire.Hosting`.
- **Exported handle types are merged by ATS type ID across the full scan.** Re-exporting the same type from multiple assemblies does not create multiple ATS handle types.
- **Capabilities are merged from all scanned assemblies, but capability IDs must remain unique.** If two assemblies export the same capability ID, scanning emits a diagnostic error.
- **Cross-assembly resolution is scan-order independent.** If one assembly exports a type and another references it, the scanner resolves it after collecting the complete type universe from every scanned assembly.

This is how Aspire's core hosting assembly exports framework types such as `IConfiguration`, `IConfigurationSection`, and `IHostEnvironment` while keeping them in the same flattened capability model as types exported directly from integration packages.

#### Flattening in Action

A concrete type gets capabilities from:
Expand Down
3 changes: 2 additions & 1 deletion src/Aspire.Hosting/Ats/ThirdPartyAtsAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public sealed class AspireExportAttribute : Attribute
}

/// <summary>
/// Type export (parameterless). The type ID is derived as {AssemblyName}/{TypeName}.
/// Type export (parameterless). The type ID is derived as {AssemblyName}/{FullTypeName}.
/// </summary>
public AspireExportAttribute()
{
Expand Down Expand Up @@ -189,5 +189,6 @@ public sealed class AddMyDatabaseOptions
- **Constructor signatures must match by arity and argument type**: `()`, `(string)`, `(Type)` for `AspireExportAttribute`; `(params Type[])` for `AspireUnionAttribute`. Parameter names can differ.
- **Property names must match exactly**: `Type`, `Description`, `MethodName`, `ExposeProperties`, `ExposeMethods`, `Reason`, `DtoTypeId`, `Types`.
- **Namespaces must match exactly**: copied attributes need to be declared in the `Aspire.Hosting` namespace so their full names match the built-in ATS attributes.
- For end-to-end behavior of assembly-level exports across multiple assemblies, see [Cross-Assembly Type Exports](../../../docs/specs/polyglot-apphost.md#cross-assembly-type-exports).
- If you later add a reference to `Aspire.Hosting` and both your custom attribute and the official one are applied to the same member, both will be detected (the scanner takes the first match).
- The scanner uses `CustomAttributeData` which reads metadata without instantiating the attribute, so your attribute types don't need to be loadable at scan time — only the full name and supported members must match.
Loading