Esempio n. 1
0
        public async Task CompileAsync_PrecompiledViewWithoutAnyChecksum_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[] { }),
            };

            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);
        }
Esempio n. 2
0
        public async Task CompileAsync_PrecompiledViewWithChecksum_UsesPrecompiledViewWhenChecksumIsMatch()
        {
            // 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"), 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));
        }
    public void CreateFactory_ProducesDelegateThatSetsPagePath()
    {
        // Arrange
        var relativePath = "/file-exists";
        var descriptor   = new CompiledViewDescriptor
        {
            RelativePath     = relativePath,
            Item             = TestRazorCompiledItem.CreateForView(typeof(TestRazorPage), relativePath),
            ExpirationTokens = Array.Empty <IChangeToken>(),
        };
        var viewCompiler = new Mock <IViewCompiler>();

        viewCompiler
        .Setup(f => f.CompileAsync(It.IsAny <string>()))
        .ReturnsAsync(descriptor);

        var factoryProvider = new DefaultRazorPageFactoryProvider(GetCompilerProvider(viewCompiler.Object));

        // Act
        var result = factoryProvider.CreateFactory(relativePath);

        // Assert
        Assert.True(result.Success);
        var actual = result.RazorPageFactory();

        Assert.Equal("/file-exists", actual.Path);
    }
    public void CreateFactory_ReturnsViewDescriptor_ForSuccessfulResults()
    {
        // Arrange
        var relativePath     = "/file-exists";
        var expirationTokens = new[]
        {
            Mock.Of <IChangeToken>(),
            Mock.Of <IChangeToken>(),
        };
        var descriptor = new CompiledViewDescriptor
        {
            RelativePath     = relativePath,
            Item             = TestRazorCompiledItem.CreateForView(typeof(TestRazorPage), relativePath),
            ExpirationTokens = expirationTokens,
        };
        var compilerCache = new Mock <IViewCompiler>();

        compilerCache
        .Setup(f => f.CompileAsync(It.IsAny <string>()))
        .ReturnsAsync(descriptor);

        var factoryProvider = new DefaultRazorPageFactoryProvider(GetCompilerProvider(compilerCache.Object));

        // Act
        var result = factoryProvider.CreateFactory(relativePath);

        // Assert
        Assert.True(result.Success);
        Assert.Equal(expirationTokens, descriptor.ExpirationTokens);
    }
    public void CreateFactory_ReturnsViewDescriptor_ForUnsuccessfulResults()
    {
        // Arrange
        var path             = "/file-does-not-exist";
        var expirationTokens = new[]
        {
            Mock.Of <IChangeToken>(),
            Mock.Of <IChangeToken>(),
        };
        var descriptor = new CompiledViewDescriptor
        {
            RelativePath     = path,
            ExpirationTokens = expirationTokens,
        };
        var compilerCache = new Mock <IViewCompiler>();

        compilerCache
        .Setup(f => f.CompileAsync(It.IsAny <string>()))
        .ReturnsAsync(descriptor);

        var factoryProvider = new DefaultRazorPageFactoryProvider(GetCompilerProvider(compilerCache.Object));

        // Act
        var result = factoryProvider.CreateFactory(path);

        // Assert
        Assert.False(result.Success);
        Assert.Same(descriptor, result.ViewDescriptor);
    }
Esempio n. 6
0
        public virtual void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature)
        {
            if (ActiveWidgetTemplates == null)
            {
                ActiveWidgetTemplates = new HashSet <string>();
                foreach (var item in feature.ViewDescriptors)
                {
                    string name = Path.GetFileName(item.RelativePath);
                    if (name.StartsWith("Widget.") && !ActiveWidgetTemplates.Contains(name))
                    {
                        ActiveWidgetTemplates.Add(name);
                    }
                }
            }
            var knownIdentifiers = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
            var attributes       = GetViewAttributesLegacy(Assembly);

            foreach (var item in attributes)
            {
                var descriptor = new CompiledViewDescriptor(item);
                if (knownIdentifiers.Add(descriptor.RelativePath))
                {
                    string name = Path.GetFileName(descriptor.RelativePath);
                    if (name.StartsWith("Widget.") && !ActiveWidgetTemplates.Contains(name))
                    {
                        ActiveWidgetTemplates.Add(name);
                    }

                    feature.ViewDescriptors.Add(descriptor);
                }
            }
        }
