diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
index e1c2a734e6..8c460c1ceb 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs
@@ -462,6 +462,22 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("Compilation_DependencyContextIsNotSpecified"), p0, p1, p2);
}
+ ///
+ /// '{0}' cannot be empty. These locations are required to locate a view for rendering.
+ ///
+ internal static string ViewLocationFormatsIsRequired
+ {
+ get { return GetString("ViewLocationFormatsIsRequired"); }
+ }
+
+ ///
+ /// '{0}' cannot be empty. These locations are required to locate a view for rendering.
+ ///
+ internal static string FormatViewLocationFormatsIsRequired(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ViewLocationFormatsIsRequired"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
index 09a333451d..3bce3cf339 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
@@ -8,7 +8,6 @@ using System.Globalization;
using System.Linq;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
-using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
@@ -21,12 +20,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// Default implementation of .
///
///
- /// For ViewResults returned from controllers, views should be located in
- /// by default. For the controllers in an area, views should exist in .
+ /// For ViewResults returned from controllers, views should be located in
+ ///
+ /// by default. For the controllers in an area, views should exist in
+ /// .
///
public class RazorViewEngine : IRazorViewEngine
{
- private const string ViewExtension = ".cshtml";
+ public static readonly string ViewExtension = ".cshtml";
+
private const string ControllerKey = "controller";
private const string AreaKey = "area";
private static readonly ViewLocationCacheItem[] EmptyViewStartLocationCacheItems =
@@ -34,10 +36,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20);
private readonly IRazorPageFactoryProvider _pageFactory;
- private readonly IList _viewLocationExpanders;
private readonly IRazorPageActivator _pageActivator;
private readonly HtmlEncoder _htmlEncoder;
private readonly ILogger _logger;
+ private readonly RazorViewEngineOptions _options;
///
/// Initializes a new instance of the .
@@ -49,9 +51,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor
IOptions optionsAccessor,
ILoggerFactory loggerFactory)
{
+ _options = optionsAccessor.Value;
+
+ if (_options.ViewLocationFormats.Count == 0)
+ {
+ throw new ArgumentException(
+ Resources.FormatViewLocationFormatsIsRequired(nameof(RazorViewEngineOptions.ViewLocationFormats)),
+ nameof(optionsAccessor));
+ }
+
+ if (_options.AreaViewLocationFormats.Count == 0)
+ {
+ throw new ArgumentException(
+ Resources.FormatViewLocationFormatsIsRequired(nameof(RazorViewEngineOptions.AreaViewLocationFormats)),
+ nameof(optionsAccessor));
+ }
+
_pageFactory = pageFactory;
_pageActivator = pageActivator;
- _viewLocationExpanders = optionsAccessor.Value.ViewLocationExpanders;
_htmlEncoder = htmlEncoder;
_logger = loggerFactory.CreateLogger();
ViewLookupCache = new MemoryCache(new MemoryCacheOptions
@@ -60,47 +77,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
});
}
- ///
- /// Gets the locations where this instance of will search for views.
- ///
- ///
- /// The locations of the views returned from controllers that do not belong to an area.
- /// Locations are composite format strings (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx),
- /// which contains following indexes:
- /// {0} - Action Name
- /// {1} - Controller Name
- /// The values for these locations are case-sensitive on case-sensitive file systems.
- /// For example, the view for the Test action of HomeController should be located at
- /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered
- ///
- public virtual IEnumerable ViewLocationFormats { get; } = new[]
- {
- "/Views/{1}/{0}" + ViewExtension,
- "/Views/Shared/{0}" + ViewExtension,
- };
-
- ///
- /// Gets the locations where this instance of will search for views within an
- /// area.
- ///
- ///
- /// The locations of the views returned from controllers that belong to an area.
- /// Locations are composite format strings (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx),
- /// which contains following indexes:
- /// {0} - Action Name
- /// {1} - Controller Name
- /// {2} - Area name
- /// The values for these locations are case-sensitive on case-sensitive file systems.
- /// For example, the view for the Test action of HomeController should be located at
- /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered
- ///
- public virtual IEnumerable AreaViewLocationFormats { get; } = new[]
- {
- "/Areas/{2}/Views/{1}/{0}" + ViewExtension,
- "/Areas/{2}/Views/Shared/{0}" + ViewExtension,
- "/Views/Shared/{0}" + ViewExtension,
- };
-
///
/// A cache for results of view lookups.
///
@@ -311,15 +287,15 @@ namespace Microsoft.AspNetCore.Mvc.Razor
isMainPage);
Dictionary expanderValues = null;
- if (_viewLocationExpanders.Count > 0)
+ if (_options.ViewLocationExpanders.Count > 0)
{
expanderValues = new Dictionary(StringComparer.Ordinal);
expanderContext.Values = expanderValues;
// Perf: Avoid allocations
- for (var i = 0; i < _viewLocationExpanders.Count; i++)
+ for (var i = 0; i < _options.ViewLocationExpanders.Count; i++)
{
- _viewLocationExpanders[i].PopulateValues(expanderContext);
+ _options.ViewLocationExpanders[i].PopulateValues(expanderContext);
}
}
@@ -385,13 +361,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
ViewLocationCacheKey cacheKey)
{
// Only use the area view location formats if we have an area token.
- var viewLocations = !string.IsNullOrEmpty(expanderContext.AreaName) ?
- AreaViewLocationFormats :
- ViewLocationFormats;
+ IEnumerable viewLocations = !string.IsNullOrEmpty(expanderContext.AreaName) ?
+ _options.AreaViewLocationFormats :
+ _options.ViewLocationFormats;
- for (var i = 0; i < _viewLocationExpanders.Count; i++)
+ for (var i = 0; i < _options.ViewLocationExpanders.Count; i++)
{
- viewLocations = _viewLocationExpanders[i].ExpandViewLocations(expanderContext, viewLocations);
+ viewLocations = _options.ViewLocationExpanders[i].ExpandViewLocations(expanderContext, viewLocations);
}
ViewLocationCacheResult cacheResult = null;
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs
index f2ab1f0c43..e279c88ec9 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs
@@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private Action _compilationCallback = c => { };
///
- /// Get a used by the .
+ /// Gets a used by the .
///
public IList ViewLocationExpanders { get; }
= new List();
@@ -35,6 +35,61 @@ namespace Microsoft.AspNetCore.Mvc.Razor
///
public IList FileProviders { get; } = new List();
+ ///
+ /// Gets the locations where will search for views.
+ ///
+ ///
+ ///
+ /// The locations of the views returned from controllers that do not belong to an area.
+ /// Locations are composite format strings (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx),
+ /// which may contain the following format items:
+ ///
+ ///
+ /// -
+ /// {0} - Action Name
+ ///
+ /// -
+ /// {1} - Controller Name
+ ///
+ ///
+ ///
+ /// The values for these locations are case-sensitive on case-sensitive file systems.
+ /// For example, the view for the Test action of HomeController should be located at
+ /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered.
+ ///
+ ///
+ public IList ViewLocationFormats { get; } = new List();
+
+ ///
+ /// Gets the locations where will search for views within an
+ /// area.
+ ///
+ ///
+ ///
+ /// The locations of the views returned from controllers that belong to an area.
+ /// Locations are composite format strings (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx),
+ /// which may contain the following format items:
+ ///
+ ///
+ /// -
+ /// {0} - Action Name
+ ///
+ /// -
+ /// {1} - Controller Name
+ ///
+ /// -
+ /// {2} - Area Name
+ ///
+ ///
+ ///
+ /// The values for these locations are case-sensitive on case-sensitive file systems.
+ /// For example, the view for the Test action of HomeController under Admin area should
+ /// be located at /Areas/Admin/Views/Home/Test.cshtml.
+ /// Locations such as /areas/admin/views/home/test.cshtml would not be discovered.
+ ///
+ ///
+ public IList AreaViewLocationFormats { get; } = new List();
+
///
/// Gets or sets the callback that is used to customize Razor compilation
/// to change compilation settings you can update property.
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs
index 97607bc9ec..dbba63a6c3 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs
@@ -50,6 +50,13 @@ namespace Microsoft.AspNetCore.Mvc
var parseOptions = razorOptions.ParseOptions;
razorOptions.ParseOptions = parseOptions.WithPreprocessorSymbols(
parseOptions.PreprocessorSymbolNames.Concat(new[] { configurationSymbol }));
+
+ razorOptions.ViewLocationFormats.Add("/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
+ razorOptions.ViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
+
+ razorOptions.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
+ razorOptions.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
+ razorOptions.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
index f44490e3db..762d5356b5 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx
@@ -203,4 +203,7 @@
The Razor page '{0}' failed to compile. Ensure that your application's {1} sets the '{2}' compilation property.
+
+ '{0}' cannot be empty. These locations are required to locate a view for rendering.
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs
index bd6b59f1ca..6172036b6c 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs
@@ -1,6 +1,8 @@
// 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 System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.DependencyInjection;
@@ -11,6 +13,67 @@ namespace Microsoft.AspNetCore.Mvc.Razor
{
public class RazorViewEngineOptionsTest
{
+ [Fact]
+ public void AreaViewLocationFormats_ContainsExpectedLocations()
+ {
+ // Arrange
+ var services = new ServiceCollection().AddOptions();
+ var areaViewLocations = new[]
+ {
+ "/Areas/{2}/MvcViews/{1}/{0}.cshtml",
+ "/Areas/{2}/MvcViews/Shared/{0}.cshtml",
+ "/MvcViews/Shared/{0}.cshtml"
+ };
+ var builder = new MvcBuilder(services, new ApplicationPartManager());
+ builder.AddRazorOptions(options =>
+ {
+ options.AreaViewLocationFormats.Clear();
+
+ foreach (var location in areaViewLocations)
+ {
+ options.AreaViewLocationFormats.Add(location);
+ }
+ });
+ var serviceProvider = services.BuildServiceProvider();
+ var accessor = serviceProvider.GetRequiredService>();
+
+ // Act
+ var formats = accessor.Value.AreaViewLocationFormats;
+
+ // Assert
+ Assert.Equal(areaViewLocations, formats, StringComparer.Ordinal);
+ }
+
+ [Fact]
+ public void ViewLocationFormats_ContainsExpectedLocations()
+ {
+ // Arrange
+ var services = new ServiceCollection().AddOptions();
+ var viewLocations = new[]
+ {
+ "/MvcViews/{1}/{0}.cshtml",
+ "/MvcViews/Shared/{0}.cshtml"
+ };
+ var builder = new MvcBuilder(services, new ApplicationPartManager());
+ builder.AddRazorOptions(options =>
+ {
+ options.ViewLocationFormats.Clear();
+
+ foreach (var location in viewLocations)
+ {
+ options.ViewLocationFormats.Add(location);
+ }
+ });
+ var serviceProvider = services.BuildServiceProvider();
+ var accessor = serviceProvider.GetRequiredService>();
+
+ // Act
+ var formats = accessor.Value.ViewLocationFormats;
+
+ // Assert
+ Assert.Equal(viewLocations, formats, StringComparer.Ordinal);
+ }
+
[Fact]
public void AddRazorOptions_ConfiguresOptionsAsExpected()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
index 13888ae2de..5be204df1c 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Routing;
@@ -364,10 +365,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
- GetOptionsAccessor());
- viewEngine.SetLocationFormats(
- new[] { "fake-path1/{1}/{0}.rzr" },
- new[] { "fake-area-path/{2}/{1}/{0}.rzr" });
+ GetOptionsAccessor(
+ viewLocationFormats: new[] { "fake-path1/{1}/{0}.rzr" },
+ areaViewLocationFormats: new[] { "fake-area-path/{2}/{1}/{0}.rzr" }));
var context = GetActionContext(_controllerTestContext);
// Act
@@ -393,10 +393,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
- GetOptionsAccessor());
- viewEngine.SetLocationFormats(
- new[] { "fake-path1/{1}/{0}.rzr" },
- new[] { "fake-area-path/{2}/{1}/{0}.rzr" });
+ GetOptionsAccessor(
+ viewLocationFormats: new[] { "fake-path1/{1}/{0}.rzr" },
+ areaViewLocationFormats: new[] { "fake-area-path/{2}/{1}/{0}.rzr" }));
var context = GetActionContext(_areaTestContext);
// Act
@@ -1462,38 +1461,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
Assert.Equal(expectedPagePath, result);
}
- [Fact]
- public void AreaViewLocationFormats_ContainsExpectedLocations()
- {
- // Arrange
- var viewEngine = CreateViewEngine();
- var areaViewLocations = new string[]
- {
- "/Areas/{2}/Views/{1}/{0}.cshtml",
- "/Areas/{2}/Views/Shared/{0}.cshtml",
- "/Views/Shared/{0}.cshtml"
- };
-
- // Act & Assert
- Assert.Equal(areaViewLocations, viewEngine.AreaViewLocationFormats);
- }
-
- [Fact]
- public void ViewLocationFormats_ContainsExpectedLocations()
- {
- // Arrange
- var viewEngine = CreateViewEngine();
-
- var viewLocations = new string[]
- {
- "/Views/{1}/{0}.cshtml",
- "/Views/Shared/{0}.cshtml"
- };
-
- // Act & Assert
- Assert.Equal(viewLocations, viewEngine.ViewLocationFormats);
- }
-
[Fact]
public void GetNormalizedRouteValue_ReturnsValueFromRouteValues_IfKeyHandlingIsRequired()
{
@@ -1713,15 +1680,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
IEnumerable expanders = null)
{
pageFactory = pageFactory ?? Mock.Of();
- return new TestableRazorViewEngine(
- pageFactory,
- GetOptionsAccessor(expanders));
+ return new TestableRazorViewEngine(pageFactory, GetOptionsAccessor(expanders));
}
private static IOptions GetOptionsAccessor(
- IEnumerable expanders = null)
+ IEnumerable expanders = null,
+ IEnumerable viewLocationFormats = null,
+ IEnumerable areaViewLocationFormats = null)
{
+ var optionsSetup = new RazorViewEngineOptionsSetup(Mock.Of());
+
var options = new RazorViewEngineOptions();
+ optionsSetup.Configure(options);
+
if (expanders != null)
{
foreach (var expander in expanders)
@@ -1730,6 +1701,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
}
}
+ if (viewLocationFormats != null)
+ {
+ options.ViewLocationFormats.Clear();
+
+ foreach (var location in viewLocationFormats)
+ {
+ options.ViewLocationFormats.Add(location);
+ }
+ }
+
+ if (areaViewLocationFormats != null)
+ {
+ options.AreaViewLocationFormats.Clear();
+
+ foreach (var location in areaViewLocationFormats)
+ {
+ options.AreaViewLocationFormats.Add(location);
+ }
+ }
+
var optionsAccessor = new Mock>();
optionsAccessor
.SetupGet(v => v.Value)
@@ -1784,9 +1775,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
private class TestableRazorViewEngine : RazorViewEngine
{
- private IEnumerable _viewLocationFormats;
- private IEnumerable _areaViewLocationFormats;
-
public TestableRazorViewEngine(
IRazorPageFactoryProvider pageFactory,
IOptions optionsAccessor)
@@ -1794,20 +1782,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test
{
}
- public void SetLocationFormats(
- IEnumerable viewLocationFormats,
- IEnumerable areaViewLocationFormats)
- {
- _viewLocationFormats = viewLocationFormats;
- _areaViewLocationFormats = areaViewLocationFormats;
- }
-
- public override IEnumerable ViewLocationFormats =>
- _viewLocationFormats != null ? _viewLocationFormats : base.ViewLocationFormats;
-
- public override IEnumerable AreaViewLocationFormats =>
- _areaViewLocationFormats != null ? _areaViewLocationFormats : base.AreaViewLocationFormats;
-
public IMemoryCache ViewLookupCachePublic => ViewLookupCache;
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Test/RazorViewEngineOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/RazorViewEngineOptionsSetupTest.cs
index f47ee5c334..5969526041 100644
--- a/test/Microsoft.AspNetCore.Mvc.Test/RazorViewEngineOptionsSetupTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Test/RazorViewEngineOptionsSetupTest.cs
@@ -1,12 +1,10 @@
// 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.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.FileProviders;
-using Microsoft.Extensions.PlatformAbstractions;
using Moq;
using Xunit;