Exemple #1
0
        public void CompileCalculatesRootRelativePath(string appPath, string viewPath)
        {
            // Arrange
            var host = new Mock <IMvcRazorHost>();

            host.Setup(h => h.GenerateCode(@"Views\index\home.cshtml", It.IsAny <Stream>()))
            .Returns(GetGeneratorResult())
            .Verifiable();

            var fileInfo = new Mock <IFileInfo>();

            fileInfo.Setup(f => f.PhysicalPath).Returns(viewPath);
            fileInfo.Setup(f => f.CreateReadStream()).Returns(Stream.Null);

            var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");

            var compiler = new Mock <ICompilationService>();

            compiler.Setup(c => c.Compile(relativeFileInfo, It.IsAny <string>()))
            .Returns(CompilationResult.Successful(typeof(RazorCompilationServiceTest)));

            var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());

            // Act
            razorService.Compile(relativeFileInfo);

            // Assert
            host.Verify();
        }
Exemple #2
0
        public void GetOrAdd_RecompilesFile_IfContentAndLengthAreChanged(
            Type resultViewType,
            long fileTimeUTC)
        {
            // Arrange
            var instance     = (View)Activator.CreateInstance(resultViewType);
            var length       = Encoding.UTF8.GetByteCount(instance.Content);
            var fileProvider = new TestFileProvider();
            var cache        = new CompilerCache(new[] { new ViewCollection() }, TestLoadContext, fileProvider);

            var fileInfo = new TestFileInfo
            {
                Length       = length,
                LastModified = DateTime.FromFileTimeUtc(fileTimeUTC),
                Content      = instance.Content
            };

            fileProvider.AddFile(ViewPath, fileInfo);

            // Act
            var result = cache.GetOrAdd(ViewPath,
                                        compile: _ => CompilationResult.Successful(resultViewType));

            // Assert
            Assert.NotSame(CompilerCacheResult.FileNotFound, result);
            var actual = result.CompilationResult;

            Assert.NotNull(actual);
            Assert.Equal(resultViewType, actual.CompiledType);
        }
        public void GetOrAdd_ReturnsRuntimeCompiledAndPrecompiledViews()
        {
            // Arrange
            var fileProvider = new TestFileProvider();

            fileProvider.AddFile(ViewPath, "some content");
            var cache    = new CompilerCache(new[] { new TestViewCollection() }, TestLoadContext, fileProvider);
            var expected = CompilationResult.Successful(typeof(TestView));

            // Act 1
            var result1 = cache.GetOrAdd(ViewPath, _ => expected);

            // Assert 1
            Assert.Same(expected, result1.CompilationResult);

            // Act 2
            var result2 = cache.GetOrAdd(ViewPath, ThrowsIfCalled);

            // Assert 2
            Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
            Assert.Same(typeof(TestView), result2.CompilationResult.CompiledType);

            // Act 3
            var result3 = cache.GetOrAdd(PrecompiledViewsPath, ThrowsIfCalled);

            // Assert 3
            Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
            Assert.Same(typeof(PreCompile), result3.CompilationResult.CompiledType);
        }
Exemple #4
0
        public void GetOrAdd_IgnoresCachedValue_IfGlobalFileWasChangedSinceCacheWasCreated(
            RazorFileInfo viewStartRazorFileInfo, IFileInfo globalFileInfo)
        {
            // Arrange
            var expectedType          = typeof(RuntimeCompileDifferent);
            var lastModified          = DateTime.UtcNow;
            var viewStartLastModified = DateTime.UtcNow;
            var content  = "some content";
            var fileInfo = new TestFileInfo
            {
                Length       = 1020,
                Content      = content,
                LastModified = lastModified,
                PhysicalPath = "Views\\home\\index.cshtml"
            };

            var fileProvider = new TestFileProvider();

            fileProvider.AddFile(fileInfo.PhysicalPath, fileInfo);
            fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, globalFileInfo);
            var viewCollection = new ViewCollection();
            var cache          = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);

            // Act
            var result = cache.GetOrAdd(fileInfo.PhysicalPath,
                                        compile: _ => CompilationResult.Successful(expectedType));

            // Assert
            Assert.NotSame(CompilerCacheResult.FileNotFound, result);
            var actual = result.CompilationResult;

            Assert.NotNull(actual);
            Assert.Equal(expectedType, actual.CompiledType);
        }