Esempio n. 7
0
        // Token: 0x0600017E RID: 382 RVA: 0x00006EDC File Offset: 0x000050DC
        private Task <CompiledViewDescriptor> OnCacheMiss(string normalizedPath)
        {
            object cacheLock = this._cacheLock;
            ViewCompilerWorkItem    viewCompilerWorkItem;
            MemoryCacheEntryOptions memoryCacheEntryOptions;
            TaskCompletionSource <CompiledViewDescriptor> taskCompletionSource;

            lock (cacheLock)
            {
                Task <CompiledViewDescriptor> result;
                if (CacheExtensions.TryGetValue <Task <CompiledViewDescriptor> >(this._cache, normalizedPath, out result))
                {
                    return(result);
                }
                CompiledViewDescriptor precompiledView;
                if (this._precompiledViews.TryGetValue(normalizedPath, out precompiledView))
                {
                    this._logger.ViewCompilerLocatedCompiledViewForPath(normalizedPath);
                    viewCompilerWorkItem = this.CreatePrecompiledWorkItem(normalizedPath, precompiledView);
                }
                else
                {
                    viewCompilerWorkItem = this.CreateRuntimeCompilationWorkItem(normalizedPath);
                }
                memoryCacheEntryOptions = new MemoryCacheEntryOptions();
                for (int i = 0; i < viewCompilerWorkItem.ExpirationTokens.Count; i++)
                {
                    memoryCacheEntryOptions.ExpirationTokens.Add(viewCompilerWorkItem.ExpirationTokens[i]);
                }
                taskCompletionSource = new TaskCompletionSource <CompiledViewDescriptor>();
                if (!viewCompilerWorkItem.SupportsCompilation)
                {
                    taskCompletionSource.SetResult(viewCompilerWorkItem.Descriptor);
                }
                _cacheKeyList.Add(normalizedPath);
                CacheExtensions.Set <Task <CompiledViewDescriptor> >(this._cache, normalizedPath, taskCompletionSource.Task, memoryCacheEntryOptions);
            }
            if (viewCompilerWorkItem.SupportsCompilation)
            {
                CompiledViewDescriptor descriptor = viewCompilerWorkItem.Descriptor;
                if (((descriptor != null) ? descriptor.Item : null) != null && ChecksumValidator.IsItemValid(this._projectEngine.FileSystem, viewCompilerWorkItem.Descriptor.Item))
                {
                    taskCompletionSource.SetResult(viewCompilerWorkItem.Descriptor);
                    return(taskCompletionSource.Task);
                }
                this._logger.ViewCompilerInvalidingCompiledFile(viewCompilerWorkItem.NormalizedPath);
                try
                {
                    CompiledViewDescriptor compiledViewDescriptor = this.CompileAndEmit(normalizedPath);
                    compiledViewDescriptor.ExpirationTokens = memoryCacheEntryOptions.ExpirationTokens;
                    taskCompletionSource.SetResult(compiledViewDescriptor);
                }
                catch (Exception exception)
                {
                    taskCompletionSource.SetException(exception);
                }
            }
            return(taskCompletionSource.Task);
        }
 public void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature)
 {
     foreach (var item in this.compiledItems)
     {
         var descriptor = new CompiledViewDescriptor(item);
         feature.ViewDescriptors.Add(descriptor);
     }
 }
        static bool IsRazorPage(CompiledViewDescriptor viewDescriptor)
        {
            if (viewDescriptor.Item != null)
            {
                return(viewDescriptor.Item.Kind == RazorPageDocumentKind);
            }

            return(false);
        }
    public void GetRouteTemplate_ReturnsNull_IfAttributeDoesNotExist()
    {
        // Arrange
        var descriptor = new CompiledViewDescriptor(TestRazorCompiledItem.CreateForPage("/Pages/About.cshtml"));

        // Act
        var result = CompiledPageRouteModelProvider.GetRouteTemplate(descriptor);

        // Assert
        Assert.Null(result);
    }
