diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs
index 34514536e7..c88a512af0 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs
@@ -8,6 +8,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{
public class CompiledViewDescriptor
{
+ ///
+ /// The normalized application relative path of the view.
+ ///
public string RelativePath { get; set; }
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs
index 09af95c9c1..0ffd390d52 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs
@@ -57,11 +57,11 @@ public RazorPageFactoryResult CreateFactory(string relativePath)
var pageFactory = Expression
.Lambda>(objectInitializeExpression)
.Compile();
- return new RazorPageFactoryResult(pageFactory, viewDescriptor.ExpirationTokens, viewDescriptor.IsPrecompiled);
+ return new RazorPageFactoryResult(viewDescriptor, pageFactory);
}
else
{
- return new RazorPageFactoryResult(viewDescriptor.ExpirationTokens);
+ return new RazorPageFactoryResult(viewDescriptor, razorPageFactory: null);
}
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs
index 1d430ca4da..31f7b1ce94 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs
@@ -2,8 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.Primitives;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
namespace Microsoft.AspNetCore.Mvc.Razor
{
@@ -12,61 +11,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor
///
public struct RazorPageFactoryResult
{
- ///
- /// Initializes a new instance of with the
- /// specified .
- ///
- /// One or more instances.
- public RazorPageFactoryResult(IList expirationTokens)
- {
- if (expirationTokens == null)
- {
- throw new ArgumentNullException(nameof(expirationTokens));
- }
-
- ExpirationTokens = expirationTokens;
- RazorPageFactory = null;
- IsPrecompiled = false;
- }
-
///
/// Initializes a new instance of with the
/// specified factory.
///
/// The factory.
- /// One or more instances.
+ /// The .
public RazorPageFactoryResult(
- Func razorPageFactory,
- IList expirationTokens)
- : this(razorPageFactory, expirationTokens, isPrecompiled: false)
+ CompiledViewDescriptor viewDescriptor,
+ Func razorPageFactory)
{
- }
-
- ///
- /// Initializes a new instance of with the
- /// specified factory.
- ///
- /// The factory.
- /// One or more instances.
- /// true if the view is precompiled, false otherwise.
- public RazorPageFactoryResult(
- Func razorPageFactory,
- IList expirationTokens,
- bool isPrecompiled)
- {
- if (razorPageFactory == null)
- {
- throw new ArgumentNullException(nameof(razorPageFactory));
- }
-
- if (expirationTokens == null)
- {
- throw new ArgumentNullException(nameof(expirationTokens));
- }
-
+ ViewDescriptor = viewDescriptor ?? throw new ArgumentNullException(nameof(viewDescriptor));
RazorPageFactory = razorPageFactory;
- ExpirationTokens = expirationTokens;
- IsPrecompiled = isPrecompiled;
}
///
@@ -76,19 +32,13 @@ public RazorPageFactoryResult(
public Func RazorPageFactory { get; }
///
- /// One or more s associated with this instance of
- /// .
+ /// Gets the .
///
- public IList ExpirationTokens { get; }
+ public CompiledViewDescriptor ViewDescriptor { get; }
///
/// Gets a value that determines if the page was successfully located.
///
public bool Success => RazorPageFactory != null;
-
- ///
- /// Gets a value that determines if the view is precompiled.
- ///
- public bool IsPrecompiled { get; }
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
index 243a899f46..75e7382423 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
@@ -393,11 +393,12 @@ internal ViewLocationCacheResult CreateCacheResult(
bool isMainPage)
{
var factoryResult = _pageFactory.CreateFactory(relativePath);
- if (factoryResult.ExpirationTokens != null)
+ var viewDescriptor = factoryResult.ViewDescriptor;
+ if (viewDescriptor?.ExpirationTokens != null)
{
- for (var i = 0; i < factoryResult.ExpirationTokens.Count; i++)
+ for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++)
{
- expirationTokens.Add(factoryResult.ExpirationTokens[i]);
+ expirationTokens.Add(viewDescriptor.ExpirationTokens[i]);
}
}
@@ -405,9 +406,9 @@ internal ViewLocationCacheResult CreateCacheResult(
{
// Only need to lookup _ViewStarts for the main page.
var viewStartPages = isMainPage ?
- GetViewStartPages(relativePath, expirationTokens) :
+ GetViewStartPages(viewDescriptor.RelativePath, expirationTokens) :
Array.Empty();
- if (factoryResult.IsPrecompiled)
+ if (viewDescriptor.IsPrecompiled)
{
_logger.PrecompiledViewFound(relativePath);
}
@@ -424,17 +425,17 @@ private IReadOnlyList GetViewStartPages(
string path,
HashSet expirationTokens)
{
- var applicationRelativePath = MakePathApplicationRelative(path);
var viewStartPages = new List();
- foreach (var viewStartProjectItem in _razorProject.FindHierarchicalItems(applicationRelativePath, ViewStartFileName))
+ foreach (var viewStartProjectItem in _razorProject.FindHierarchicalItems(path, ViewStartFileName))
{
var result = _pageFactory.CreateFactory(viewStartProjectItem.Path);
- if (result.ExpirationTokens != null)
+ var viewDescriptor = result.ViewDescriptor;
+ if (viewDescriptor?.ExpirationTokens != null)
{
- for (var i = 0; i < result.ExpirationTokens.Count; i++)
+ for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++)
{
- expirationTokens.Add(result.ExpirationTokens[i]);
+ expirationTokens.Add(viewDescriptor.ExpirationTokens[i]);
}
}
@@ -476,22 +477,6 @@ private static bool IsApplicationRelativePath(string name)
return name[0] == '~' || name[0] == '/';
}
- private string MakePathApplicationRelative(string path)
- {
- Debug.Assert(!string.IsNullOrEmpty(path));
- if (path[0] == '~')
- {
- path = path.Substring(1);
- }
-
- if (path[0] != '/')
- {
- path = '/' + path;
- }
-
- return path;
- }
-
private static bool IsRelativePath(string name)
{
Debug.Assert(!string.IsNullOrEmpty(name));
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs
index cfa8378b50..af66f1ce37 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs
@@ -476,5 +476,21 @@ public async Task RazorView_SetsViewPathAndExecutingPagePath()
ignoreLineEndingDifferences: true);
#endif
}
+
+ [Fact]
+ public async Task ViewEngine_NormalizesPathsReturnedByViewLocationExpanders()
+ {
+ // Arrange
+ var expected =
+@"Layout
+Page
+Partial";
+
+ // Act
+ var responseContent = await Client.GetStringAsync("/BackSlash");
+
+ // Assert
+ Assert.Equal(expected, responseContent.Trim());
+ }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs
index 8a8697c731..c216e4993a 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
public class DefaultRazorPageFactoryProviderTest
{
[Fact]
- public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessfulResults()
+ public void CreateFactory_ReturnsViewDescriptor_ForUnsuccessfulResults()
{
// Arrange
var path = "/file-does-not-exist";
@@ -39,11 +39,11 @@ public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForUnsuccessf
// Assert
Assert.False(result.Success);
- Assert.Equal(expirationTokens, result.ExpirationTokens);
+ Assert.Same(descriptor, result.ViewDescriptor);
}
[Fact]
- public void CreateFactory_ReturnsExpirationTokensFromCompilerCache_ForSuccessfulResults()
+ public void CreateFactory_ReturnsViewDescriptor_ForSuccessfulResults()
{
// Arrange
var relativePath = "/file-exists";
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
index be2529dd93..5334cbe371 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
@@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewEngines;
@@ -168,15 +169,15 @@ public void FindView_ReturnsRazorView_IfLookupWasSuccessful()
pageFactory
.Setup(p => p.CreateFactory("/Views/bar/test-view.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page));
pageFactory
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart2, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => viewStart2));
pageFactory
.Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart1, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => viewStart1));
var viewEngine = CreateViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
@@ -204,11 +205,11 @@ public void FindView_DoesNotExpireCachedResults_IfViewStartsExpire()
pageFactory
.Setup(p => p.CreateFactory("/Views/bar/test-view.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page));
pageFactory
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart, new[] { changeToken }));
+ .Returns(GetPageFactoryResult(() => viewStart, new[] { changeToken }));
var viewEngine = CreateViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
@@ -333,15 +334,15 @@ public void FindView_IsMainPage_ReturnsRazorView_IfLookupWasSuccessful()
pageFactory
.Setup(p => p.CreateFactory("/Views/bar/test-view.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page));
pageFactory
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart2, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => viewStart2));
pageFactory
.Setup(p => p.CreateFactory("/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart1, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => viewStart1));
var viewEngine = CreateViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
@@ -369,7 +370,7 @@ public void FindView_UsesViewLocationFormat_IfRouteDoesNotContainArea()
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory("fake-path1/bar/test-view.rzr"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -397,7 +398,7 @@ public void FindView_UsesAreaViewLocationFormat_IfRouteContainsArea()
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedViewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -426,7 +427,7 @@ public void GetView_DoesNotUseViewLocationFormat_WithRelativePath_IfRouteDoesNot
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedViewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -452,7 +453,7 @@ public void GetView_DoesNotUseViewLocationFormat_WithRelativePath_IfRouteContain
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedViewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -480,7 +481,7 @@ public void GetView_UsesGivenPath_WithAppRelativePath(string viewName)
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(viewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -508,7 +509,7 @@ public void GetView_ResolvesRelativeToCurrentPage_WithRelativePath(string viewNa
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedViewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -536,7 +537,7 @@ public void GetView_ResolvesRelativeToAppRoot_WithRelativePath_IfNoPageExecuting
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedViewName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -564,10 +565,10 @@ public void FindView_CreatesDifferentCacheEntries_ForAreaViewsAndNonAreaViews(bo
var nonAreaPage = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory("/Areas/Admin/Views/Home/Index.cshtml"))
- .Returns(new RazorPageFactoryResult(() => areaPage, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => areaPage));
pageFactory
.Setup(p => p.CreateFactory("/Views/Home/Index.cshtml"))
- .Returns(new RazorPageFactoryResult(() => nonAreaPage, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => nonAreaPage));
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -628,10 +629,10 @@ public void FindView_CreatesDifferentCacheEntries_ForDifferentAreas(bool isMainP
var areaPage2 = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory("/Areas/Marketing/Views/Home/Index.cshtml"))
- .Returns(new RazorPageFactoryResult(() => areaPage1, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => areaPage1));
pageFactory
.Setup(p => p.CreateFactory("/Areas/Sales/Views/Home/Index.cshtml"))
- .Returns(new RazorPageFactoryResult(() => areaPage2, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => areaPage2));
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -692,7 +693,7 @@ public void FindView_UsesViewLocationExpandersToLocateViews(
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("test-string/bar.cshtml"))
- .Returns(new RazorPageFactoryResult(() => Mock.Of(), new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => Mock.Of()))
.Verifiable();
var expander1Result = new[] { "some-seed" };
@@ -745,6 +746,37 @@ public void FindView_UsesViewLocationExpandersToLocateViews(
expander2.Verify();
}
+ [Fact]
+ public void FindView_NoramlizesPaths_ReturnedByViewLocationExpanders()
+ {
+ // Arrange
+ var pageFactory = new Mock();
+ pageFactory
+ .Setup(p => p.CreateFactory(@"Views\Home\Index.cshtml"))
+ .Returns(GetPageFactoryResult(() => Mock.Of()))
+ .Verifiable();
+
+ var expander = new Mock();
+ expander
+ .Setup(e => e.ExpandViewLocations(
+ It.IsAny(),
+ It.IsAny>()))
+ .Returns(new[] { @"Views\Home\Index.cshtml" });
+
+ var viewEngine = CreateViewEngine(
+ pageFactory.Object,
+ new[] { expander.Object });
+ var context = GetActionContext(new Dictionary());
+
+ // Act
+ var result = viewEngine.FindView(context, "test-view", isMainPage: true);
+
+ // Assert
+ Assert.True(result.Success);
+ Assert.IsAssignableFrom(result.View);
+ pageFactory.Verify();
+ }
+
[Fact]
public void FindView_CachesValuesIfViewWasFound()
{
@@ -753,11 +785,11 @@ public void FindView_CachesValuesIfViewWasFound()
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(factory: null))
.Verifiable();
pageFactory
.Setup(p => p.CreateFactory("/Views/Shared/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = CreateViewEngine(pageFactory.Object);
@@ -794,7 +826,7 @@ public void FindView_CachesValuesIfViewWasFound_ForPages()
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("/Views/Shared/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = CreateViewEngine(pageFactory.Object);
@@ -837,16 +869,16 @@ public void FindView_InvokesPageFactoryIfChangeTokenExpired()
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(new[] { changeToken }));
+ .Returns(GetPageFactoryResult(factory: null, changeTokens: new[] { changeToken }));
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/Shared/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page1, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page1))
.Verifiable();
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page2, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page2));
var viewEngine = CreateViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
@@ -885,20 +917,20 @@ public void FindView_InvokesPageFactoryIfViewStartExpirationTokensHaveExpired()
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page1, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page1));
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(new[] { changeToken }))
+ .Returns(GetPageFactoryResult(factory: null, changeTokens: new[] { changeToken }))
.Verifiable();
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/bar/baz.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page2, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page2));
pageFactory
.InSequence(sequence)
.Setup(p => p.CreateFactory("/Views/_ViewStart.cshtml"))
- .Returns(new RazorPageFactoryResult(() => viewStart, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => viewStart));
var fileProvider = new TestFileProvider();
var razorProject = new FileProviderRazorProject(fileProvider);
@@ -992,7 +1024,7 @@ public void FindView_InvokesViewLocationExpanders_IfChangeTokenExpires()
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("viewlocation3"))
- .Returns(new RazorPageFactoryResult(new[] { changeToken }));
+ .Returns(GetPageFactoryResult(factory: null, changeTokens: new[] { changeToken }));
var expander = new Mock();
var expandedLocations = new[]
{
@@ -1030,7 +1062,7 @@ public void FindView_InvokesViewLocationExpanders_IfChangeTokenExpires()
// Act - 2
pageFactory
.Setup(p => p.CreateFactory("viewlocation3"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => page));
cancellationTokenSource.Cancel();
result = viewEngine.FindView(context, "MyView", isMainPage: true);
@@ -1130,7 +1162,7 @@ public void FindPage_UsesViewLocationExpander_ToExpandPaths(
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("expanded-path/bar-layout"))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var expander = new Mock();
@@ -1209,7 +1241,7 @@ public void FindPage_SelectsActionCaseInsensitively()
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory("/Views/Foo/details.cshtml"))
- .Returns(new RazorPageFactoryResult(() => page.Object, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page.Object))
.Verifiable();
var viewEngine = CreateViewEngine(pageFactory.Object);
@@ -1338,10 +1370,12 @@ public void CreateCacheResult_LogsPrecompiledViewFound()
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var relativePath = "/Views/Foo/details.cshtml";
+ var factoryResult = GetPageFactoryResult(() => Mock.Of());
+ factoryResult.ViewDescriptor.IsPrecompiled = true;
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory(relativePath))
- .Returns(new RazorPageFactoryResult(() => Mock.Of(), new IChangeToken[0], isPrecompiled: true))
+ .Returns(factoryResult)
.Verifiable();
var viewEngine = new RazorViewEngine(
@@ -1373,7 +1407,7 @@ public void GetPage_UsesGivenPath_WithAppRelativePath(string pageName)
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(pageName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -1401,7 +1435,7 @@ public void GetPage_ResolvesRelativeToCurrentPage_WithRelativePath(string pageNa
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedPageName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -1429,7 +1463,7 @@ public void GetPage_ResolvesRelativeToAppRoot_WithRelativePath_IfNoPageExecuting
var page = Mock.Of();
pageFactory
.Setup(p => p.CreateFactory(expectedPageName))
- .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0]))
+ .Returns(GetPageFactoryResult(() => page))
.Verifiable();
var viewEngine = new TestableRazorViewEngine(
pageFactory.Object,
@@ -1759,11 +1793,25 @@ private RazorViewEngine CreateSuccessfulViewEngine()
var pageFactory = new Mock(MockBehavior.Strict);
pageFactory
.Setup(f => f.CreateFactory(It.IsAny()))
- .Returns(new RazorPageFactoryResult(() => Mock.Of(), new IChangeToken[0]));
+ .Returns(GetPageFactoryResult(() => Mock.Of()));
return CreateViewEngine(pageFactory.Object);
}
+ private static RazorPageFactoryResult GetPageFactoryResult(
+ Func factory,
+ IList changeTokens = null,
+ string path = "/Views/Home/Index.cshtml")
+ {
+ var descriptor = new CompiledViewDescriptor
+ {
+ ExpirationTokens = changeTokens ?? Array.Empty(),
+ RelativePath = path,
+ };
+
+ return new RazorPageFactoryResult(descriptor, factory);
+ }
+
private TestableRazorViewEngine CreateViewEngine(
IRazorPageFactoryProvider pageFactory = null,
IEnumerable expanders = null,
diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs
index ce5dc500da..7655ac50ba 100644
--- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs
@@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
@@ -201,10 +202,11 @@ public async Task RenderAsync_AsPartial_ExecutesLayout_ButNotViewStartPages()
v.Write("layout-content" + Environment.NewLine);
v.RenderBodyPublic();
});
+ var pageFactoryResult = new RazorPageFactoryResult(new CompiledViewDescriptor(), () => layout);
var pageFactory = new Mock();
pageFactory
.Setup(p => p.CreateFactory(LayoutPath))
- .Returns(new RazorPageFactoryResult(() => layout, new IChangeToken[0]));
+ .Returns(pageFactoryResult);
var viewEngine = new Mock(MockBehavior.Strict);
viewEngine
diff --git a/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs b/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs
new file mode 100644
index 0000000000..ca43a6f58d
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs
@@ -0,0 +1,12 @@
+// 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 Microsoft.AspNetCore.Mvc;
+
+namespace RazorWebSite.Controllers
+{
+ public class BackSlashController : Controller
+ {
+ public IActionResult Index() => View(@"Views\BackSlash\BackSlashView.cshtml");
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/RazorWebSite.csproj b/test/WebSites/RazorWebSite/RazorWebSite.csproj
index 6234f1c05f..d087f196fd 100644
--- a/test/WebSites/RazorWebSite/RazorWebSite.csproj
+++ b/test/WebSites/RazorWebSite/RazorWebSite.csproj
@@ -19,5 +19,6 @@
+
diff --git a/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs b/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs
new file mode 100644
index 0000000000..065d9a6413
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs
@@ -0,0 +1,27 @@
+// 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.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.Razor;
+using Microsoft.AspNetCore.Mvc.Rendering;
+
+namespace RazorWebSite
+{
+ public class ForwardSlashExpander : IViewLocationExpander
+ {
+ public void PopulateValues(ViewLocationExpanderContext context)
+ {
+ }
+
+ public virtual IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations)
+ {
+ if (context.ActionContext is ViewContext viewContext && (string)viewContext.ViewData["back-slash"] == "true")
+ {
+ return new[] { $@"Views\BackSlash\{context.ViewName}.cshtml" };
+
+ }
+
+ return viewLocations;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs
index a4c7f7b68e..4bb646bc5c 100644
--- a/test/WebSites/RazorWebSite/Startup.cs
+++ b/test/WebSites/RazorWebSite/Startup.cs
@@ -33,6 +33,7 @@ public void ConfigureServices(IServiceCollection services)
$"{nameof(RazorWebSite)}.EmbeddedViews"));
options.FileProviders.Add(updateableFileProvider);
options.ViewLocationExpanders.Add(new NonMainPageViewLocationExpander());
+ options.ViewLocationExpanders.Add(new ForwardSlashExpander());
})
.AddViewOptions(options =>
{
@@ -51,6 +52,7 @@ public void ConfigureServices(IServiceCollection services)
public void Configure(IApplicationBuilder app)
{
+ app.UseDeveloperExceptionPage();
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-GB", "en-US"),
diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml b/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml
new file mode 100644
index 0000000000..e28dd6e75f
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml
@@ -0,0 +1,6 @@
+@{
+ ViewData["back-slash"] = "true";
+ Layout = "_Layout";
+}
+Page
+@Html.Partial("_BackSlashPartial")
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml b/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml
new file mode 100644
index 0000000000..8bfc4e8000
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml
@@ -0,0 +1 @@
+Partial
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml b/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml
new file mode 100644
index 0000000000..6bba921339
--- /dev/null
+++ b/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml
@@ -0,0 +1,2 @@
+Layout
+@RenderBody()
\ No newline at end of file