diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs index a3e1879797..158b1085bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRoslynCompilationService.cs @@ -85,7 +85,6 @@ internal DefaultRoslynCompilationService( #if NETSTANDARD1_5 _razorLoadContext = new RazorLoadContext(); #endif - } /// @@ -144,6 +143,18 @@ public CompilationResult Compile(RelativeFileInfo fileInfo, string compilationCo if (!result.Success) { + if (!compilation.References.Any() && !_applicationReferences.Value.Any()) + { + // DependencyModel had no references specified and the user did not use the + // CompilationCallback to add extra references. It is likely that the user did not specify + // preserveCompilationContext in the app's project.json. + throw new InvalidOperationException( + Resources.FormatCompilation_DependencyContextIsNotSpecified( + fileInfo.RelativePath, + "project.json", + "preserveCompilationContext")); + } + return GetCompilationFailedResult( fileInfo.RelativePath, compilationContent, diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs index 5343888a29..a89c641c01 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs @@ -462,6 +462,22 @@ internal static string FormatLayoutHasCircularReference(object p0, object p1) return string.Format(CultureInfo.CurrentCulture, GetString("LayoutHasCircularReference"), p0, p1); } + /// + /// The Razor page '{0}' failed to compile. Ensure that your application's {1} sets the '{2}' compilation property. + /// + internal static string Compilation_DependencyContextIsNotSpecified + { + get { return GetString("Compilation_DependencyContextIsNotSpecified"); } + } + + /// + /// The Razor page '{0}' failed to compile. Ensure that your application's {1} sets the '{2}' compilation property. + /// + internal static string FormatCompilation_DependencyContextIsNotSpecified(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("Compilation_DependencyContextIsNotSpecified"), p0, p1, p2); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx index f94c7316aa..caac824d01 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx @@ -203,4 +203,7 @@ A circular layout reference was detected when rendering '{0}'. The layout page '{1}' has already been rendered. + + The Razor page '{0}' failed to compile. Ensure that your application's {1} sets the '{2}' compilation property. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs index 758bb1845b..ef2a00ead3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRoslynCompilationServiceTest.cs @@ -256,6 +256,7 @@ public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessa [Fact] public void Compile_RunsCallback() { + // Arrange var content = "public class MyTestType {}"; RoslynCompilationContext usedCompilation = null; @@ -276,6 +277,117 @@ public void Compile_RunsCallback() Assert.Single(usedCompilation.Compilation.SyntaxTrees); } + [Fact] + public void Compile_ThrowsIfDependencyContextIsNullAndTheApplicationFailsToCompileWithNoReferences() + { + // Arrange + var content = "public class MyTestType {}"; + var compilationService = new DefaultRoslynCompilationService( + dependencyContext: null, + viewEngineOptions: GetOptions(), + fileProviderAccessor: GetFileProviderAccessor(), + loggerFactory: NullLoggerFactory.Instance); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + var expected = "The Razor page 'some-relative-path.cshtml' failed to compile. Ensure that your " + + "application's project.json sets the 'preserveCompilationContext' compilation property."; + + // Act and Assert + var ex = Assert.Throws(() => + compilationService.Compile(relativeFileInfo, content)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Compile_ThrowsIfDependencyContextReturnsNoReferencesAndTheApplicationFailsToCompile() + { + // Arrange + var content = "public class MyTestType {}"; + var dependencyContext = new DependencyContext( + target: null, + runtime: null, + compilationOptions: Extensions.DependencyModel.CompilationOptions.Default, + compileLibraries: new CompilationLibrary[0], + runtimeLibraries: new RuntimeLibrary[0]); + var compilationService = new DefaultRoslynCompilationService( + dependencyContext: dependencyContext, + viewEngineOptions: GetOptions(), + fileProviderAccessor: GetFileProviderAccessor(), + loggerFactory: NullLoggerFactory.Instance); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + var expected = "The Razor page 'some-relative-path.cshtml' failed to compile. Ensure that your " + + "application's project.json sets the 'preserveCompilationContext' compilation property."; + + // Act and Assert + var ex = Assert.Throws(() => + compilationService.Compile(relativeFileInfo, content)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Compile_DoesNotThrowIfReferencesWereClearedInCallback() + { + // Arrange + var options = GetOptions(context => + { + context.Compilation = context.Compilation.RemoveAllReferences(); + }); + var content = "public class MyTestType {}"; + var compilationService = new DefaultRoslynCompilationService( + dependencyContext: GetDependencyContext(), + viewEngineOptions: options, + fileProviderAccessor: GetFileProviderAccessor(), + loggerFactory: NullLoggerFactory.Instance); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.Single(result.CompilationFailures); + } + + [Fact] + public void Compile_SucceedsIfReferencesAreAddedInCallback() + { + // Arrange + var options = GetOptions(context => + { + var assemblyLocation = typeof(object).GetTypeInfo().Assembly.Location; + + context.Compilation = context + .Compilation + .AddReferences(MetadataReference.CreateFromFile(assemblyLocation)); + }); + var content = "public class MyTestType {}"; + var compilationService = new DefaultRoslynCompilationService( + dependencyContext: null, + viewEngineOptions: options, + fileProviderAccessor: GetFileProviderAccessor(), + loggerFactory: NullLoggerFactory.Instance); + + var relativeFileInfo = new RelativeFileInfo( + new TestFileInfo { PhysicalPath = "SomePath" }, + "some-relative-path.cshtml"); + + // Act + var result = compilationService.Compile(relativeFileInfo, content); + + // Assert + Assert.Null(result.CompilationFailures); + Assert.NotNull(result.CompiledType); + } + private static DiagnosticDescriptor GetDiagnosticDescriptor(string messageFormat) { return new DiagnosticDescriptor(