From f649de2976c6340b8bac0b1ae41f87cc148b4448 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Sun, 18 Feb 2018 23:57:20 +0000 Subject: [PATCH] Support _ViewImports.cshtml files hierarchically --- .../RazorCompilation/BlazorProjectItem.cs | 35 ++++++++++++++++ .../Core/RazorCompilation/RazorCompiler.cs | 11 ++--- .../BlazorRazorEngine.cs | 5 +-- .../BlazorTemplateEngine.cs | 41 +++++++++++++++++++ .../RazorCompilerTest.cs | 4 +- .../Tests/ComponentRenderingTest.cs | 14 +++++++ .../Subdir/ComponentUsingImports.cshtml | 5 +++ .../Subdir/_ViewImports.cshtml | 1 + .../_ViewImports.cshtml | 1 + 9 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/BlazorProjectItem.cs create mode 100644 src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorTemplateEngine.cs create mode 100644 test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/ComponentUsingImports.cshtml create mode 100644 test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/_ViewImports.cshtml create mode 100644 test/testapps/BasicTestApp/HierarchicalImportsTest/_ViewImports.cshtml diff --git a/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/BlazorProjectItem.cs b/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/BlazorProjectItem.cs new file mode 100644 index 0000000000..95bf70376f --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/BlazorProjectItem.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Razor.Language; +using System.IO; + +namespace Microsoft.AspNetCore.Blazor.Build.Core.RazorCompilation +{ + internal class BlazorProjectItem : RazorProjectItem + { + private readonly string _projectBasePath; + private readonly string _itemFullPhysicalPath; + private readonly Stream _itemContents; + + public BlazorProjectItem( + string projectBasePath, + string itemFullPhysicalPath, + Stream itemFileContents) + { + _projectBasePath = projectBasePath; + _itemFullPhysicalPath = itemFullPhysicalPath; + _itemContents = itemFileContents; + } + + public override string BasePath => _projectBasePath; + + public override string FilePath => _itemFullPhysicalPath; + + public override string PhysicalPath => _itemFullPhysicalPath; + + public override bool Exists => true; + + public override Stream Read() => _itemContents; + } +} diff --git a/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs b/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs index cc1f6bc5b3..b573bdcf00 100644 --- a/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs +++ b/src/Microsoft.AspNetCore.Blazor.Build/Core/RazorCompilation/RazorCompiler.cs @@ -94,13 +94,14 @@ namespace Microsoft.AspNetCore.Blazor.Build.Core.RazorCompilation // invocations. var engine = new BlazorRazorEngine(); - - var sourceDoc = RazorSourceDocument.ReadFrom(inputFileContents, inputFilePath); - var codeDoc = RazorCodeDocument.Create(sourceDoc); + var blazorTemplateEngine = new BlazorTemplateEngine( + engine.Engine, + RazorProject.Create(inputRootPath)); + var codeDoc = blazorTemplateEngine.CreateCodeDocument( + new BlazorProjectItem(inputRootPath, inputFilePath, inputFileContents)); codeDoc.Items[BlazorCodeDocItems.Namespace] = combinedNamespace; codeDoc.Items[BlazorCodeDocItems.ClassName] = itemClassName; - engine.Process(codeDoc); - var csharpDocument = codeDoc.GetCSharpDocument(); + var csharpDocument = blazorTemplateEngine.GenerateCode(codeDoc); var generatedCode = csharpDocument.GeneratedCode; // Add parameters to the primary method via string manipulation because diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRazorEngine.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRazorEngine.cs index 66d29fb6d0..b8d708402d 100644 --- a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRazorEngine.cs +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorRazorEngine.cs @@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Blazor.Razor private readonly RazorEngine _engine; private readonly RazorCodeGenerationOptions _codegenOptions; + public RazorEngine Engine => _engine; + public BlazorRazorEngine() { _codegenOptions = RazorCodeGenerationOptions.CreateDefault(); @@ -39,8 +41,5 @@ namespace Microsoft.AspNetCore.Blazor.Razor }); }); } - - public void Process(RazorCodeDocument document) - => _engine.Process(document); } } diff --git a/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorTemplateEngine.cs b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorTemplateEngine.cs new file mode 100644 index 0000000000..abba4ececf --- /dev/null +++ b/src/Microsoft.AspNetCore.Blazor.Razor.Extensions/BlazorTemplateEngine.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using Microsoft.AspNetCore.Razor.Language; +using System.Text; + +namespace Microsoft.AspNetCore.Blazor.Razor +{ + /// + /// A for Blazor components. + /// + public class BlazorTemplateEngine : RazorTemplateEngine + { + public BlazorTemplateEngine(RazorEngine engine, RazorProject project) + : base(engine, project) + { + Options.ImportsFileName = "_ViewImports.cshtml"; + Options.DefaultImports = GetDefaultImports(); + } + + private static RazorSourceDocument GetDefaultImports() + { + using (var stream = new MemoryStream()) + using (var writer = new StreamWriter(stream, Encoding.UTF8)) + { + // TODO: Add other commonly-used Blazor namespaces here. Can't do so yet + // because the tooling wouldn't know about it, so it would still look like + // an error if you hadn't explicitly imported them. + writer.WriteLine("@using System"); + writer.WriteLine("@using System.Collections.Generic"); + writer.WriteLine("@using System.Linq"); + writer.WriteLine("@using System.Threading.Tasks"); + writer.Flush(); + + stream.Position = 0; + return RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs index 8fc9adab43..7ae3467e5d 100644 --- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RazorCompilerTest.cs @@ -496,7 +496,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test { compilation.Emit(peStream); - var diagnostics = compilation.GetDiagnostics(); + var diagnostics = compilation + .GetDiagnostics() + .Where(d => d.Severity != DiagnosticSeverity.Hidden); return new CompileToAssemblyResult { Diagnostics = diagnostics, diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs index aad402072d..307255c5ca 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Configuration.Assemblies; using System.Linq; +using System.Numerics; using BasicTestApp; +using BasicTestApp.HierarchicalImportsTest.Subdir; using Microsoft.AspNetCore.Blazor.Components; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; @@ -189,6 +192,17 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Assert.Empty(fragmentElements); } + [Fact] + public void CanUseViewImportsHierarchically() + { + // The component is able to compile and output these type names only because + // of the _ViewImports.cshtml files at the same and ancestor levels + var appElement = MountTestComponent(); + Assert.Collection(appElement.FindElements(By.TagName("p")), + elem => Assert.Equal(typeof(Complex).FullName, elem.Text), + elem => Assert.Equal(typeof(AssemblyHashAlgorithm).FullName, elem.Text)); + } + private IWebElement MountTestComponent() where TComponent: IComponent { var componentTypeName = typeof(TComponent).FullName; diff --git a/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/ComponentUsingImports.cshtml b/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/ComponentUsingImports.cshtml new file mode 100644 index 0000000000..c7b1530e5a --- /dev/null +++ b/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/ComponentUsingImports.cshtml @@ -0,0 +1,5 @@ +The following two outputs rely on "using" directives in _ViewImports files at the current and ancestor levels. + +

@(typeof(Complex).FullName)

+ +

@(typeof(AssemblyHashAlgorithm).FullName)

diff --git a/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/_ViewImports.cshtml b/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/_ViewImports.cshtml new file mode 100644 index 0000000000..ff4b8e8cd1 --- /dev/null +++ b/test/testapps/BasicTestApp/HierarchicalImportsTest/Subdir/_ViewImports.cshtml @@ -0,0 +1 @@ +@using System.Configuration.Assemblies diff --git a/test/testapps/BasicTestApp/HierarchicalImportsTest/_ViewImports.cshtml b/test/testapps/BasicTestApp/HierarchicalImportsTest/_ViewImports.cshtml new file mode 100644 index 0000000000..ce47793529 --- /dev/null +++ b/test/testapps/BasicTestApp/HierarchicalImportsTest/_ViewImports.cshtml @@ -0,0 +1 @@ +@using System.Numerics