Exemple #5
0
        private CompilerCacheResult CreateCacheEntry(
            string normalizedPath,
            Func <RelativeFileInfo, CompilationResult> compile)
        {
            CompilerCacheResult cacheResult;
            var fileInfo = _fileProvider.GetFileInfo(normalizedPath);
            MemoryCacheEntryOptions cacheEntryOptions;
            CompilerCacheResult     cacheResultToCache;

            if (!fileInfo.Exists)
            {
                cacheResultToCache = CompilerCacheResult.FileNotFound;
                cacheResult        = CompilerCacheResult.FileNotFound;

                cacheEntryOptions = new MemoryCacheEntryOptions();
                cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(normalizedPath));
            }
            else
            {
                var relativeFileInfo  = new RelativeFileInfo(fileInfo, normalizedPath);
                var compilationResult = compile(relativeFileInfo).EnsureSuccessful();
                cacheEntryOptions = GetMemoryCacheEntryOptions(normalizedPath);

                // By default the CompilationResult returned by IRoslynCompiler is an instance of
                // UncachedCompilationResult. This type has the generated code as a string property and do not want
                // to cache it. We'll instead cache the unwrapped result.
                cacheResultToCache = new CompilerCacheResult(
                    CompilationResult.Successful(compilationResult.CompiledType));
                cacheResult = new CompilerCacheResult(compilationResult);
            }

            _cache.Set(normalizedPath, cacheResultToCache, cacheEntryOptions);
            return(cacheResult);
        }
Exemple #6
0
        /// <summary>
        /// Initializes a new instance of <see cref="CompilerCache"/> populated with precompiled views
        /// specified by <paramref name="precompiledViews"/>.
        /// </summary>
        /// <param name="fileProvider"><see cref="IFileProvider"/> used to locate Razor views.</param>
        /// <param name="precompiledViews">A mapping of application relative paths of view to the precompiled view
        /// <see cref="Type"/>s.</param>
        public CompilerCache(
            IFileProvider fileProvider,
            IDictionary <string, Type> precompiledViews)
            : this(fileProvider)
        {
            if (precompiledViews == null)
            {
                throw new ArgumentNullException(nameof(precompiledViews));
            }

            foreach (var item in precompiledViews)
            {
                var cacheEntry = new CompilerCacheResult(CompilationResult.Successful(item.Value));
                _cache.Set(GetNormalizedPath(item.Key), cacheEntry);
            }
        }
Exemple #7
0
        public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButViewImportsWasAdedSinceTheCacheWasCreated()
        {
            // Arrange
            var expectedType    = typeof(RuntimeCompileDifferent);
            var fileProvider    = new TestFileProvider();
            var collection      = new ViewCollection();
            var precompiledFile = collection.FileInfos[0];

            precompiledFile.RelativePath = "Views\\home\\index.cshtml";
            var cache    = new CompilerCache(new[] { collection }, TestLoadContext, fileProvider);
            var testFile = new TestFileInfo
            {
                Content      = new PreCompile().Content,
                LastModified = precompiledFile.LastModified,
                PhysicalPath = precompiledFile.RelativePath
            };

            fileProvider.AddFile(precompiledFile.RelativePath, testFile);
            var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath);

            // Act 1
            var result1 = cache.GetOrAdd(testFile.PhysicalPath,
                                         compile: _ => { throw new Exception("should not be called"); });

            // Assert 1
            Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
            var actual1 = result1.CompilationResult;

            Assert.NotNull(actual1);
            Assert.Equal(typeof(PreCompile), actual1.CompiledType);

            // Act 2
            var globalTrigger = fileProvider.GetTrigger("Views\\_ViewImports.cshtml");

            globalTrigger.IsExpired = true;
            var result2 = cache.GetOrAdd(testFile.PhysicalPath,
                                         compile: _ => CompilationResult.Successful(expectedType));

            // Assert 2
            Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
            var actual2 = result2.CompilationResult;

            Assert.NotNull(actual2);
            Assert.Equal(expectedType, actual2.CompiledType);
        }
