From 624909763bf762c4448955d00eef1649737df8d6 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 7 Feb 2017 11:00:10 -0800 Subject: [PATCH] Make _PageImports work Add support for generating the Model property --- samples/MvcSandbox/Models/Index.cs | 1 + samples/MvcSandbox/Models/TestModel.cs | 9 ++ samples/MvcSandbox/Pages/Index.cshtml | 1 + samples/MvcSandbox/Pages/_PageImports.cshtml | 1 + .../PagesPropertyInjectionPass.cs | 61 ++++++++++++++ .../MvcRazorMvcCoreBuilderExtensions.cs | 1 + .../Internal/MvcRazorLoggerExtensions.cs | 2 +- .../Internal/RazorCompilationService.cs | 17 ++-- .../Internal/DefaultPageLoader.cs | 82 +++++++++++++++++-- .../Internal/RazorCompilationServiceTest.cs | 3 +- 10 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 samples/MvcSandbox/Models/Index.cs create mode 100644 samples/MvcSandbox/Models/TestModel.cs create mode 100644 samples/MvcSandbox/Pages/_PageImports.cshtml create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs diff --git a/samples/MvcSandbox/Models/Index.cs b/samples/MvcSandbox/Models/Index.cs new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/samples/MvcSandbox/Models/Index.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/MvcSandbox/Models/TestModel.cs b/samples/MvcSandbox/Models/TestModel.cs new file mode 100644 index 0000000000..49c72dc2aa --- /dev/null +++ b/samples/MvcSandbox/Models/TestModel.cs @@ -0,0 +1,9 @@ + +namespace MvcSandbox +{ + public class TestModel + { + public string Name { get; set; } + + } +} diff --git a/samples/MvcSandbox/Pages/Index.cshtml b/samples/MvcSandbox/Pages/Index.cshtml index ab2ffb46fd..1cc8dea003 100644 --- a/samples/MvcSandbox/Pages/Index.cshtml +++ b/samples/MvcSandbox/Pages/Index.cshtml @@ -1,4 +1,5 @@ @page Test +@model TestModel
diff --git a/samples/MvcSandbox/Pages/_PageImports.cshtml b/samples/MvcSandbox/Pages/_PageImports.cshtml new file mode 100644 index 0000000000..61d5243f3a --- /dev/null +++ b/samples/MvcSandbox/Pages/_PageImports.cshtml @@ -0,0 +1 @@ +@using MvcSandbox \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs new file mode 100644 index 0000000000..eb0d45e63d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Host/PagesPropertyInjectionPass.cs @@ -0,0 +1,61 @@ +// 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.Evolution; +using Microsoft.AspNetCore.Razor.Evolution.Intermediate; + +namespace Microsoft.AspNetCore.Mvc.Razor.Host +{ + public class PagesPropertyInjectionPass : IRazorIRPass + { + public RazorEngine Engine { get; set; } + + public int Order => RazorIRPass.LoweringOrder; + + public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument) + { + if (irDocument.DocumentKind != RazorPageDocumentClassifier.DocumentKind) + { + return irDocument; + } + + var modelType = ModelDirective.GetModelType(irDocument); + var visitor = new Visitor(); + visitor.Visit(irDocument); + + var @class = visitor.Class; + + var viewDataType = $"global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary<{modelType}>"; + var vddProperty = new CSharpStatementIRNode + { + Content = $"public {viewDataType} ViewData => ({viewDataType})PageContext?.ViewData;", + Parent = @class, + }; + var modelProperty = new CSharpStatementIRNode + { + Content = $"public {modelType} Model => ViewData.Model;", + Parent = @class, + }; + + @class.Children.Add(vddProperty); + @class.Children.Add(modelProperty); + + return irDocument; + } + + private class Visitor : RazorIRNodeWalker + { + public ClassDeclarationIRNode Class { get; private set; } + + public override void VisitClass(ClassDeclarationIRNode node) + { + if (Class == null) + { + Class = node; + } + + base.VisitClass(node); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 0452fba843..1a5b5aff99 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -186,6 +186,7 @@ namespace Microsoft.Extensions.DependencyInjection PageDirective.Register(b); b.Features.Add(new ModelExpressionPass()); + b.Features.Add(new PagesPropertyInjectionPass()); b.Features.Add(new ViewComponentTagHelperPass()); b.Features.Add(new RazorPageDocumentClassifier()); b.Features.Add(new MvcViewDocumentClassifierPass()); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs index 2342938e6c..8d5dc6a1dd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { - internal static class MvcRazorLoggerExtensions + public static class MvcRazorLoggerExtensions { private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorCompilationService.cs index 512b6227eb..e00c9858c3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorCompilationService.cs @@ -9,8 +9,6 @@ using System.Linq; using System.Text; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Razor; -using Microsoft.AspNetCore.Razor.CodeGenerators; using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; @@ -27,7 +25,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly RazorProject _project; private readonly IFileProvider _fileProvider; private readonly ILogger _logger; - private readonly RazorSourceDocument _globalImports; /// /// Instantiates a new instance of the class. @@ -68,10 +65,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal writer.Flush(); stream.Seek(0L, SeekOrigin.Begin); - _globalImports = RazorSourceDocument.ReadFrom(stream, filename: null, encoding: Encoding.UTF8); + GlobalImports = RazorSourceDocument.ReadFrom(stream, filename: null, encoding: Encoding.UTF8); } - + public RazorSourceDocument GlobalImports { get; } /// public CompilationResult Compile(RelativeFileInfo file) @@ -97,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal if (cSharpDocument.Diagnostics.Count > 0) { - return GetCompilationFailedResult(file, cSharpDocument.Diagnostics); + return GetCompilationFailedResult(file.RelativePath, cSharpDocument.Diagnostics); } return _compilationService.Compile(codeDocument, cSharpDocument); @@ -111,7 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var imports = new List() { - _globalImports, + GlobalImports, }; var paths = ViewHierarchyUtility.GetViewImportsLocations(relativePath); @@ -138,15 +135,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal } // Internal for unit testing - internal CompilationResult GetCompilationFailedResult( - RelativeFileInfo file, + public CompilationResult GetCompilationFailedResult( + string relativePath, IEnumerable errors) { // If a SourceLocation does not specify a file path, assume it is produced // from parsing the current file. var messageGroups = errors .GroupBy(razorError => - razorError.Location.FilePath ?? file.RelativePath, + razorError.Location.FilePath ?? relativePath, StringComparer.Ordinal); var failures = new List(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs index d70c00f85e..a15d24cfcc 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs @@ -2,24 +2,35 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Razor.Evolution; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public class DefaultPageLoader : IPageLoader { - private readonly IRazorCompilationService _razorCompilationService; + private readonly RazorCompilationService _razorCompilationService; + private readonly ICompilationService _compilationService; private readonly RazorProject _project; + private readonly ILogger _logger; public DefaultPageLoader( IRazorCompilationService razorCompilationService, - RazorProject razorProject) + ICompilationService compilationService, + RazorProject razorProject, + ILogger logger) { - _razorCompilationService = razorCompilationService; + _razorCompilationService = (RazorCompilationService)razorCompilationService; + _compilationService = compilationService; _project = razorProject; + _logger = logger; } public Type Load(PageActionDescriptor actionDescriptor) @@ -30,11 +41,70 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal throw new InvalidOperationException($"File {actionDescriptor.RelativePath} was not found."); } - var projectItem = (DefaultRazorProjectItem)item; - var compilationResult = _razorCompilationService.Compile(new RelativeFileInfo(projectItem.FileInfo, item.Path)); - compilationResult.EnsureSuccessful(); + RazorCodeDocument codeDocument; + RazorCSharpDocument cSharpDocument; + _logger.RazorFileToCodeCompilationStart(item.Path); + var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0; + + codeDocument = CreateCodeDocument(item); + cSharpDocument = _razorCompilationService.ProcessCodeDocument(codeDocument); + + _logger.RazorFileToCodeCompilationEnd(item.Path, startTimestamp); + + CompilationResult compilationResult; + if (cSharpDocument.Diagnostics.Count > 0) + { + compilationResult = _razorCompilationService.GetCompilationFailedResult(item.Path, cSharpDocument.Diagnostics); + } + else + { + compilationResult = _compilationService.Compile(codeDocument, cSharpDocument); + } + + compilationResult.EnsureSuccessful(); return compilationResult.CompiledType; } + + private RazorCodeDocument CreateCodeDocument(RazorProjectItem item) + { + var absolutePath = GetItemPath(item); + + RazorSourceDocument source; + using (var inputStream = item.Read()) + { + source = RazorSourceDocument.ReadFrom(inputStream, absolutePath); + } + + var imports = new List() + { + _razorCompilationService.GlobalImports, + }; + + var pageImports = _project.FindHierarchicalItems(item.Path, "_PageImports.cshtml"); + foreach (var pageImport in pageImports.Reverse()) + { + if (pageImport.Exists) + { + using (var stream = pageImport.Read()) + { + imports.Add(RazorSourceDocument.ReadFrom(stream, GetItemPath(item))); + } + } + } + + return RazorCodeDocument.Create(source, imports); + } + + private static string GetItemPath(RazorProjectItem item) + { + var absolutePath = item.Path; + if (item.Exists && string.IsNullOrEmpty(item.PhysicalPath)) + { + absolutePath = item.PhysicalPath; + } + + return absolutePath; + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorCompilationServiceTest.cs index a0a0f734a5..f44701967d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorCompilationServiceTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorCompilationServiceTest.cs @@ -157,7 +157,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var fileProvider = new TestFileProvider(); var file = fileProvider.AddFile(viewPath, "View Content"); fileProvider.AddFile(viewImportsPath, "Global Import Content"); - var relativeFileInfo = new RelativeFileInfo(file, viewPath); var razorService = new RazorCompilationService( Mock.Of(), Mock.Of(), @@ -173,7 +172,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; // Act - var result = razorService.GetCompilationFailedResult(relativeFileInfo, errors); + var result = razorService.GetCompilationFailedResult(viewPath, errors); // Assert Assert.NotNull(result.CompilationFailures);