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);