Exemple #8
0
        /// <inheritdoc />
        public CompilerCacheResult GetOrAdd(
            [NotNull] string relativePath,
            [NotNull] Func <RelativeFileInfo, CompilationResult> compile)
        {
            var normalizedPath = NormalizePath(relativePath);
            CompilerCacheResult cacheResult;

            if (!_cache.TryGetValue(normalizedPath, out cacheResult))
            {
                var fileInfo = _fileProvider.GetFileInfo(relativePath);
                MemoryCacheEntryOptions cacheEntryOptions;
                CompilerCacheResult     cacheResultToCache;
                if (!fileInfo.Exists)
                {
                    cacheResultToCache = CompilerCacheResult.FileNotFound;
                    cacheResult        = CompilerCacheResult.FileNotFound;

                    cacheEntryOptions = new MemoryCacheEntryOptions();
                    cacheEntryOptions.AddExpirationTrigger(_fileProvider.Watch(relativePath));
                }
                else
                {
                    var relativeFileInfo  = new RelativeFileInfo(fileInfo, relativePath);
                    var compilationResult = compile(relativeFileInfo).EnsureSuccessful();
                    cacheEntryOptions = GetMemoryCacheEntryOptions(relativePath);

                    // By default the CompilationResult returned by IRoslynCompiler is an instance of
                    // UncachedCompilationResult. This type has the generated code as a string property and do not want
                    // to cache it. We'll instead cache the unwrapped result.
                    cacheResultToCache = new CompilerCacheResult(
                        CompilationResult.Successful(compilationResult.CompiledType));
                    cacheResult = new CompilerCacheResult(compilationResult);
                }

                _cache.Set(normalizedPath, cacheResultToCache, cacheEntryOptions);
            }

            return(cacheResult);
        }
Exemple #9
0
        internal CompilerCache(
            IEnumerable <RazorFileInfoCollection> razorFileInfoCollections,
            IAssemblyLoadContext loadContext,
            IFileProvider fileProvider)
        {
            _fileProvider = fileProvider;
            _cache        = new MemoryCache(new MemoryCacheOptions {
                CompactOnMemoryPressure = false
            });

            foreach (var viewCollection in razorFileInfoCollections)
            {
                var containingAssembly = viewCollection.LoadAssembly(loadContext);
                foreach (var fileInfo in viewCollection.FileInfos)
                {
                    var viewType       = containingAssembly.GetType(fileInfo.FullTypeName);
                    var cacheEntry     = new CompilerCacheResult(CompilationResult.Successful(viewType));
                    var normalizedPath = NormalizePath(fileInfo.RelativePath);
                    _cache.Set(normalizedPath, cacheEntry);
                }
            }
        }
