From 4ceeeffb9ecd3d564b617063a7fc118643af7843 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 29 Dec 2015 09:08:04 -0800 Subject: [PATCH] Make RazorViewEngineOptions.FileProvider a list instead of a single item Fixes #3806 --- samples/EmbeddedViewSample.Web/Startup.cs | 6 +- .../DefaultCompilerCacheProvider.cs | 2 +- .../PrecompiledViewsCompilerCacheProvider.cs | 2 +- .../Compilation/RazorCompilationService.cs | 9 ++- .../Compilation/RoslynCompilationService.cs | 2 +- .../MvcRazorMvcCoreBuilderExtensions.cs | 5 +- .../RazorViewEngineOptions.cs | 57 ++++++++++------ .../RazorViewEngineOptionsSetup.cs | 2 +- src/Microsoft.AspNet.Mvc.Razor/project.json | 4 +- .../RazorCompilationServiceTest.cs | 6 +- .../RoslynCompilationServiceTest.cs | 17 +++-- .../RazorViewEngineOptionsTest.cs | 68 ++++++++++++++++--- .../RazorViewEngineOptionsSetupTest.cs | 12 ++-- 13 files changed, 127 insertions(+), 65 deletions(-) diff --git a/samples/EmbeddedViewSample.Web/Startup.cs b/samples/EmbeddedViewSample.Web/Startup.cs index c0c065e7de..4d24da892b 100644 --- a/samples/EmbeddedViewSample.Web/Startup.cs +++ b/samples/EmbeddedViewSample.Web/Startup.cs @@ -20,10 +20,12 @@ public void ConfigureServices(IServiceCollection services) services.Configure(options => { + options.FileProviders.Clear(); + // Base namespace matches the resources added to the assembly from the EmbeddedResources folder. - options.FileProvider = new EmbeddedFileProvider( + options.FileProviders.Add(new EmbeddedFileProvider( GetType().GetTypeInfo().Assembly, - baseNamespace: "EmbeddedViewSample.Web.EmbeddedResources"); + baseNamespace: "EmbeddedViewSample.Web.EmbeddedResources")); }); } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultCompilerCacheProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultCompilerCacheProvider.cs index c98070ce75..10383b4983 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultCompilerCacheProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultCompilerCacheProvider.cs @@ -16,7 +16,7 @@ public class DefaultCompilerCacheProvider : ICompilerCacheProvider /// An accessor to the . public DefaultCompilerCacheProvider(IOptions mvcViewOptions) { - var fileProvider = mvcViewOptions.Value.FileProvider; + var fileProvider = mvcViewOptions.Value.GetCompositeFileProvider(); Cache = new CompilerCache(fileProvider); } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/PrecompiledViewsCompilerCacheProvider.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/PrecompiledViewsCompilerCacheProvider.cs index 2db34f9a26..db00d41cf1 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/PrecompiledViewsCompilerCacheProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/PrecompiledViewsCompilerCacheProvider.cs @@ -42,7 +42,7 @@ public PrecompiledViewsCompilerCacheProvider( IEnumerable assemblies) { _loadContextAccessor = loadContextAccessor; - _fileProvider = mvcViewOptions.Value.FileProvider; + _fileProvider = mvcViewOptions.Value.GetCompositeFileProvider(); _createCache = CreateCache; _assemblies = assemblies.ToArray(); } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs index 0005730bc0..7144090f82 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RazorCompilationService.cs @@ -27,9 +27,7 @@ public class RazorCompilationService : IRazorCompilationService /// /// The to compile generated code. /// The to generate code from Razor files. - /// - /// The to read Razor files referenced in error messages. - /// + /// Accessor to . public RazorCompilationService( ICompilationService compilationService, IMvcRazorHost razorHost, @@ -37,7 +35,7 @@ public RazorCompilationService( { _compilationService = compilationService; _razorHost = razorHost; - _fileProvider = viewEngineOptions.Value.FileProvider; + _fileProvider = viewEngineOptions.Value.GetCompositeFileProvider(); } /// @@ -105,9 +103,10 @@ internal CompilationResult GetCompilationFailedResult(RelativeFileInfo file, IEn private DiagnosticMessage CreateDiagnosticMessage(RazorError error, string filePath) { + var location = error.Location; return new DiagnosticMessage( message: error.Message, - formattedMessage: $"{error} ({error.Location.LineIndex},{error.Location.CharacterIndex}) {error.Message}", + formattedMessage: $"{error} ({location.LineIndex},{location.CharacterIndex}) {error.Message}", filePath: filePath, startLine: error.Location.LineIndex + 1, startColumn: error.Location.CharacterIndex, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index 0736bed2ed..02ffbc3116 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -65,7 +65,7 @@ public RoslynCompilationService( _environment = environment; _libraryExporter = libraryExporter; _applicationReferences = new Lazy>(GetApplicationReferences); - _fileProvider = optionsAccessor.Value.FileProvider; + _fileProvider = optionsAccessor.Value.GetCompositeFileProvider(); _classPrefix = host.MainClassNamePrefix; _compilationCallback = optionsAccessor.Value.CompilationCallback; _parseOptions = optionsAccessor.Value.ParseOptions; diff --git a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index dd7cc28fe7..d84f213eae 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.CompilationAbstractions; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.MemoryPool; using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; @@ -134,8 +133,8 @@ internal static void AddRazorViewEngineServices(IServiceCollection services) services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => { - var cachedFileProvider = serviceProvider.GetRequiredService>(); - return new DefaultChunkTreeCache(cachedFileProvider.Value.FileProvider); + var viewEngineOptions = serviceProvider.GetRequiredService>(); + return new DefaultChunkTreeCache(viewEngineOptions.Value.GetCompositeFileProvider()); })); // Caches compilation artifacts across the lifetime of the application. diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptions.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptions.cs index cb8c63ed54..8910ee47bd 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptions.cs @@ -14,13 +14,13 @@ namespace Microsoft.AspNet.Mvc.Razor /// public class RazorViewEngineOptions { - private IFileProvider _fileProvider; - private CSharpParseOptions _parseOptions = new CSharpParseOptions(LanguageVersion.CSharp6); - private CSharpCompilationOptions _compilationOptions = new CSharpCompilationOptions(CodeAnalysis.OutputKind.DynamicallyLinkedLibrary); + private CSharpCompilationOptions _compilationOptions = + new CSharpCompilationOptions(CodeAnalysis.OutputKind.DynamicallyLinkedLibrary); private Action _compilationCallback = c => { }; + private IFileProvider _fileProvider; /// /// Get a used by the . @@ -29,33 +29,22 @@ public class RazorViewEngineOptions = new List(); /// - /// Gets or sets the used by to locate Razor files on - /// disk. + /// Gets the sequence of instances used by to + /// locate Razor files. /// /// - /// At startup, this is initialized to an instance of that is rooted at the - /// application root. + /// At startup, this is initialized to include an instance of that is + /// rooted at the application root. /// - public IFileProvider FileProvider - { - get { return _fileProvider; } - - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _fileProvider = value; - } - } + public IList FileProviders { get; } = new List(); /// /// Gets or sets the callback that is used to customize Razor compilation /// to change compilation settings you can update property. - /// Customizations made here would not reflect in tooling (Intellisense). /// + /// + /// Customizations made here would not reflect in tooling (Intellisense). + /// public Action CompilationCallback { get { return _compilationCallback; } @@ -65,6 +54,7 @@ public Action CompilationCallback { throw new ArgumentNullException(nameof(value)); } + _compilationCallback = value; } } @@ -81,6 +71,7 @@ public CSharpParseOptions ParseOptions { throw new ArgumentNullException(nameof(value)); } + _parseOptions = value; } } @@ -97,9 +88,31 @@ public CSharpCompilationOptions CompilationOptions { throw new ArgumentNullException(nameof(value)); } + _compilationOptions = value; } } + /// + /// Gets the composite result of . + /// + /// The instance if exactly one instance is registered, otherwise + /// a that contains all registered instances. + public IFileProvider GetCompositeFileProvider() + { + if (_fileProvider == null) + { + if (FileProviders.Count == 1) + { + _fileProvider = FileProviders[0]; + } + else + { + _fileProvider = new CompositeFileProvider(FileProviders); + } + } + + return _fileProvider; + } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptionsSetup.cs index 905dceada0..6f72117d3f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngineOptionsSetup.cs @@ -31,7 +31,7 @@ private static void ConfigureRazor(RazorViewEngineOptions razorOptions, IApplicationEnvironment applicationEnvironment, IHostingEnvironment hostingEnvironment) { - razorOptions.FileProvider = new PhysicalFileProvider(applicationEnvironment.ApplicationBasePath); + razorOptions.FileProviders.Add(new PhysicalFileProvider(applicationEnvironment.ApplicationBasePath)); var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp6); var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json index 58f3b8322a..4c84eebeea 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor/project.json @@ -10,6 +10,7 @@ "keyFile": "../../tools/Key.snk" }, "dependencies": { + "Microsoft.AspNet.FileProviders.Composite": "1.0.0-*", "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", "Microsoft.AspNet.Mvc.ViewFeatures": "6.0.0-*", "Microsoft.AspNet.Razor.Runtime.Precompilation": "4.0.0-*", @@ -26,8 +27,7 @@ "type": "build" }, "Microsoft.Dnx.Compilation.CSharp.Common": "1.0.0-*", - "Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*", - "Microsoft.Extensions.MemoryPool": "1.0.0-*" + "Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*" }, "frameworks": { "net451": { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs index e45598d0cf..0de344c602 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs @@ -213,10 +213,8 @@ private static GeneratorResults GetGeneratorResult() private static IOptions GetOptions(IFileProvider fileProvider = null) { - var razorViewEngineOptions = new RazorViewEngineOptions - { - FileProvider = fileProvider ?? new TestFileProvider() - }; + var razorViewEngineOptions = new RazorViewEngineOptions(); + razorViewEngineOptions.FileProviders.Add(fileProvider ?? new TestFileProvider()); var options = new Mock>(); options.SetupGet(o => o.Value) .Returns(razorViewEngineOptions); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs index 8eea92a5ca..7c3428d518 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs @@ -224,17 +224,16 @@ public void GetCompilationFailedResult_ReturnsCompilationResult_WithGroupedMessa var generatedCodeFileName = "Generated Code"; var fileProvider = new TestFileProvider(); fileProvider.AddFile(viewPath, "view-content"); - var options = new Mock>(); - options.SetupGet(o => o.Value) - .Returns(new RazorViewEngineOptions - { - FileProvider = fileProvider - }); + var options = new RazorViewEngineOptions(); + options.FileProviders.Add(fileProvider); + var optionsAccessor = new Mock>(); + optionsAccessor.SetupGet(o => o.Value) + .Returns(options); var compilationService = new RoslynCompilationService( PlatformServices.Default.Application, CompilationServices.Default.LibraryExporter, Mock.Of(), - options.Object); + optionsAccessor.Object); var assemblyName = "random-assembly-name"; @@ -355,9 +354,9 @@ private static IOptions GetOptions(IFileProvider filePro { var razorViewEngineOptions = new RazorViewEngineOptions { - FileProvider = fileProvider ?? new TestFileProvider(), - CompilationCallback = callback ?? (c => { }) + CompilationCallback = callback ?? (c => { }), }; + razorViewEngineOptions.FileProviders.Add(fileProvider ?? new TestFileProvider()); var options = new Mock>(); options .SetupGet(o => o.Value) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs index 4f2fc1c814..925b05843f 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -12,18 +12,27 @@ namespace Microsoft.AspNet.Mvc.Razor public class RazorViewEngineOptionsTest { [Fact] - public void FileProviderThrows_IfNullIsAssigned() + public void AddRazorOptions_ConfiguresOptionsAsExpected() { // Arrange - var options = new RazorViewEngineOptions(); + var services = new ServiceCollection().AddOptions(); + var fileProvider = new TestFileProvider(); - // Act and Assert - var ex = Assert.Throws(() => options.FileProvider = null); - Assert.Equal("value", ex.ParamName); + // Act + var builder = new MvcBuilder(services); + builder.AddRazorOptions(options => + { + options.FileProviders.Add(fileProvider); + }); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var accessor = serviceProvider.GetRequiredService>(); + Assert.Same(fileProvider, accessor.Value.FileProviders[0]); } [Fact] - public void AddRazorOptions_ConfiguresOptionsProperly() + public void GetCompositeFileProvider_ReturnsInstanceIfExactlyOneFileProviderIsSpecified() { // Arrange var services = new ServiceCollection().AddOptions(); @@ -33,13 +42,54 @@ public void AddRazorOptions_ConfiguresOptionsProperly() var builder = new MvcBuilder(services); builder.AddRazorOptions(options => { - options.FileProvider = fileProvider; + options.FileProviders.Add(fileProvider); + }); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var accessor = serviceProvider.GetRequiredService>(); + Assert.Same(fileProvider, accessor.Value.GetCompositeFileProvider()); + } + + [Fact] + public void GetCompositeFileProvider_ReturnsCompositeFileProviderIfNoInstancesAreRegistered() + { + // Arrange + var services = new ServiceCollection().AddOptions(); + + // Act + var builder = new MvcBuilder(services); + builder.AddRazorOptions(options => + { + options.FileProviders.Clear(); + }); + var serviceProvider = services.BuildServiceProvider(); + + // Assert + var accessor = serviceProvider.GetRequiredService>(); + var result = accessor.Value.GetCompositeFileProvider(); + Assert.IsType(result); + } + + [Fact] + public void GetCompositeFileProvider_ReturnsCompositeFileProviderIfMoreThanOneInstanceIsRegistered() + { + // Arrange + var services = new ServiceCollection().AddOptions(); + + // Act + var builder = new MvcBuilder(services); + builder.AddRazorOptions(options => + { + options.FileProviders.Add(new TestFileProvider()); + options.FileProviders.Add(new TestFileProvider()); }); var serviceProvider = services.BuildServiceProvider(); // Assert var accessor = serviceProvider.GetRequiredService>(); - Assert.Same(fileProvider, accessor.Value.FileProvider); + var result = accessor.Value.GetCompositeFileProvider(); + Assert.IsType(result); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs index b0921c51ec..0bd74da724 100644 --- a/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs @@ -21,18 +21,18 @@ public void RazorViewEngineOptionsSetup_SetsUpFileProvider() var options = new RazorViewEngineOptions(); var appEnv = new Mock(); appEnv.SetupGet(e => e.ApplicationBasePath) - .Returns(Directory.GetCurrentDirectory()); + .Returns(Directory.GetCurrentDirectory()); var hostingEnv = new Mock(); hostingEnv.SetupGet(e => e.EnvironmentName) - .Returns("Development"); + .Returns("Development"); var optionsSetup = new RazorViewEngineOptionsSetup(appEnv.Object, hostingEnv.Object); // Act optionsSetup.Configure(options); // Assert - Assert.NotNull(options.FileProvider); - Assert.IsType(options.FileProvider); + var fileProvider = Assert.Single(options.FileProviders); + Assert.IsType(fileProvider); } [Theory] @@ -62,7 +62,9 @@ public void RazorViewEngineOptionsSetup_SetsPreprocessorSymbols(string environme [InlineData("Development", OptimizationLevel.Debug)] [InlineData("Staging", OptimizationLevel.Release)] [InlineData("Production", OptimizationLevel.Release)] - public void RazorViewEngineOptionsSetup_SetsOptimizationLevel(string environment, OptimizationLevel expectedOptimizationLevel) + public void RazorViewEngineOptionsSetup_SetsOptimizationLevel( + string environment, + OptimizationLevel expectedOptimizationLevel) { // Arrange var options = new RazorViewEngineOptions();