Esempio n. 11
0
    internal static string?GetRouteTemplate(CompiledViewDescriptor viewDescriptor)
    {
        if (viewDescriptor.Item != null)
        {
            return(viewDescriptor.Item.Metadata
                   .OfType <RazorCompiledItemMetadataAttribute>()
                   .FirstOrDefault(f => f.Key == RouteTemplateKey)
                   ?.Value);
        }

        return(null);
    }
        private HashSet <string> GetAvailableTemplates()
        {
            HashSet <string> templates = new HashSet <string>();
            var attributes             = new RazorCompiledItemLoader().LoadItems(this.GetType().Assembly);

            foreach (var item in attributes)
            {
                var    descriptor = new CompiledViewDescriptor(item);
                string name       = Path.GetFileName(descriptor.RelativePath);
                templates.Add(name);
            }
            return(templates);
        }
Esempio n. 13
0
        private Task <CompiledViewDescriptor> OnCacheMiss(string normalizedPath)
        {
            TaskCompletionSource <CompiledViewDescriptor> taskSource;

            lock (_cacheLock)
            {
                // Double-checked locking to handle a possible race.
                if (_cache.TryGetValue(normalizedPath, out Task <CompiledViewDescriptor> result))
                {
                    return(result);
                }
                var razorParts = _partManager.ApplicationParts.OfType <CompiledRazorModulesAssemblyPart>().ToList();
                foreach (var razorPart in razorParts)
                {
                    var provider           = razorPart as IRazorCompiledItemProvider;
                    var razorCompiledItems = provider.CompiledItems;
                    var item = razorCompiledItems.FirstOrDefault(item => normalizedPath.Equals(GetNormalizedPath(item.Identifier)));
                    if (item != null)
                    {
                        taskSource = new TaskCompletionSource <CompiledViewDescriptor>(creationOptions: TaskCreationOptions.RunContinuationsAsynchronously);

                        var descriptor = new CompiledViewDescriptor(item)
                        {
                            ExpirationTokens = new List <IChangeToken>()
                            {
                                _moduleChangeProvider.GetChangeToken(razorPart.EntryAssemblyPath)
                            }
                        };

                        // At this point, we've decided what to do - but we should create the cache entry and
                        // release the lock first.
                        var cacheEntryOptions = new MemoryCacheEntryOptions();

                        for (var i = 0; i < descriptor.ExpirationTokens.Count; i++)
                        {
                            cacheEntryOptions.ExpirationTokens.Add(descriptor.ExpirationTokens[i]);
                        }
                        var task = _cache.Set(descriptor.RelativePath, Task.FromResult(descriptor), cacheEntryOptions);
                        return(task);
                    }
                }
            }

            // Entry does not exist. Attempt to create one.
            _logger.ViewCompilerCouldNotFindFileAtPath(normalizedPath);
            return(Task.FromResult(new CompiledViewDescriptor
            {
                RelativePath = normalizedPath,
                ExpirationTokens = Array.Empty <IChangeToken>(),
            }));
        }
Esempio n. 14
0
        public virtual void PopulateFeature(IEnumerable <ApplicationPart> parts, ViewsFeature feature)
        {
            var knownIdentifiers = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
            var attributes       = GetViewAttributesLegacy(Assembly);

            foreach (var item in attributes)
            {
                var descriptor = new CompiledViewDescriptor(item);
                if (knownIdentifiers.Add(descriptor.RelativePath))
                {
                    feature.ViewDescriptors.Add(descriptor);
                }
            }
        }
Esempio n. 15
0
    private IList <IChangeToken> GetExpirationTokens(CompiledViewDescriptor precompiledView)
    {
        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);
    }
Esempio n. 16
0
        private static IViewCompilerProvider GetCompilerProvider()
        {
            var compiledItem = TestRazorCompiledItem.CreateForView(typeof(object), "/Views/Index.cshtml");
            var descriptor   = new CompiledViewDescriptor(compiledItem);
            var compiler     = new Mock <IViewCompiler>();

            compiler.Setup(c => c.CompileAsync(It.IsAny <string>()))
            .ReturnsAsync(descriptor);
            var compilerProvider = new Mock <IViewCompilerProvider>();

            compilerProvider.Setup(p => p.GetCompiler())
            .Returns(compiler.Object);
            return(compilerProvider.Object);
        }
        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 void GetRouteTemplate_ReturnsPathFromMetadataAttribute()
    {
        // Arrange
        var expected   = "test";
        var descriptor = new CompiledViewDescriptor(TestRazorCompiledItem.CreateForPage("/Pages/About.cshtml", metadata: new object[]
        {
            new RazorCompiledItemMetadataAttribute("RouteTemplate", expected),
        }));

        // Act
        var result = CompiledPageRouteModelProvider.GetRouteTemplate(descriptor);

        // Assert
        Assert.Equal(expected, result);
    }