Exemple #10
0
        public void Compile_ReturnsResultFromCompilationServiceIfParseSucceeds()
        {
            // Arrange
            var code            = "compiled-content";
            var generatorResult = new GeneratorResults(
                new Block(new BlockBuilder {
                Type = BlockType.Comment
            }),
                Enumerable.Empty <TagHelperDescriptor>(),
                new ErrorSink(),
                new CodeGeneratorResult(code, new LineMapping[0]),
                new ChunkTree());
            var host = new Mock <IMvcRazorHost>();

            host.Setup(h => h.GenerateCode(It.IsAny <string>(), It.IsAny <Stream>()))
            .Returns(generatorResult);

            var fileInfo = new Mock <IFileInfo>();

            fileInfo.Setup(f => f.CreateReadStream())
            .Returns(Stream.Null);
            var relativeFileInfo = new RelativeFileInfo(fileInfo.Object, @"Views\index\home.cshtml");

            var compilationResult = CompilationResult.Successful(typeof(object));
            var compiler          = new Mock <ICompilationService>();

            compiler.Setup(c => c.Compile(relativeFileInfo, code))
            .Returns(compilationResult)
            .Verifiable();
            var razorService = new RazorCompilationService(compiler.Object, host.Object, GetOptions());

            // Act
            var result = razorService.Compile(relativeFileInfo);

            // Assert
            Assert.Same(compilationResult, result);
            compiler.Verify();
        }
        private GetOrAddResult GetOrAddCore(string relativePath,
                                            Func <RelativeFileInfo, CompilationResult> compile)
        {
            var normalizedPath = NormalizePath(relativePath);
            var cacheEntry     = _cache.Get <CompilerCacheEntry>(normalizedPath);

            if (cacheEntry == null)
            {
                var fileInfo = _fileProvider.GetFileInfo(relativePath);
                if (!fileInfo.Exists)
                {
                    return(null);
                }

                var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
                return(OnCacheMiss(relativeFileInfo, normalizedPath, compile));
            }
            else if (cacheEntry.IsPreCompiled && !cacheEntry.IsValidatedPreCompiled)
            {
                // For precompiled views, the first time the entry is read, we need to ensure that no changes were made
                // either to the file associated with this entry, or any _ViewImports associated with it between the time
                // the View was precompiled and the time EnsureInitialized was called. For later iterations, we can
                // rely on expiration triggers ensuring the validity of the entry.

                var fileInfo = _fileProvider.GetFileInfo(relativePath);
                if (!fileInfo.Exists)
                {
                    return(null);
                }

                var relativeFileInfo = new RelativeFileInfo(fileInfo, relativePath);
                if (cacheEntry.Length != fileInfo.Length)
                {
                    // Recompile if the file lengths differ
                    return(OnCacheMiss(relativeFileInfo, normalizedPath, compile));
                }

                if (AssociatedGlobalFilesChanged(cacheEntry, compile))
                {
                    // Recompile if _ViewImports have changed since the entry was created.
                    return(OnCacheMiss(relativeFileInfo, normalizedPath, compile));
                }

                if (cacheEntry.LastModified == fileInfo.LastModified)
                {
                    // Assigning to IsValidatedPreCompiled is an atomic operation and will result in a safe race
                    // if it is being concurrently updated and read.
                    cacheEntry.IsValidatedPreCompiled = true;
                    return(new GetOrAddResult
                    {
                        CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
                        CompilerCacheEntry = cacheEntry
                    });
                }

                // Timestamp doesn't match but it might be because of deployment, compare the hash.
                if (cacheEntry.IsPreCompiled &&
                    string.Equals(cacheEntry.Hash,
                                  RazorFileHash.GetHash(fileInfo, cacheEntry.HashAlgorithmVersion),
                                  StringComparison.Ordinal))
                {
                    // Cache hit, but we need to update the entry.
                    // Assigning to LastModified and IsValidatedPreCompiled are atomic operations and will result in safe race
                    // if the entry is being concurrently read or updated.
                    cacheEntry.LastModified           = fileInfo.LastModified;
                    cacheEntry.IsValidatedPreCompiled = true;
                    return(new GetOrAddResult
                    {
                        CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
                        CompilerCacheEntry = cacheEntry
                    });
                }

                // it's not a match, recompile
                return(OnCacheMiss(relativeFileInfo, normalizedPath, compile));
            }

            return(new GetOrAddResult
            {
                CompilationResult = CompilationResult.Successful(cacheEntry.CompiledType),
                CompilerCacheEntry = cacheEntry
            });
        }
