Beispiel #1
0
            public ViewCollection()
            {
                FileInfos = _fileInfos;

                var content = new PreCompile().Content;
                var length  = Encoding.UTF8.GetByteCount(content);

                Add(new RazorFileInfo()
                {
                    FullTypeName = typeof(PreCompile).FullName,
                    Hash         = RazorFileHash.GetHash(GetMemoryStream(content)),
                    LastModified = DateTime.FromFileTimeUtc(10000),
                    Length       = length,
                    RelativePath = "ab",
                });
            }
Beispiel #2
0
        public void GetOrAdd_UsesValueFromCache_IfViewStartHasNotChanged()
        {
            // Arrange
            var instance   = (View)Activator.CreateInstance(typeof(PreCompile));
            var length     = Encoding.UTF8.GetByteCount(instance.Content);
            var fileSystem = new TestFileSystem();

            var lastModified = DateTime.UtcNow;

            var fileInfo = new TestFileInfo
            {
                Length       = length,
                LastModified = lastModified,
                Content      = instance.Content
            };
            var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");

            var viewStartContent  = "viewstart-content";
            var viewStartFileInfo = new TestFileInfo
            {
                Content      = viewStartContent,
                LastModified = DateTime.UtcNow
            };

            fileSystem.AddFile("_ViewStart.cshtml", viewStartFileInfo);
            var viewStartRazorFileInfo = new RazorFileInfo
            {
                Hash         = RazorFileHash.GetHash(GetMemoryStream(viewStartContent)),
                LastModified = viewStartFileInfo.LastModified,
                Length       = viewStartFileInfo.Length,
                RelativePath = "_ViewStart.cshtml",
                FullTypeName = typeof(RuntimeCompileIdentical).FullName
            };

            var precompiledViews = new ViewCollection();

            precompiledViews.Add(viewStartRazorFileInfo);
            var cache = new CompilerCache(new[] { precompiledViews }, fileSystem);

            // Act
            var actual = cache.GetOrAdd(runtimeFileInfo,
                                        compile: _ => { throw new Exception("shouldn't be invoked"); });

            // Assert
            Assert.Equal(typeof(PreCompile), actual.CompiledType);
        }
Beispiel #3
0
        protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo)
        {
            using (var stream = fileInfo.FileInfo.CreateReadStream())
            {
                var host    = GetRazorHost();
                var results = host.GenerateCode(fileInfo.RelativePath, stream);

                if (results.Success)
                {
                    var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode,
                                                                  fileInfo.FileInfo.PhysicalPath,
                                                                  CompilationSettings);
                    var fullTypeName = results.GetMainClassName(host, syntaxTree);

                    if (fullTypeName != null)
                    {
                        var hashAlgorithmVersion = RazorFileHash.HashAlgorithmVersion1;
                        var hash          = RazorFileHash.GetHash(fileInfo.FileInfo, hashAlgorithmVersion);
                        var razorFileInfo = new RazorFileInfo
                        {
                            RelativePath         = fileInfo.RelativePath,
                            LastModified         = fileInfo.FileInfo.LastModified,
                            Length               = fileInfo.FileInfo.Length,
                            FullTypeName         = fullTypeName,
                            Hash                 = hash,
                            HashAlgorithmVersion = hashAlgorithmVersion
                        };

                        return(new PrecompilationCacheEntry(razorFileInfo, syntaxTree));
                    }
                }
                else
                {
                    var diagnostics = results.ParserErrors
                                      .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
                                      .ToList();

                    return(new PrecompilationCacheEntry(diagnostics));
                }
            }

            return(null);
        }
