/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Mvc/Mvc.Razor.RuntimeCompilation/test/RuntimeViewCompilerTest.cs
909 строк
30 KB
Brennan
Obsolete RazorRuntimeCompilation (#62793)
18 июл 2025, 20:59
Не верифицирован
18 июл 2025, 20:59
223fe49
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Emit; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using static Microsoft.AspNetCore.Razor.Hosting.TestRazorCompiledItem; namespace Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; public class RuntimeViewCompilerTest { [Fact] public async Task CompileAsync_ReturnsResultWithNullAttribute_IfFileIsNotFoundInFileSystem() { // Arrange var path = "/file/does-not-exist"; var fileProvider = new TestFileProvider(); var viewCompiler = GetViewCompiler(fileProvider); // Act var result1 = await viewCompiler.CompileAsync(path); var result2 = await viewCompiler.CompileAsync(path); // Assert Assert.Same(result1, result2); Assert.Null(result1.Item); var token = Assert.Single(result1.ExpirationTokens); Assert.Same(fileProvider.GetChangeToken(path), token); } [Fact] public async Task CompileAsync_ReturnsResultWithExpirationToken() { // Arrange var path = "/file/does-not-exist"; var fileProvider = new TestFileProvider(); var viewCompiler = GetViewCompiler(fileProvider); // Act var result1 = await viewCompiler.CompileAsync(path); var result2 = await viewCompiler.CompileAsync(path); // Assert Assert.Same(result1, result2); Assert.Null(result1.Item); Assert.Collection( result1.ExpirationTokens, token => Assert.Equal(fileProvider.GetChangeToken(path), token)); } [Fact] public async Task CompileAsync_AddsChangeTokensForViewStartsIfFileExists() { // Arrange var path = "/file/exists/FilePath.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.NotNull(result.Item); Assert.Collection( result.ExpirationTokens, token => Assert.Same(fileProvider.GetChangeToken(path), token), token => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), token), token => Assert.Same(fileProvider.GetChangeToken("/file/_ViewImports.cshtml"), token), token => Assert.Same(fileProvider.GetChangeToken("/file/exists/_ViewImports.cshtml"), token)); } [Theory] [InlineData("/Areas/Finances/Views/Home/Index.cshtml")] [InlineData(@"Areas\Finances\Views\Home\Index.cshtml")] [InlineData(@"\Areas\Finances\Views\Home\Index.cshtml")] [InlineData(@"\Areas\Finances\Views/Home\Index.cshtml")] public async Task CompileAsync_NormalizesPathSeparatorForPaths(string relativePath) { // Arrange var viewPath = "/Areas/Finances/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(viewPath, "some content"); var viewCompiler = GetViewCompiler(fileProvider); // Act - 1 var result1 = await viewCompiler.CompileAsync(@"Areas\Finances\Views\Home\Index.cshtml"); // Act - 2 viewCompiler.Compile = _ => throw new Exception("Can't call me"); var result2 = await viewCompiler.CompileAsync(relativePath); // Assert - 2 Assert.Same(result1, result2); } [Fact] public async Task CompileAsync_InvalidatesCache_IfChangeTokenExpires() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var fileNode = fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); // Act 1 var result1 = await viewCompiler.CompileAsync(path); // Assert 1 Assert.NotNull(result1.Item); // Act 2 // Simulate deleting the file fileProvider.GetChangeToken(path).HasChanged = true; fileProvider.DeleteFile(path); viewCompiler.Compile = _ => throw new Exception("Can't call me"); var result2 = await viewCompiler.CompileAsync(path); // Assert 2 Assert.NotSame(result1, result2); Assert.Null(result2.Item); } [Fact] public async Task CompileAsync_ReturnsNewResultIfFileWasModified() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); var expected2 = new CompiledViewDescriptor(); // Act 1 var result1 = await viewCompiler.CompileAsync(path); // Assert 1 Assert.NotNull(result1.Item); // Act 2 fileProvider.GetChangeToken(path).HasChanged = true; viewCompiler.Compile = _ => expected2; var result2 = await viewCompiler.CompileAsync(path); // Assert 2 Assert.NotSame(result1, result2); Assert.Same(expected2, result2); } [Fact] public async Task CompileAsync_ReturnsNewResult_IfAncestorViewImportsWereModified() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); var expected2 = new CompiledViewDescriptor(); // Act 1 var result1 = await viewCompiler.CompileAsync(path); // Assert 1 Assert.NotNull(result1.Item); // Act 2 fileProvider.GetChangeToken("/Views/_ViewImports.cshtml").HasChanged = true; viewCompiler.Compile = _ => expected2; var result2 = await viewCompiler.CompileAsync(path); // Assert 2 Assert.NotSame(result1, result2); Assert.Same(expected2, result2); } [Fact] public async Task CompileAsync_ReturnsPrecompiledViews() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.Same(precompiledView, result); // This view doesn't have checksums so it can't be recompiled. Assert.Null(precompiledView.ExpirationTokens); } [Theory] [InlineData("/views/home/index.cshtml")] [InlineData("/VIEWS/HOME/INDEX.CSHTML")] [InlineData("/viEws/HoME/inDex.cshtml")] public async Task CompileAsync_PerformsCaseInsensitiveLookupsForPrecompiledViews(string lookupPath) { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync(lookupPath); // Assert Assert.Same(precompiledView, result); } [Fact] public async Task CompileAsync_PerformsCaseInsensitiveLookupsForPrecompiledViews_WithNonNormalizedPaths() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync("Views\\Home\\Index.cshtml"); // Assert Assert.Same(precompiledView, result); } [Fact] public async Task CompileAsync_PrecompiledViewWithoutChecksumForMainSource_DoesNotSupportRecompilation() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("sha1", GetChecksum("some content"), "/Views/Some-Other-View"), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); // Act - 2 fileProvider.Watch(path); fileProvider.GetChangeToken(path).HasChanged = true; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(precompiledView.Item, result.Item); // This view doesn't have checksums so it can't be recompiled. Assert.Null(result.ExpirationTokens); } [Fact] public async Task CompileAsync_PrecompiledViewWithoutAnyChecksum_DoesNotSupportRecompilation() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView, result); // Act - 2 fileProvider.Watch(path); fileProvider.GetChangeToken(path).HasChanged = true; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(precompiledView, result); // This view doesn't have checksums so it can't be recompiled. Assert.Null(result.ExpirationTokens); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_UsesPrecompiledViewWhenChecksumIsMatch() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.Same(precompiledView.Item, result.Item); // This view has checksums so it should also have tokens Assert.Collection( result.ExpirationTokens, token => Assert.Same(fileProvider.GetChangeToken(path), token)); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_CanRejectWhenChecksumFails() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var expected = new CompiledViewDescriptor(); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some other content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); viewCompiler.Compile = _ => expected; // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.Same(expected, result); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_AddsExpirationTokensForFilesInChecksumAttributes() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.Same(precompiledView.Item, result.Item); var token = Assert.Single(result.ExpirationTokens); Assert.Same(fileProvider.GetChangeToken(path), token); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var file = fileProvider.AddFile(path, "some content"); var expected2 = new CompiledViewDescriptor(); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); Assert.NotEmpty(result.ExpirationTokens); // Act - 2 file.Content = "different"; fileProvider.GetChangeToken(path).HasChanged = true; viewCompiler.Compile = _ => expected2; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(expected2, result); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_DoesNotRecompiledWithoutContentChange() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); // Act - 2 fileProvider.GetChangeToken(path).HasChanged = true; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(precompiledView.Item, result.Item); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_CanReusePrecompiledViewIfContentChangesToMatch() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var file = fileProvider.AddFile(path, "different content"); var expected1 = new CompiledViewDescriptor(); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); viewCompiler.Compile = _ => expected1; // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(expected1, result); // Act - 2 file.Content = "some content"; fileProvider.GetChangeToken(path).HasChanged = true; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(precompiledView.Item, result.Item); } [Fact] public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompileWhenViewImportChanges() { // Arrange var path = "/Views/Home/Index.cshtml"; var importPath = "/Views/_ViewImports.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var importFile = fileProvider.AddFile(importPath, "some import"); var expected2 = new CompiledViewDescriptor(); var precompiledView = new CompiledViewDescriptor { RelativePath = path, Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] { new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), new RazorSourceChecksumAttribute("SHA1", GetChecksum("some import"), importPath), }), }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); // Act - 2 importFile.Content = "different content"; fileProvider.GetChangeToken(importPath).HasChanged = true; viewCompiler.Compile = _ => expected2; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(expected2, result); } [Fact] public async Task GetOrAdd_AllowsConcurrentCompilationOfMultipleRazorPages() { // Arrange var path1 = "/Views/Home/Index.cshtml"; var path2 = "/Views/Home/About.cshtml"; var waitDuration = TimeSpan.FromSeconds(20); var fileProvider = new TestFileProvider(); fileProvider.AddFile(path1, "some content"); fileProvider.AddFile(path2, "some content"); var resetEvent1 = new AutoResetEvent(initialState: false); var resetEvent2 = new ManualResetEvent(initialState: false); var compilingOne = false; var compilingTwo = false; var result1 = new CompiledViewDescriptor(); var result2 = new CompiledViewDescriptor(); var compiler = GetViewCompiler(fileProvider); compiler.Compile = path => { if (path == path1) { compilingOne = true; // Event 2 Assert.True(resetEvent1.WaitOne(waitDuration)); // Event 3 Assert.True(resetEvent2.Set()); // Event 6 Assert.True(resetEvent1.WaitOne(waitDuration)); Assert.True(compilingTwo); return result1; } else if (path == path2) { compilingTwo = true; // Event 4 Assert.True(resetEvent2.WaitOne(waitDuration)); // Event 5 Assert.True(resetEvent1.Set()); Assert.True(compilingOne); return result2; } else { throw new Exception(); } }; // Act var task1 = Task.Run(() => compiler.CompileAsync(path1)); var task2 = Task.Run(() => compiler.CompileAsync(path2)); // Event 1 resetEvent1.Set(); await Task.WhenAll(task1, task2); // Assert Assert.True(compilingOne); Assert.True(compilingTwo); #pragma warning disable xUnit1031 // Do not use blocking task operations in test method Assert.Same(result1, task1.Result); Assert.Same(result2, task2.Result); #pragma warning restore xUnit1031 // Do not use blocking task operations in test method } [Fact] public async Task CompileAsync_DoesNotCreateMultipleCompilationResults_ForConcurrentInvocations() { // Arrange var path = "/Views/Home/Index.cshtml"; var waitDuration = TimeSpan.FromSeconds(20); var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var resetEvent1 = new ManualResetEvent(initialState: false); var resetEvent2 = new ManualResetEvent(initialState: false); var compiler = GetViewCompiler(fileProvider); compiler.Compile = _ => { // Event 2 resetEvent1.WaitOne(waitDuration); // Event 3 resetEvent2.Set(); return new CompiledViewDescriptor(); }; // Act var task1 = Task.Run(() => compiler.CompileAsync(path)); var task2 = Task.Run(() => { // Event 4 Assert.True(resetEvent2.WaitOne(waitDuration)); return compiler.CompileAsync(path); }); // Event 1 resetEvent1.Set(); await Task.WhenAll(task1, task2); // Assert #pragma warning disable xUnit1031 // Do not use blocking task operations in test method var result1 = task1.Result; var result2 = task2.Result; Assert.Same(result1, result2); #pragma warning restore xUnit1031 // Do not use blocking task operations in test method } [Fact] public async Task GetOrAdd_CachesCompilationExceptions() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(path, "some content"); var exception = new InvalidTimeZoneException(); var compiler = GetViewCompiler(fileProvider); compiler.Compile = _ => throw exception; // Act and Assert - 1 var actual = await Assert.ThrowsAsync<InvalidTimeZoneException>( () => compiler.CompileAsync(path)); Assert.Same(exception, actual); // Act and Assert - 2 compiler.Compile = _ => throw new Exception("Shouldn't be called"); actual = await Assert.ThrowsAsync<InvalidTimeZoneException>( () => compiler.CompileAsync(path)); Assert.Same(exception, actual); } [Fact] public void Compile_SucceedsForCSharp7() { // Arrange var content = @" public class MyTestType { private string _name; public string Name { get => _name; set => _name = value ?? throw new System.ArgumentNullException(nameof(value)); } }"; var compiler = GetViewCompiler(new TestFileProvider()); var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("razor-content", "filename")); // Act var result = compiler.CompileAndEmit(codeDocument, content); // Assert var exportedType = Assert.Single(result.ExportedTypes); Assert.Equal("MyTestType", exportedType.Name); } [Fact] public void Compile_ReturnsCompilationFailureWithPathsFromLinePragmas() { // Arrange var viewPath = "some-relative-path"; var fileContent = "test file content"; var content = $@" #line 1 ""{viewPath}"" this should fail"; var compiler = GetViewCompiler(new TestFileProvider()); var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(fileContent, viewPath)); // Act & Assert var ex = Assert.Throws<CompilationFailedException>(() => compiler.CompileAndEmit(codeDocument, content)); var compilationFailure = Assert.Single(ex.CompilationFailures); Assert.Equal(viewPath, compilationFailure.SourceFilePath); Assert.Equal(fileContent, compilationFailure.SourceFileContent); } [Fact] public void Compile_ReturnsGeneratedCodePath_IfLinePragmaIsNotAvailable() { // Arrange var viewPath = "some-relative-path"; var fileContent = "file content"; var content = "public class Bad { this should fail }"; var compiler = GetViewCompiler(new TestFileProvider()); var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create(fileContent, viewPath)); // Act & Assert var ex = Assert.Throws<CompilationFailedException>(() => compiler.CompileAndEmit(codeDocument, content)); var compilationFailure = Assert.Single(ex.CompilationFailures); Assert.Equal("Generated Code", compilationFailure.SourceFilePath); Assert.Equal(content, compilationFailure.SourceFileContent); } [Fact] public void CompileAndEmit_DoesNotThrowIfDebugTypeIsEmbedded() { // Arrange var referenceManager = CreateReferenceManager(); var csharpCompiler = new TestCSharpCompiler(referenceManager, Mock.Of<IWebHostEnvironment>()) { EmitOptionsSettable = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded), }; var compiler = GetViewCompiler(csharpCompiler: csharpCompiler); var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("Hello world", "some-relative-path.cshtml")); // Act var result = compiler.CompileAndEmit(codeDocument, "public class Test{}"); // Assert Assert.NotNull(result); } [Fact] public void CompileAndEmit_WorksIfEmitPdbIsNotSet() { // Arrange var referenceManager = CreateReferenceManager(); var csharpCompiler = new TestCSharpCompiler(referenceManager, Mock.Of<IWebHostEnvironment>()) { EmitPdbSettable = false, }; var compiler = GetViewCompiler(csharpCompiler: csharpCompiler); var codeDocument = RazorCodeDocument.Create(RazorSourceDocument.Create("Hello world", "some-relative-path.cshtml")); // Act var result = compiler.CompileAndEmit(codeDocument, "public class Test{}"); // Assert Assert.NotNull(result); } private static TestRazorViewCompiler GetViewCompiler( TestFileProvider fileProvider = null, RazorReferenceManager referenceManager = null, IList<CompiledViewDescriptor> precompiledViews = null, CSharpCompiler csharpCompiler = null) { fileProvider = fileProvider ?? new TestFileProvider(); #pragma warning disable ASPDEPR003 // Type or member is obsolete var options = Options.Create(new MvcRazorRuntimeCompilationOptions { FileProviders = { fileProvider } }); #pragma warning restore ASPDEPR003 // Type or member is obsolete var compilationFileProvider = new RuntimeCompilationFileProvider(options); referenceManager = referenceManager ?? CreateReferenceManager(); precompiledViews = precompiledViews ?? Array.Empty<CompiledViewDescriptor>(); var hostingEnvironment = Mock.Of<IWebHostEnvironment>(e => e.ContentRootPath == "BasePath"); var fileSystem = new FileProviderRazorProjectFileSystem(compilationFileProvider, hostingEnvironment); var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder => { RazorExtensions.Register(builder); }); csharpCompiler = csharpCompiler ?? new CSharpCompiler(referenceManager, hostingEnvironment); return new TestRazorViewCompiler( fileProvider, projectEngine, csharpCompiler, precompiledViews); } private static RazorReferenceManager CreateReferenceManager() { var applicationPartManager = new ApplicationPartManager(); var assembly = typeof(RuntimeViewCompilerTest).Assembly; applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly)); #pragma warning disable ASPDEPR003 // Type or member is obsolete return new RazorReferenceManager(applicationPartManager, Options.Create(new MvcRazorRuntimeCompilationOptions())); #pragma warning restore ASPDEPR003 // Type or member is obsolete } private class TestRazorViewCompiler : RuntimeViewCompiler { public TestRazorViewCompiler( TestFileProvider fileProvider, RazorProjectEngine projectEngine, CSharpCompiler csharpCompiler, IList<CompiledViewDescriptor> precompiledViews, Func<string, CompiledViewDescriptor> compile = null) : base(fileProvider, projectEngine, csharpCompiler, precompiledViews, NullLogger.Instance) { Compile = compile; if (Compile == null) { Compile = path => new CompiledViewDescriptor { RelativePath = path, Item = CreateForView(path), }; } } public Func<string, CompiledViewDescriptor> Compile { get; set; } protected override CompiledViewDescriptor CompileAndEmit(string relativePath) { return Compile(relativePath); } } private class TestCSharpCompiler : CSharpCompiler { public TestCSharpCompiler(RazorReferenceManager manager, IWebHostEnvironment hostingEnvironment) : base(manager, hostingEnvironment) { } public EmitOptions EmitOptionsSettable { get; set; } public bool EmitPdbSettable { get; set; } public override EmitOptions EmitOptions => EmitOptionsSettable; public override bool EmitPdb => EmitPdbSettable; } }