diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs
index 6fc87d7ced9..2b18952b4e8 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs
@@ -90,6 +90,28 @@ internal set
}
}
+ ///
+ /// Safely gets a namespace override from the SourceInputModel if it's available.
+ /// Returns null if the generator or SourceInputModel is not yet initialized.
+ ///
+ internal static string? GetNamespaceOverride(string typeName)
+ {
+ if (_instance == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return _instance.SourceInputModel.GetNamespaceOverride(typeName);
+ }
+ catch (InvalidOperationException)
+ {
+ // SourceInputModel not yet initialized
+ return null;
+ }
+ }
+
public string LicenseHeader => Configuration.LicenseInfo?.Header ?? string.Empty;
public virtual OutputLibrary OutputLibrary { get; } = new();
public virtual InputLibrary InputLibrary => _inputLibrary;
@@ -103,7 +125,8 @@ internal set
new CodeGenTypeAttributeDefinition(),
new CodeGenMemberAttributeDefinition(),
new CodeGenSuppressAttributeDefinition(),
- new CodeGenSerializationAttributeDefinition()
+ new CodeGenSerializationAttributeDefinition(),
+ new CodeGenNamespaceAttributeDefinition()
];
protected internal virtual void Configure()
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs
new file mode 100644
index 00000000000..1de8466743a
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/CodeGenNamespaceAttributeDefinition.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.TypeSpec.Generator.Expressions;
+using Microsoft.TypeSpec.Generator.Primitives;
+using Microsoft.TypeSpec.Generator.Statements;
+using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
+
+namespace Microsoft.TypeSpec.Generator.Providers
+{
+ internal class CodeGenNamespaceAttributeDefinition : TypeProvider
+ {
+ protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", "Internal", $"{Name}.cs");
+
+ protected override string BuildName() => "CodeGenNamespaceAttribute";
+
+ protected override string BuildNamespace() => CodeModelGenerator.CustomizationAttributeNamespace;
+
+ private protected sealed override NamedTypeSymbolProvider? BuildCustomCodeView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null;
+ private protected sealed override NamedTypeSymbolProvider? BuildLastContractView(string? generatedTypeName = default, string? generatedTypeNamespace = default) => null;
+
+ protected override TypeSignatureModifiers BuildDeclarationModifiers() =>
+ TypeSignatureModifiers.Internal | TypeSignatureModifiers.Class;
+
+ protected internal override CSharpType[] BuildImplements() => [typeof(Attribute)];
+
+ protected override IReadOnlyList BuildAttributes()
+ {
+ return [new AttributeStatement(typeof(AttributeUsageAttribute),
+ [FrameworkEnumValue(AttributeTargets.Assembly)],
+ [
+ new KeyValuePair("AllowMultiple", True)
+ ])];
+ }
+
+ protected internal override PropertyProvider[] BuildProperties() =>
+ [
+ new PropertyProvider(
+ $"Gets the original name of the type whose namespace should be changed.",
+ MethodSignatureModifiers.Public,
+ typeof(string),
+ "TypeName",
+ new AutoPropertyBody(false),
+ this),
+ new PropertyProvider(
+ $"Gets the new namespace for the type.",
+ MethodSignatureModifiers.Public,
+ typeof(string),
+ "Namespace",
+ new AutoPropertyBody(false),
+ this)
+ ];
+
+ protected internal override ConstructorProvider[] BuildConstructors()
+ {
+ var typeNameParameter = new ParameterProvider("typeName", $"The original name of the type.", typeof(string));
+ var namespaceParameter = new ParameterProvider("namespace", $"The new namespace for the type.", typeof(string));
+
+ return
+ [
+ new ConstructorProvider(
+ new ConstructorSignature(
+ Type,
+ null,
+ MethodSignatureModifiers.Public,
+ [typeNameParameter, namespaceParameter]),
+ new[]
+ {
+ This.Property("TypeName").Assign(typeNameParameter).Terminate(),
+ This.Property("Namespace").Assign(namespaceParameter).Terminate()
+ },
+ this)
+ ];
+ }
+ }
+}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs
index e34c6b33e35..f99a06540ee 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs
@@ -49,7 +49,7 @@ protected TypeProvider() : this(null)
private protected virtual TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null)
=> CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract(
- generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(),
+ generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? CodeModelGenerator.GetNamespaceOverride(BuildName()) ?? BuildNamespace(),
generatedTypeName ?? CustomCodeView?.Name ?? BuildName(),
DeclaringTypeProvider?.Type.Name);
@@ -134,7 +134,7 @@ public string? Deprecated
public CSharpType Type => _type ??=
new(
CustomCodeView?.Name ?? BuildName(),
- CustomCodeView?.Type.Namespace ?? BuildNamespace(),
+ GetResolvedNamespace(),
this is EnumProvider ||
DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct) ||
DeclarationModifiers.HasFlag(TypeSignatureModifiers.Enum),
@@ -149,6 +149,26 @@ this is EnumProvider ||
protected virtual bool GetIsEnum() => false;
public bool IsEnum => GetIsEnum();
+ ///
+ /// Resolves the namespace for this type, checking custom code view first,
+ /// then assembly-level CodeGenNamespace overrides, then the default namespace.
+ ///
+ private string GetResolvedNamespace()
+ {
+ if (CustomCodeView?.Type.Namespace is { } customNs)
+ {
+ return customNs;
+ }
+
+ var overrideNs = CodeModelGenerator.GetNamespaceOverride(BuildName());
+ if (overrideNs != null)
+ {
+ return overrideNs;
+ }
+
+ return BuildNamespace();
+ }
+
protected virtual string BuildNamespace() => CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace;
private TypeSignatureModifiers? _declarationModifiers;
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs
index 973d262735f..26a4dae697b 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs
@@ -19,6 +19,8 @@ public static class CodeGenAttributes
public const string CodeGenSerializationAttributeName = "CodeGenSerializationAttribute";
+ public const string CodeGenNamespaceAttributeName = "CodeGenNamespaceAttribute";
+
private const string SerializationName = "SerializationName";
private const string SerializationValueHook = "SerializationValueHook";
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs
index 54c17f609ba..9180dc8fa93 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs
@@ -15,6 +15,7 @@ public class SourceInputModel
public Compilation? LastContract { get; }
private readonly Lazy> _nameMap;
+ private readonly Lazy> _namespaceOverrides;
public SourceInputModel(Compilation? customization, Compilation? lastContract)
{
@@ -22,6 +23,7 @@ public SourceInputModel(Compilation? customization, Compilation? lastContract)
LastContract = lastContract;
_nameMap = new(PopulateNameMap);
+ _namespaceOverrides = new(PopulateNamespaceOverrides);
}
private IReadOnlyDictionary PopulateNameMap()
@@ -52,6 +54,14 @@ private IReadOnlyDictionary PopulateNameMap()
return FindTypeInCustomization(Customization, ns, name, false, declaringTypeName);
}
+ ///
+ /// Gets the namespace override for a type if one was specified via [assembly: CodeGenNamespace] in custom code.
+ ///
+ public string? GetNamespaceOverride(string typeName)
+ {
+ return _namespaceOverrides.Value.TryGetValue(typeName, out var ns) ? ns : null;
+ }
+
public TypeProvider? FindForTypeInLastContract(string ns, string name, string? declaringTypeName = null)
{
return FindTypeInCompilation(LastContract, ns, name, true, declaringTypeName, includeInternal: false);
@@ -113,6 +123,28 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri
return type != null ? new NamedTypeSymbolProvider(type, compilation) : null;
}
+ private IReadOnlyDictionary PopulateNamespaceOverrides()
+ {
+ var overrides = new Dictionary();
+ if (Customization == null)
+ {
+ return overrides;
+ }
+
+ foreach (var attribute in Customization.Assembly.GetAttributes())
+ {
+ if (attribute.AttributeClass?.Name == CodeGenAttributes.CodeGenNamespaceAttributeName
+ && attribute.ConstructorArguments.Length >= 2
+ && attribute.ConstructorArguments[0].Value is string typeName
+ && attribute.ConstructorArguments[1].Value is string @namespace)
+ {
+ overrides[typeName] = @namespace;
+ }
+ }
+
+ return overrides;
+ }
+
private bool TryGetName(ISymbol symbol, [NotNullWhen(true)] out string? name)
{
name = null;
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs
index 4f107ab227d..4233063d6e0 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/CustomizationAttributeTests.cs
@@ -45,5 +45,13 @@ public void CodeGenTypeAttributeEmitted()
var codeGenTypeAttribute = new CodeGenTypeAttribute("PropertyName");
Assert.AreEqual("PropertyName", codeGenTypeAttribute.OriginalName);
}
+
+ [Test]
+ public void CodeGenNamespaceAttributeEmitted()
+ {
+ var codeGenNamespaceAttribute = new CodeGenNamespaceAttribute("EnumTypeName", "New.Namespace.Models");
+ Assert.AreEqual("EnumTypeName", codeGenNamespaceAttribute.TypeName);
+ Assert.AreEqual("New.Namespace.Models", codeGenNamespaceAttribute.Namespace);
+ }
}
}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs
index b03e2ab0457..1d2567146a4 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderCustomizationTests.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Linq;
using System.Threading.Tasks;
+using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.Tests.Common;
@@ -29,5 +31,117 @@ public async Task CanCustomizeExtensibleEnumAccessibility()
Assert.IsTrue(enumType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public));
Assert.IsFalse(enumType.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal));
}
+
+ [Test]
+ public async Task CanChangeFixedEnumNamespace()
+ {
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var input = InputFactory.Int32Enum("mockInputEnum",
+ [
+ ("One", 1),
+ ("Two", 2)
+ ],
+ isExtensible: false);
+
+ var enumType = EnumProvider.Create(input);
+ Assert.IsNotNull(enumType);
+ Assert.IsNull(enumType.CustomCodeView, "CustomCodeView should be null since no custom type is defined");
+ Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace);
+ Assert.AreEqual("MockInputEnum", enumType.Type.Name);
+ Assert.IsTrue(enumType is FixedEnumProvider, "Enum should remain a FixedEnumProvider");
+ Assert.AreEqual(2, enumType.EnumValues.Count);
+ Assert.AreEqual(2, enumType.Fields.Count);
+ }
+
+ [Test]
+ public async Task CanChangeExtensibleEnumNamespace()
+ {
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var input = InputFactory.Int32Enum("mockInputEnum",
+ [
+ ("One", 1),
+ ("Two", 2)
+ ],
+ isExtensible: true);
+
+ var enumType = EnumProvider.Create(input);
+ Assert.IsNotNull(enumType);
+ Assert.IsNull(enumType.CustomCodeView, "CustomCodeView should be null since no custom type is defined");
+ Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace);
+ Assert.AreEqual("MockInputEnum", enumType.Type.Name);
+ Assert.IsTrue(enumType is ExtensibleEnumProvider, "Enum should remain an ExtensibleEnumProvider");
+ Assert.AreEqual(2, enumType.EnumValues.Count);
+ }
+
+ [Test]
+ public async Task CanChangeFixedEnumNamespacePreservesMembers()
+ {
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var input = InputFactory.Int32Enum("mockInputEnum",
+ [
+ ("Red", 1),
+ ("Green", 2),
+ ("Blue", 3)
+ ],
+ isExtensible: false);
+
+ var enumType = EnumProvider.Create(input);
+ Assert.IsNotNull(enumType);
+ Assert.AreEqual("NewNamespace.Models", enumType.Type.Namespace);
+
+ // Verify all enum members are preserved
+ Assert.AreEqual(3, enumType.EnumValues.Count);
+ Assert.AreEqual("Red", enumType.EnumValues[0].Name);
+ Assert.AreEqual("Green", enumType.EnumValues[1].Name);
+ Assert.AreEqual("Blue", enumType.EnumValues[2].Name);
+ // Verify fields match enum values
+ Assert.AreEqual(3, enumType.Fields.Count);
+ }
+
+ [Test]
+ public async Task CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace()
+ {
+ // When both [CodeGenType] and [assembly: CodeGenNamespace] target the same type,
+ // CodeGenType (CustomCodeView) takes precedence.
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var input = InputFactory.Int32Enum("mockInputEnum",
+ [
+ ("One", 1),
+ ("Two", 2)
+ ],
+ isExtensible: false);
+
+ var enumType = EnumProvider.Create(input);
+ Assert.IsNotNull(enumType);
+ Assert.IsNotNull(enumType.CustomCodeView, "CustomCodeView should exist from [CodeGenType]");
+ // CodeGenType's namespace wins over CodeGenNamespace
+ Assert.AreEqual("CustomCodeView.Models", enumType.Type.Namespace);
+ Assert.AreEqual("RenamedEnum", enumType.Type.Name);
+ }
+
+ [Test]
+ public async Task CodeGenNamespaceIgnoredForNonMatchingType()
+ {
+ // [assembly: CodeGenNamespace("NonExistentType", ...)] should have no effect on other types.
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var input = InputFactory.Int32Enum("mockInputEnum",
+ [
+ ("One", 1),
+ ("Two", 2)
+ ],
+ isExtensible: false);
+
+ var enumType = EnumProvider.Create(input);
+ Assert.IsNotNull(enumType);
+ Assert.IsNull(enumType.CustomCodeView);
+ // Should use the default namespace, not the assembly attribute's namespace
+ Assert.AreEqual("Sample.Models", enumType.Type.Namespace);
+ Assert.AreEqual("MockInputEnum", enumType.Type.Name);
+ }
}
}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs
new file mode 100644
index 00000000000..d28811202eb
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeExtensibleEnumNamespace/NamespaceOverride.cs
@@ -0,0 +1,3 @@
+using Microsoft.TypeSpec.Generator.Customizations;
+
+[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")]
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs
new file mode 100644
index 00000000000..d28811202eb
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespace/NamespaceOverride.cs
@@ -0,0 +1,3 @@
+using Microsoft.TypeSpec.Generator.Customizations;
+
+[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")]
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs
new file mode 100644
index 00000000000..d28811202eb
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespacePreservesMembers/NamespaceOverride.cs
@@ -0,0 +1,3 @@
+using Microsoft.TypeSpec.Generator.Customizations;
+
+[assembly: CodeGenNamespace("MockInputEnum", "NewNamespace.Models")]
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs
new file mode 100644
index 00000000000..e6e3400e260
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CanChangeFixedEnumNamespaceWithCodeGenTypeAndCodeGenNamespace/MockInputEnum.cs
@@ -0,0 +1,16 @@
+#nullable disable
+
+using Sample;
+using SampleTypeSpec;
+using Microsoft.TypeSpec.Generator.Customizations;
+
+// CodeGenType takes precedence over CodeGenNamespace when both are present
+[assembly: CodeGenNamespace("MockInputEnum", "OverriddenNamespace.Models")]
+
+namespace CustomCodeView.Models
+{
+ [CodeGenType("MockInputEnum")]
+ public enum RenamedEnum
+ {
+ }
+}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs
new file mode 100644
index 00000000000..86981dbb78c
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/TestData/EnumProviderCustomizationTests/CodeGenNamespaceIgnoredForNonMatchingType/NamespaceOverride.cs
@@ -0,0 +1,4 @@
+using Microsoft.TypeSpec.Generator.Customizations;
+
+// CodeGenNamespace targets a type that does not exist in the input
+[assembly: CodeGenNamespace("NonExistentType", "SomeOther.Namespace")]
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs
index 556948c476e..7e44a96fa16 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs
@@ -45,6 +45,24 @@ public async Task CanChangeEnumName()
AssertCommon(enumProvider, "NewNamespace.Models", "CustomizedEnum");
}
+ [Test]
+ public async Task CanChangeModelNamespaceWithAssemblyAttribute()
+ {
+ await MockHelpers.LoadMockGeneratorAsync(compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var props = new[]
+ {
+ InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String))
+ };
+
+ var inputModel = InputFactory.Model("mockInputModel", properties: props);
+ var modelTypeProvider = new ModelProvider(inputModel);
+
+ Assert.IsNull(modelTypeProvider.CustomCodeView, "CustomCodeView should be null since no custom type is defined");
+ Assert.AreEqual("NewNamespace.Models", modelTypeProvider.Type.Namespace);
+ Assert.AreEqual("MockInputModel", modelTypeProvider.Type.Name);
+ }
+
[Test]
public async Task CanChangePropertyName()
{
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs
new file mode 100644
index 00000000000..b472d0615da
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelNamespaceWithAssemblyAttribute/NamespaceOverride.cs
@@ -0,0 +1,3 @@
+using Microsoft.TypeSpec.Generator.Customizations;
+
+[assembly: CodeGenNamespace("MockInputModel", "NewNamespace.Models")]
diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs
new file mode 100644
index 00000000000..086a9d2a78b
--- /dev/null
+++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/CodeGenNamespaceAttribute.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT License.
+
+//
+
+#nullable disable
+
+using System;
+
+namespace Microsoft.TypeSpec.Generator.Customizations
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ internal partial class CodeGenNamespaceAttribute : Attribute
+ {
+ /// The original name of the type.
+ /// The new namespace for the type.
+ public CodeGenNamespaceAttribute(string typeName, string @namespace)
+ {
+ TypeName = typeName;
+ Namespace = @namespace;
+ }
+
+ /// Gets the original name of the type whose namespace should be changed.
+ public string TypeName { get; }
+
+ /// Gets the new namespace for the type.
+ public string Namespace { get; }
+ }
+}