diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.codegen.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.codegen.cs index a440afb781..ee8a246582 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.codegen.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.codegen.cs @@ -1,6 +1,6 @@ // #pragma warning disable 1591 -namespace __BlazorGenerated +namespace __GeneratedComponent { #line hidden using TModel = global::System.Object; diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.ir.txt b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.ir.txt index 2f6aa48182..7cc88c4c21 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.ir.txt +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.ir.txt @@ -1,5 +1,5 @@ Document - - NamespaceDeclaration - - __BlazorGenerated + NamespaceDeclaration - - __GeneratedComponent UsingDirective - - TModel = global::System.Object UsingDirective - (1:0,1 [12] ) - System UsingDirective - (16:1,1 [32] ) - System.Collections.Generic diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.mappings.txt b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.mappings.txt index 0baa77e8ca..9220571512 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.mappings.txt +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_DesignTime.mappings.txt @@ -1,23 +1,23 @@ Source Location: (12:0,12 [11] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) |IDisposable| -Generated Location: (662:17,0 [11] ) +Generated Location: (665:17,0 [11] ) |IDisposable| Source Location: (38:1,13 [15] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) |this.ToString()| -Generated Location: (1258:33,13 [15] ) +Generated Location: (1261:33,13 [15] ) |this.ToString()| Source Location: (79:3,5 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) |string.Format("{0}", "Hello")| -Generated Location: (1403:38,6 [29] ) +Generated Location: (1406:38,6 [29] ) |string.Format("{0}", "Hello")| Source Location: (132:6,12 [37] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml) | void IDisposable.Dispose(){ } | -Generated Location: (1617:45,12 [37] ) +Generated Location: (1620:45,12 [37] ) | void IDisposable.Dispose(){ } | diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.codegen.cs b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.codegen.cs index c376721682..63ec0d9879 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.codegen.cs +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.codegen.cs @@ -1,7 +1,7 @@ #pragma checksum "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "d3c3d6059615673cb46fc4974164d61eabadb890" // #pragma warning disable 1591 -namespace __BlazorGenerated +namespace __GeneratedComponent { #line hidden using System; diff --git a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.ir.txt b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.ir.txt index 65c88c6ce0..ab41d275db 100644 --- a/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.ir.txt +++ b/src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/BasicComponent_Runtime.ir.txt @@ -1,5 +1,5 @@ Document - - NamespaceDeclaration - - __BlazorGenerated + NamespaceDeclaration - - __GeneratedComponent UsingDirective - (1:0,1 [14] ) - System UsingDirective - (16:1,1 [34] ) - System.Collections.Generic UsingDirective - (51:2,1 [19] ) - System.Linq diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs index 4a992de20a..9c4a7a9f2a 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Components/ComponentDocumentClassifierPass.cs @@ -18,11 +18,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Components private static readonly char[] NamespaceSeparators = new char[] { '.' }; /// - /// The base namespace. + /// The fallback value of the root namespace. Only used if the fallback root namespace + /// was not passed in. /// - // This is a fallback value and will only be used if we can't compute - // a reasonable namespace. - public string BaseNamespace { get; set; } = "__BlazorGenerated"; + public string FallbackRootNamespace { get; set; } = "__GeneratedComponent"; /// /// Gets or sets whether to mangle class names. @@ -58,7 +57,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Components ClassDeclarationIntermediateNode @class, MethodDeclarationIntermediateNode method) { + var options = codeDocument.GetDocumentIntermediateNode().Options; if (!TryComputeNamespaceAndClass( + options, codeDocument.Source.FilePath, codeDocument.Source.RelativePath, out var computedNamespace, @@ -66,7 +67,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components { // If we can't compute a nice namespace (no relative path) then just generate something // mangled. - computedNamespace = BaseNamespace; + computedNamespace = FallbackRootNamespace; var checksum = Checksum.BytesToString(codeDocument.Source.GetChecksum()); computedClass = $"AspNetCore_{checksum}"; } @@ -125,7 +126,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Components // // However all kinds of thing are possible in tools. We shouldn't barf here if the document isn't // set up correctly. - private bool TryComputeNamespaceAndClass(string filePath, string relativePath, out string @namespace, out string @class) + private bool TryComputeNamespaceAndClass( + RazorCodeGenerationOptions options, + string filePath, + string relativePath, + out string @namespace, + out string @class) { if (filePath == null || relativePath == null || filePath.Length <= relativePath.Length) { @@ -134,14 +140,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Components return false; } - // Try and infer a namespace from the project directory. We don't yet have the ability to pass - // the namespace through from the project. - var trimLength = relativePath.Length + (relativePath.StartsWith("/") ? 0 : 1); - var baseDirectory = filePath.Substring(0, filePath.Length - trimLength); - - var lastSlash = baseDirectory.LastIndexOfAny(PathSeparators); - var baseNamespace = lastSlash == -1 ? baseDirectory : baseDirectory.Substring(lastSlash + 1); - if (string.IsNullOrEmpty(baseNamespace)) + var rootNamespace = options.RootNamespace; + if (string.IsNullOrEmpty(rootNamespace)) { @namespace = null; @class = null; @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Components var builder = new StringBuilder(); // Sanitize the base namespace, but leave the dots. - var segments = baseNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); + var segments = rootNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries); builder.Append(CSharpIdentifier.SanitizeIdentifier(segments[0])); for (var i = 1; i < segments.Length; i++) { diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs index 6b694d9382..d97eb15af8 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptions.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language public DefaultRazorCodeGenerationOptions( bool indentWithTabs, int indentSize, - bool designTime, + bool designTime, + string rootNamespace, bool suppressChecksum, bool supressMetadataAttributes, bool suppressPrimaryMethodBody) @@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Language IndentWithTabs = indentWithTabs; IndentSize = indentSize; DesignTime = designTime; + RootNamespace = rootNamespace; SuppressChecksum = suppressChecksum; SuppressMetadataAttributes = supressMetadataAttributes; SuppressPrimaryMethodBody = suppressPrimaryMethodBody; @@ -27,6 +29,8 @@ namespace Microsoft.AspNetCore.Razor.Language public override int IndentSize { get; } + public override string RootNamespace { get; } + public override bool SuppressChecksum { get; } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs index b54f339b51..dbf795587f 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorCodeGenerationOptionsBuilder.cs @@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Razor.Language IndentWithTabs, IndentSize, DesignTime, + RootNamespace, SuppressChecksum, SuppressMetadataAttributes, SuppressPrimaryMethodBody); diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs index ad0a7bc95a..4a3f479396 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptions.cs @@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Razor.Language indentSize: 4, designTime: false, suppressChecksum: false, + rootNamespace: null, supressMetadataAttributes: false, suppressPrimaryMethodBody: false); } @@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Razor.Language indentWithTabs: false, indentSize: 4, designTime: true, + rootNamespace: null, suppressChecksum: false, supressMetadataAttributes: true, suppressPrimaryMethodBody: false); @@ -67,6 +69,11 @@ namespace Microsoft.AspNetCore.Razor.Language public abstract int IndentSize { get; } + /// + /// Gets the root namespace for the generated code. + /// + public virtual string RootNamespace { get; } + /// /// Gets a value that indicates whether to suppress the default #pragma checksum directive in the /// generated C# code. If false the checkum directive will be included, otherwise it will not be diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs index 8d9ea917f4..e2813630c5 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorCodeGenerationOptionsBuilder.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Language public abstract bool IndentWithTabs { get; set; } + /// + /// Gets or sets the root namespace of the generated code. + /// + public virtual string RootNamespace { get; set; } + /// /// Gets or sets a value that indicates whether to suppress the default #pragma checksum directive in the /// generated C# code. If false the checkum directive will be included, otherwise it will not be diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs index cffead3a8a..09fdf087f4 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/RazorProjectEngineBuilderExtensions.cs @@ -75,6 +75,23 @@ namespace Microsoft.AspNetCore.Razor.Language return builder; } + /// + /// Sets the root namespace for the generated code. + /// + /// The . + /// The root namespace. + /// The . + public static RazorProjectEngineBuilder SetRootNamespace(this RazorProjectEngineBuilder builder, string rootNamespace) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Features.Add(new ConfigureRootNamespaceFeature(rootNamespace)); + return builder; + } + public static void SetImportFeature(this RazorProjectEngineBuilder builder, IImportProjectFeature feature) { if (builder == null) @@ -281,5 +298,29 @@ namespace Microsoft.AspNetCore.Razor.Language public override Stream Read() => new MemoryStream(_importBytes); } } + + private class ConfigureRootNamespaceFeature : IConfigureRazorCodeGenerationOptionsFeature + { + private readonly string _rootNamespace; + + public ConfigureRootNamespaceFeature(string rootNamespace) + { + _rootNamespace = rootNamespace; + } + + public int Order { get; set; } + + public RazorEngine Engine { get; set; } + + public void Configure(RazorCodeGenerationOptionsBuilder options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.RootNamespace = _rootNamespace; + } + } } } diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs index e912b8ec26..f2e1b76b7a 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Tools/src/GenerateCommand.cs @@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Tools Configuration = Option("-c", "Razor configuration name", CommandOptionType.SingleValue); ExtensionNames = Option("-n", "extension name", CommandOptionType.MultipleValue); ExtensionFilePaths = Option("-e", "extension file path", CommandOptionType.MultipleValue); + RootNamespace = Option("--root-namespace", "root namespace for generated code", CommandOptionType.SingleValue); GenerateDeclaration = Option("--generate-declaration", "Generate declaration", CommandOptionType.NoValue); } @@ -52,6 +53,8 @@ namespace Microsoft.AspNetCore.Razor.Tools public CommandOption ExtensionFilePaths { get; } + public CommandOption RootNamespace { get; } + public CommandOption GenerateDeclaration { get; } protected override Task ExecuteCoreAsync() @@ -176,6 +179,11 @@ namespace Microsoft.AspNetCore.Razor.Tools { b.Features.Add(new SetSuppressPrimaryMethodBodyOptionFeature()); } + + if (RootNamespace.HasValue()) + { + b.SetRootNamespace(RootNamespace.Value()); + } }); var results = GenerateCode(engine, sourceItems); diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs b/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs index 07892cfa38..6e8364eea5 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/RazorGenerate.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNetCore.Razor.Tasks private const string AssemblyName = "AssemblyName"; private const string AssemblyFilePath = "AssemblyFilePath"; + public string RootNamespace { get; set; } + [Required] public string Version { get; set; } @@ -135,6 +137,13 @@ namespace Microsoft.AspNetCore.Razor.Tasks builder.AppendLine("-c"); builder.AppendLine(Configuration[0].GetMetadata(Identity)); + // Added in 3.0 + if (parsedVersion.Major >= 3 && !string.IsNullOrEmpty(RootNamespace)) + { + builder.AppendLine("--root-namespace"); + builder.AppendLine(RootNamespace); + } + // Added in 3.0 if (parsedVersion.Major >= 3 && GenerateDeclaration) { diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets index 72599af998..89b921e422 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.CodeGeneration.targets @@ -155,6 +155,7 @@ Copyright (c) .NET Foundation. All rights reserved. ForceServer="$(_RazorForceBuildServer)" PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" + RootNamespace="$(RootNamespace)" Configuration="@(ResolvedRazorConfiguration)" Extensions="@(ResolvedRazorExtension)" Sources="@(RazorGenerateWithTargetPath)" diff --git a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets index 15088d3206..6061d8cbc8 100644 --- a/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets +++ b/src/Razor/Microsoft.NET.Sdk.Razor/src/build/netstandard2.0/Microsoft.NET.Sdk.Razor.Component.targets @@ -98,6 +98,7 @@ Copyright (c) .NET Foundation. All rights reserved. ForceServer="$(_RazorForceBuildServer)" PipeName="$(_RazorBuildServerPipeName)" Version="$(RazorLangVersion)" + RootNamespace="$(RootNamespace)" Configuration="@(ResolvedRazorConfiguration)" Extensions="@(ResolvedRazorExtension)" Sources="@(_RazorComponentDeclarationSources)" diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs index 6c6ca1fc49..8a9c10aa8c 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.Test.Common/Language/IntegrationTests/RazorIntegrationTestBase.cs @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests PathSeparator = Path.DirectorySeparatorChar.ToString(); WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath; - DefaultBaseNamespace = "Test"; // Matches the default working directory + DefaultRootNamespace = "Test"; // Matches the default working directory DefaultFileName = "TestComponent.cshtml"; } @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests internal virtual RazorConfiguration Configuration { get; } - internal virtual string DefaultBaseNamespace { get; } + internal virtual string DefaultRootNamespace { get; } internal virtual string DefaultFileName { get; } @@ -113,6 +113,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { return RazorProjectEngine.Create(configuration, FileSystem, b => { + b.SetRootNamespace(DefaultRootNamespace); + // Turn off checksums, we're testing code generation. b.Features.Add(new SuppressChecksum()); @@ -320,7 +322,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests { var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource); - var componentFullTypeName = $"{DefaultBaseNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}"; + var componentFullTypeName = $"{DefaultRootNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}"; return CompileToComponent(assemblyResult, componentFullTypeName); }