Beispiel #4
0
        /// <inheritdoc />
        public CompilationResult GetOrAdd([NotNull] RelativeFileInfo fileInfo,
                                          bool enableInstrumentation,
                                          [NotNull] Func <CompilationResult> compile)
        {
            CompilerCacheEntry cacheEntry;

            if (!_cache.TryGetValue(NormalizePath(fileInfo.RelativePath), out cacheEntry))
            {
                return(OnCacheMiss(fileInfo, enableInstrumentation, compile));
            }
            else
            {
                if ((cacheEntry.Length != fileInfo.FileInfo.Length) ||
                    (enableInstrumentation && !cacheEntry.IsInstrumented))
                {
                    // Recompile if
                    // (a) If the file lengths differ
                    // (b) If the compiled type is not instrumented but we require it to be instrumented.
                    return(OnCacheMiss(fileInfo, enableInstrumentation, compile));
                }

                if (cacheEntry.LastModified == fileInfo.FileInfo.LastModified)
                {
                    // Match, not update needed
                    return(CompilationResult.Successful(cacheEntry.CompiledType));
                }

                var hash = RazorFileHash.GetHash(fileInfo.FileInfo);

                // Timestamp doesn't match but it might be because of deployment, compare the hash.
                if (cacheEntry.IsPreCompiled &&
                    string.Equals(cacheEntry.Hash, hash, StringComparison.Ordinal))
                {
                    // Cache hit, but we need to update the entry
                    return(OnCacheMiss(fileInfo,
                                       enableInstrumentation,
                                       () => CompilationResult.Successful(cacheEntry.CompiledType)));
                }

                // it's not a match, recompile
                return(OnCacheMiss(fileInfo, enableInstrumentation, compile));
            }
        }
Beispiel #5
0
        protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
                                                  [NotNull] IBeforeCompileContext context,
                                                  [NotNull] CSharpParseOptions options)
        {
            using (var stream = fileInfo.FileInfo.CreateReadStream())
            {
                var results = _host.GenerateCode(fileInfo.RelativePath, stream);

                foreach (var parserError in results.ParserErrors)
                {
                    var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath);
                    context.Diagnostics.Add(diagnostic);
                }

                var generatedCode = results.GeneratedCode;

                if (generatedCode != null)
                {
                    var syntaxTree   = SyntaxTreeGenerator.Generate(generatedCode, fileInfo.FileInfo.PhysicalPath, options);
                    var fullTypeName = results.GetMainClassName(_host, syntaxTree);

                    if (fullTypeName != null)
                    {
                        context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree);

                        var hash = RazorFileHash.GetHash(fileInfo.FileInfo);

                        return(new RazorFileInfo()
                        {
                            FullTypeName = fullTypeName,
                            RelativePath = fileInfo.RelativePath,
                            LastModified = fileInfo.FileInfo.LastModified,
                            Length = fileInfo.FileInfo.Length,
                            Hash = hash,
                        });
                    }
                }
            }

            return(null);
        }
Beispiel #6
0
        public void GetOrAdd_IgnoresCachedValue_IfViewStartWasChangedSinceCacheWasCreated(
            RazorFileInfo viewStartRazorFileInfo, TestFileInfo viewStartFileInfo)
        {
            // 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 runtimeFileInfo = new RelativeFileInfo(fileInfo, fileInfo.PhysicalPath);

            var razorFileInfo = new RazorFileInfo
            {
                FullTypeName = typeof(PreCompile).FullName,
                Hash         = RazorFileHash.GetHash(fileInfo),
                LastModified = lastModified,
                Length       = Encoding.UTF8.GetByteCount(content),
                RelativePath = fileInfo.PhysicalPath,
            };

            var fileSystem = new TestFileSystem();

            fileSystem.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo);
            var viewCollection = new ViewCollection();
            var cache          = new CompilerCache(new[] { viewCollection }, fileSystem);

            // Act
            var actual = cache.GetOrAdd(runtimeFileInfo,
                                        compile: _ => CompilationResult.Successful(expectedType));

            // Assert
            Assert.Equal(expectedType, actual.CompiledType);
        }
Beispiel #7
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 collection = new ViewCollection();
            var fileSystem = new TestFileSystem();
            var cache      = new CompilerCache(new[] { new ViewCollection() }, fileSystem);

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

            var runtimeFileInfo = new RelativeFileInfo(fileInfo, "ab");

            var precompiledContent = new PreCompile().Content;
            var razorFileInfo      = new RazorFileInfo
            {
                FullTypeName = typeof(PreCompile).FullName,
                Hash         = RazorFileHash.GetHash(GetMemoryStream(precompiledContent)),
                LastModified = DateTime.FromFileTimeUtc(10000),
                Length       = Encoding.UTF8.GetByteCount(precompiledContent),
                RelativePath = "ab",
            };

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

            // Assert
            Assert.Equal(resultViewType, actual.CompiledType);
        }
Beispiel #8
0
        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 _GlobalImport 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 _GlobalImports 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
            });
        }
Beispiel #9
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\\_GlobalImport.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);
        }