Patterns and techniques for mitigating breaking changes during Azure management-plane SDK migration from Swagger/AutoRest to TypeSpec. Covers SDK-side customizations (partial classes, CodeGenType, CodeGenSuppress) and TypeSpec decorator customizations (clientName, access, markAsPageable, alternateType, hierarchyBuilding).
Patterns and techniques for mitigating breaking changes when migrating or regenerating Azure management-plane .NET SDKs. Use these to preserve backward compatibility in the generated SDK surface.
Trigger phrases: "mitigate breaking changes", "fix breaking change", "customization patterns", "how to keep backward compat", "CodeGenType", "CodeGenSuppress", "markAsPageable", "hierarchyBuilding", "base type change".
Use Custom/*.cs or Customization/*.cs partial classes (follow the package's existing structure) for .NET-side fixes.
// src/Custom/MyModel.cs (or src/Customization/MyModel.cs — follow the package's existing convention)
namespace Azure.ResourceManager.<Service>.Models
{
public partial class MyModel
{
// Add computed properties, rename via [CodeGenMember], etc.
}
}
[CodeGenType] — Override accessibility or rename a generated typeWhen a generated type is internal and @@access in client.tsp doesn't work (common for nested/wrapper types), use [CodeGenType] in Custom code to make it public:
// src/Custom/Models/MyPublicModel.cs
using Microsoft.TypeSpec.Generator.Customizations;
namespace Azure.ResourceManager.<Service>.Models
{
[CodeGenType("OriginalTypeSpecModelName")]
public partial class MyPublicModel
{
}
}
The [CodeGenType("...")] attribute takes the original TypeSpec model name (not the C# renamed name). This links the Custom partial class to the generated internal type and overrides its accessibility to public.
These decorators are added to client.tsp in the spec repo.
// Rename parameter
@@clientName(Operations.create::parameters.resource, "content");
// Rename path parameter
@@Azure.ResourceManager.Legacy.renamePathParameter(Resources.list, "fooName", "name");
// Mark a non-pageable list operation as pageable (returns Pageable<T> instead of Response<ListType>)
// Requires: using Azure.ClientGenerator.Core.Legacy;
#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration"
@@markAsPageable(InterfaceName.operationName, "csharp");
// Suppress warning
#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration"
@@markAsPageableWhen the old SDK returned Pageable<T> / AsyncPageable<T> for a list operation, but the TypeSpec spec defines the operation as non-pageable (returns a wrapper list type like FooList), use @@markAsPageable to make the generator produce pageable methods. This is preferred over writing custom SinglePagePageable<T> wrapper code because:
Do NOT use @@markAsPageable if the operation is already marked with @list — the @list decorator already makes the operation pageable, and adding @@markAsPageable will cause a compile error. Check the spec's operation definition before adding the decorator.
Requirements:
using Azure.ClientGenerator.Core.Legacy; to the client.tsp imports#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "migration" before each @@markAsPageable call[CodeGenSuppress] + SinglePagePageable wrapper code@@alternateType DecoratorWhen the spec uses older common types that generate incorrect C# types (e.g., string instead of ResourceIdentifier for ID properties), use @@alternateType:
@@alternateType(MyModel.resourceId, Azure.ResourceManager.CommonTypes.ArmResourceIdentifier, "csharp");
@@hierarchyBuilding Decorator — Change a resource model's base typeWhen a TypeSpec resource model generates with the wrong base class (e.g., a plain Resource model instead of TrackedResource or ProxyResource), use @@hierarchyBuilding to override the base type. This is common when the spec defines a resource using a non-standard base type that doesn't map to the correct ARM SDK base class (ResourceData, TrackedResourceData, etc.).
Syntax:
// Requires: using Azure.ClientGenerator.Core.Legacy;
#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "Change the base type back to <TargetBase> for backward compatibility"
@@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(MyResource,
Azure.ResourceManager.Foundations.TrackedResource,
"csharp"
);
Common target base types:
Azure.ResourceManager.Foundations.TrackedResource — generates TrackedResourceData (for resources with location and tags)Azure.ResourceManager.Foundations.ProxyResource — generates ResourceData (for proxy/child resources)Azure.ResourceManager.Foundations.Resource — generates ResourceData (ARM resource base)When to use:
MyData : ResourceData or MyData : TrackedResourceData, but the new TypeSpec-generated SDK produces MyData : SomeOtherType (e.g., a service-local Resource model)CannotRemoveBaseTypeOrInterface API compatibility violation indicates this issue (e.g., "Type 'X' does not inherit from base type 'Azure.ResourceManager.Models.ResourceData'")Requirements:
using Azure.ClientGenerator.Core.Legacy; to the client.tsp imports#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "..." before each @@hierarchyBuilding callExample (from KeyVault migration):
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core.Legacy;
// Fix Vault resource to generate VaultData : TrackedResourceData
#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "Change the base type back to TrackedResource for backward compatibility"
@@Azure.ClientGenerator.Core.Legacy.hierarchyBuilding(Vault,
Azure.ResourceManager.Foundations.TrackedResource,
"csharp"
);
When the previous SDK version included WirePathAttribute on model properties (used by Azure.Provisioning libraries), migrating to TypeSpec may produce ApiCompat CannotRemoveAttribute errors for the missing attribute — because the emitter defaults to not generating it.
CannotRemoveAttribute errors referencing WirePathAttribute on model propertiesdotnet pack --no-restore when the previous SDK release had WirePathAttribute on properties but the new generation does notAdd enable-wire-path-attribute: true to the mgmt emitter options in tspconfig.yaml (in the spec repo):