Esempio n. 20
0
        protected override CompiledViewDescriptor CompileAndEmit(string relativePath)
        {
            CompiledViewDescriptor descriptor = base.CompileAndEmit(relativePath);

            // The Razor compiler adds attributes to the generated IRazorPage code that provide the relative path of the page
            // but since Statiq uses "invisible" input path(s) that appear in the physical file system but not the virtual one,
            // we have to remove the input path from the start of the relative path - otherwise we'll end up looking for nested
            // views in locations like "/input/input/_foo.cshtml"
            if (descriptor.RelativePath.EndsWith(relativePath))
            {
                descriptor.RelativePath = relativePath;
            }

            return(descriptor);
        }
        private PageRouteModel GetPageRouteModel(string rootDirectory, CompiledViewDescriptor viewDescriptor)
        {
            var viewEnginePath = GetRootTrimmedPath(rootDirectory, viewDescriptor.RelativePath);

            if (viewEnginePath.EndsWith(RazorViewEngine.ViewExtension, StringComparison.OrdinalIgnoreCase))
            {
                viewEnginePath = viewEnginePath.Substring(0, viewEnginePath.Length - RazorViewEngine.ViewExtension.Length);
            }

            var model         = new PageRouteModel(viewDescriptor.RelativePath, viewEnginePath);
            var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;

            PageSelectorModel.PopulateDefaults(model, viewEnginePath, pageAttribute.RouteTemplate);
            return(model);
        }
Esempio n. 22
0
        internal static string GetRouteTemplate(CompiledViewDescriptor viewDescriptor)
        {
            if (viewDescriptor.ViewAttribute != null)
            {
                return(((RazorPageAttribute)viewDescriptor.ViewAttribute).RouteTemplate);
            }

            if (viewDescriptor.Item != null)
            {
                return(viewDescriptor.Item.Metadata
                       .OfType <RazorCompiledItemMetadataAttribute>()
                       .FirstOrDefault(f => f.Key == RazorPageDocumentClassifierPass.RouteTemplateKey)
                       ?.Value);
            }

            return(null);
        }
Esempio n. 23
0
        private static IViewCompilerProvider GetCompilerProvider()
        {
            var descriptor = new CompiledViewDescriptor
            {
                ViewAttribute = new RazorPageAttribute("/Views/Index.cshtml", typeof(object), null),
            };

            var compiler = new Mock <IViewCompiler>();

            compiler.Setup(c => c.CompileAsync(It.IsAny <string>()))
            .ReturnsAsync(descriptor);
            var compilerProvider = new Mock <IViewCompilerProvider>();

            compilerProvider.Setup(p => p.GetCompiler())
            .Returns(compiler.Object);
            return(compilerProvider.Object);
        }
Esempio n. 24
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,
            Item             = precompiledView.Item,
            RelativePath     = precompiledView.RelativePath,
        };

        return(item);
    }
Esempio n. 25
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);
        }
        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);
        }
        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 PageRouteModel GetAreaPageRouteModel(string areaRootDirectory, CompiledViewDescriptor viewDescriptor)
        {
            var rootTrimmedPath = GetRootTrimmedPath(areaRootDirectory, viewDescriptor.RelativePath);

            if (PageSelectorModel.TryParseAreaPath(_pagesOptions, rootTrimmedPath, _logger, out var result))
            {
                var model = new PageRouteModel(viewDescriptor.RelativePath, result.viewEnginePath)
                {
                    RouteValues = { ["area"] = result.areaName },
                };
                var pageAttribute = (RazorPageAttribute)viewDescriptor.ViewAttribute;
                PageSelectorModel.PopulateDefaults(model, result.pageRoute, pageAttribute.RouteTemplate);

                return(model);
            }

            // We were unable to parse the path to match the format we expect /Areas/AreaName/Pages/PagePath.cshtml
            return(null);
        }
Esempio n. 29
0
        public async Task CompileAsync_DoesNotRecompile_IfFileTriggerWasSetForPrecompiledView()
        {
            // 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
            fileProvider.Watch(path);
            fileProvider.GetChangeToken(path).HasChanged = true;
            var result = await viewCompiler.CompileAsync(path);

            // Assert
            Assert.Same(precompiledView, result);
        }
Esempio n. 30
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);
        }