Exemple #12
0
        public void GetOrAdd_IgnoresCachedValueIfFileIsIdentical_ButGlobalWasDeletedSinceCacheWasCreated()
        {
            // Arrange
            var expectedType = typeof(RuntimeCompileDifferent);
            var lastModified = DateTime.UtcNow;
            var fileProvider = new TestFileProvider();

            var viewCollection  = new ViewCollection();
            var precompiledView = viewCollection.FileInfos[0];

            precompiledView.RelativePath = "Views\\Index.cshtml";
            var viewFileInfo = new TestFileInfo
            {
                Content      = new PreCompile().Content,
                LastModified = precompiledView.LastModified,
                PhysicalPath = precompiledView.RelativePath
            };

            fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo);

            var globalFileInfo = new TestFileInfo
            {
                PhysicalPath = "Views\\_ViewImports.cshtml",
                Content      = "viewstart-content",
                LastModified = lastModified
            };
            var globalFile = new RazorFileInfo
            {
                FullTypeName         = typeof(RuntimeCompileIdentical).FullName,
                RelativePath         = globalFileInfo.PhysicalPath,
                LastModified         = globalFileInfo.LastModified,
                Hash                 = RazorFileHash.GetHash(globalFileInfo, hashAlgorithmVersion: 1),
                HashAlgorithmVersion = 1,
                Length               = globalFileInfo.Length
            };

            fileProvider.AddFile(globalFileInfo.PhysicalPath, globalFileInfo);

            viewCollection.Add(globalFile);
            var cache = new CompilerCache(new[] { viewCollection }, TestLoadContext, fileProvider);

            // Act 1
            var result1 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
                                         compile: _ => { throw new Exception("should not be called"); });

            // Assert 1
            Assert.NotSame(CompilerCacheResult.FileNotFound, result1);
            var actual1 = result1.CompilationResult;

            Assert.NotNull(actual1);
            Assert.Equal(typeof(PreCompile), actual1.CompiledType);

            // Act 2
            var trigger = fileProvider.GetTrigger(globalFileInfo.PhysicalPath);

            trigger.IsExpired = true;
            var result2 = cache.GetOrAdd(viewFileInfo.PhysicalPath,
                                         compile: _ => CompilationResult.Successful(expectedType));

            // Assert 2
            Assert.NotSame(CompilerCacheResult.FileNotFound, result2);
            var actual2 = result2.CompilationResult;

            Assert.NotNull(actual2);
            Assert.Equal(expectedType, actual2.CompiledType);
        }
        public CompilationResult Compile(string content)
        {
            var syntaxTrees     = new[] { CSharpSyntaxTree.ParseText(content) };
            var targetFramework = _environment.TargetFramework;

            var references = GetApplicationReferences();

            var assemblyName = Path.GetRandomFileName();

            var compilation = CSharpCompilation.Create(assemblyName,
                                                       options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary),
                                                       syntaxTrees: syntaxTrees,
                                                       references: references);

            using (var ms = new MemoryStream())
            {
                using (var pdb = new MemoryStream())
                {
                    EmitResult result;

                    if (PlatformHelper.IsMono)
                    {
                        result = compilation.Emit(ms, pdbStream: null);
                    }
                    else
                    {
                        result = compilation.Emit(ms, pdbStream: pdb);
                    }

                    if (!result.Success)
                    {
                        var formatter = new DiagnosticFormatter();

                        var messages = result.Diagnostics
                                       .Where(IsError)
                                       .Select(d => GetCompilationMessage(formatter, d))
                                       .ToList();

                        return(CompilationResult.Failed(content, messages));
                    }

                    Assembly assembly;
                    ms.Seek(0, SeekOrigin.Begin);

                    if (PlatformHelper.IsMono)
                    {
                        assembly = _loader.LoadStream(ms, pdbStream: null);
                    }
                    else
                    {
                        pdb.Seek(0, SeekOrigin.Begin);
                        assembly = _loader.LoadStream(ms, pdb);
                    }

                    var type = assembly.GetExportedTypes()
                               .First();

                    return(CompilationResult.Successful(string.Empty, type));
                }
            }
        }