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);
        }
示例#3
0
        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);
        }
示例#5
0
        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);
        }
示例#6
0
        /// <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);
                }
            }
        }
示例#7
0
        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);
        }
示例#8
0
        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);
        }
示例#9
0
        /// <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);
                }
            }
        }
示例#11
0
        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);
        }
示例#12
0
        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);
        }
示例#13
0
        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);
        }