public async Task CompileAsync_PerformsCaseInsensitiveLookupsForCompiledViews(string lookupPath) { // Arrange var path = "/Views/Home/Index.cshtml"; var precompiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(compiledViews: new[] { precompiledView }); // Act var result = await viewCompiler.CompileAsync(lookupPath); // Assert Assert.Same(precompiledView, result); }
public async Task CompileAsync_PerformsCaseInsensitiveLookupsForCompiledViews_WithNonNormalizedPaths() { // Arrange var path = "/Views/Home/Index.cshtml"; var compiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(compiledViews: new[] { compiledView }); // Act var result = await viewCompiler.CompileAsync("Views\\Home\\Index.cshtml"); // Assert Assert.Same(compiledView, result); }
public async Task CompileAsync_PerformsCaseInsensitiveLookupsForPrecompiledViews_WithNonNormalizedPaths() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var fileInfo = 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); }
public async Task CompileAsync_ReturnsCompiledViews() { // Arrange var path = "/Views/Home/Index.cshtml"; var compiledView = new CompiledViewDescriptor { RelativePath = path, }; var viewCompiler = GetViewCompiler(compiledViews: new[] { compiledView }); // Act var result = await viewCompiler.CompileAsync(path); // Assert Assert.Same(compiledView, result); // This view doesn't have checksums so it can't be recompiled. Assert.Null(compiledView.ExpirationTokens); }
private IList <IChangeToken> GetExpirationTokens(CompiledViewDescriptor precompiledView) { if (!AllowRecompilingViewsOnFileChange) { return(Array.Empty <IChangeToken>()); } var checksums = precompiledView.Item.GetChecksumMetadata(); var expirationTokens = new List <IChangeToken>(checksums.Count); for (var i = 0; i < checksums.Count; i++) { // We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job, // so it probably will. expirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier)); } return(expirationTokens); }
/// <inheritdoc /> public void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature) { foreach (var assemblyPart in parts.OfType <AssemblyPart>()) { var viewAttributes = GetViewAttributes(assemblyPart); foreach (var attribute in viewAttributes) { var relativePath = ViewPath.NormalizePath(attribute.Path); var viewDescriptor = new CompiledViewDescriptor { ExpirationTokens = Array.Empty <IChangeToken>(), RelativePath = relativePath, ViewAttribute = attribute, IsPrecompiled = true, }; feature.ViewDescriptors.Add(viewDescriptor); } } }
public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompileWhenViewImportChanges() { // Arrange var path = "/Views/Home/Index.cshtml"; var importPath = "/Views/_ViewImports.cshtml"; var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(path, "some content"); var importFileInfo = 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 }); viewCompiler.AllowRecompilingViewsOnFileChange = true; // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); // Act - 2 importFileInfo.Content = "some import changed"; fileProvider.GetChangeToken(importPath).HasChanged = true; viewCompiler.Compile = _ => expected2; result = await viewCompiler.CompileAsync(path); // Assert - 2 Assert.Same(expected2, result); }
public async Task CompileAsync_ReturnsPrecompiledViews() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var fileInfo = 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); }
/// <inheritdoc /> public void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature) { var knownIdentifiers = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var descriptors = new List <CompiledViewDescriptor>(); foreach (var assemblyPart in parts.OfType <AssemblyPart>()) { var attributes = GetViewAttributes(assemblyPart); var items = LoadItems(assemblyPart); var merged = Merge(items, attributes); foreach (var item in merged) { var descriptor = new CompiledViewDescriptor(item.item, item.attribute); // We iterate through ApplicationPart instances appear in precendence order. // If a view path appears in multiple views, we'll use the order to break ties. if (knownIdentifiers.Add(descriptor.RelativePath)) { feature.ViewDescriptors.Add(descriptor); } } } }
/// <inheritdoc /> public void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature) { foreach (var assemblyPart in parts.OfType <AssemblyPart>()) { var viewAttributes = GetViewAttributes(assemblyPart) .Select(attribute => (Attribute: attribute, RelativePath: ViewPath.NormalizePath(attribute.Path))); var duplicates = viewAttributes.GroupBy(a => a.RelativePath, StringComparer.OrdinalIgnoreCase) .FirstOrDefault(g => g.Count() > 1); if (duplicates != null) { // Ensure parts do not specify views with differing cases. This is not supported // at runtime and we should flag at as such for precompiled views. var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath)); var message = string.Join( Environment.NewLine, Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, viewsDiffereningInCase); throw new InvalidOperationException(message); } foreach (var(attribute, relativePath) in viewAttributes) { var viewDescriptor = new CompiledViewDescriptor { ExpirationTokens = Array.Empty <IChangeToken>(), RelativePath = relativePath, ViewAttribute = attribute, IsPrecompiled = true, }; feature.ViewDescriptors.Add(viewDescriptor); } } }
public async Task CompileAsync_PrecompiledViewWithoutChecksumForMainSource_DoesNotSupportRecompilation() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var fileInfo = 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); }
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, "Index content"); fileProvider.AddFile(path2, "About 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); Assert.Same(result1, task1.Result); Assert.Same(result2, task2.Result); }
private ViewCompilerWorkItem CreatePrecompiledWorkItem(string normalizedPath, CompiledViewDescriptor precompiledView) { // We have a precompiled view - but we're not sure that we can use it yet. // // We need to determine first if we have enough information to 'recompile' this view. If that's the case // we'll create change tokens for all of the files. // // Then we'll attempt to validate if any of those files have different content than the original sources // based on checksums. if (precompiledView.Item == null || !ChecksumValidator.IsRecompilationSupported(precompiledView.Item)) { return(new ViewCompilerWorkItem() { // If we don't have a checksum for the primary source file we can't recompile. SupportsCompilation = false, ExpirationTokens = Array.Empty <IChangeToken>(), // Never expire because we can't recompile. Descriptor = precompiledView, // This will be used as-is. }); } var item = new ViewCompilerWorkItem() { SupportsCompilation = true, Descriptor = precompiledView, // This might be used, if the checksums match. // Used to validate and recompile NormalizedPath = normalizedPath, ExpirationTokens = GetExpirationTokens(precompiledView), }; // We also need to create a new descriptor, because the original one doesn't have expiration tokens on // it. These will be used by the view location cache, which is like an L1 cache for views (this class is // the L2 cache). item.Descriptor = new CompiledViewDescriptor() { ExpirationTokens = item.ExpirationTokens, IsPrecompiled = true, Item = precompiledView.Item, RelativePath = precompiledView.RelativePath, ViewAttribute = precompiledView.ViewAttribute, }; return(item); }