diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index acffe82583..3923e0ea2f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -245,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.Razor private IRazorPage GetLayoutPage(ViewContext context, string executingFilePath, string layoutPath) { var layoutPageResult = _viewEngine.GetPage(executingFilePath, layoutPath, isPartial: true); + var originalLocations = layoutPageResult.SearchedLocations; if (layoutPageResult.Page == null) { layoutPageResult = _viewEngine.FindPage(context, layoutPath, isPartial: true); @@ -252,8 +253,18 @@ namespace Microsoft.AspNet.Mvc.Razor if (layoutPageResult.Page == null) { - var locations = - Environment.NewLine + string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); + var locations = string.Empty; + if (originalLocations.Any()) + { + locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations); + } + + if (layoutPageResult.SearchedLocations.Any()) + { + locations += + Environment.NewLine + string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); + } + throw new InvalidOperationException(Resources.FormatLayoutCannotBeLocated(layoutPath, locations)); } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs index 805a837140..3baf4a0505 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/PartialViewResult.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc var executor = services.GetRequiredService(); var result = executor.FindView(context, this); - result.EnsureSuccessful(); + result.EnsureSuccessful(originalLocations: null); var view = result.View; using (view as IDisposable) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs index c43403c693..93e8b9ac97 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Properties/Resources.Designer.cs @@ -507,7 +507,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The view '{0}' was not found. The following locations were searched:{1}. + /// The view '{0}' was not found. The following locations were searched:{1} /// internal static string ViewEngine_ViewNotFound { @@ -515,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } /// - /// The view '{0}' was not found. The following locations were searched:{1}. + /// The view '{0}' was not found. The following locations were searched:{1} /// internal static string FormatViewEngine_ViewNotFound(object p0, object p1) { diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx index cf06865c1a..81c987746f 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Resources.resx @@ -1,17 +1,17 @@  - @@ -212,7 +212,7 @@ The partial view '{0}' was not found. The following locations were searched:{1} - The view '{0}' was not found. The following locations were searched:{1}. + The view '{0}' was not found. The following locations were searched:{1} The value must be greater than or equal to zero. diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs index 3239e763c9..7c9291e5ab 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs @@ -2,6 +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 System.Diagnostics; using System.Globalization; using System.Threading.Tasks; @@ -82,10 +83,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents var isNullOrEmptyViewName = string.IsNullOrEmpty(ViewName); ViewEngineResult result = null; + IEnumerable originalLocations = null; if (!isNullOrEmptyViewName) { // If view name was passed in is already a path, the view engine will handle this. result = viewEngine.GetView(viewContext.ExecutingFilePath, ViewName, isPartial: true); + originalLocations = result.SearchedLocations; } if (result == null || !result.Success) @@ -111,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents result = viewEngine.FindView(viewContext, qualifiedViewName, isPartial: true); } - var view = result.EnsureSuccessful().View; + var view = result.EnsureSuccessful(originalLocations).View; using (view as IDisposable) { if (_diagnosticSource == null) @@ -121,7 +124,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents _diagnosticSource.ViewComponentBeforeViewExecute(context, view); - var childViewContext = new ViewContext(viewContext, view, ViewData ?? context.ViewData, context.Writer); + var childViewContext = new ViewContext( + viewContext, + view, + ViewData ?? context.ViewData, + context.Writer); await view.RenderAsync(childViewContext); _diagnosticSource.ViewComponentAfterViewExecute(context, view); diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs index 635c03ec19..ab59770a2d 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNet.Mvc.ViewFeatures; namespace Microsoft.AspNet.Mvc.ViewEngines @@ -64,14 +65,30 @@ namespace Microsoft.AspNet.Mvc.ViewEngines }; } - public ViewEngineResult EnsureSuccessful() + /// + /// Ensure this was successful. + /// + /// + /// Additional to include in the thrown + /// if is false. + /// + /// + /// Thrown if is false. + /// + /// This if is true. + public ViewEngineResult EnsureSuccessful(IEnumerable originalLocations) { if (!Success) { var locations = string.Empty; - if (SearchedLocations != null) + if (originalLocations != null && originalLocations.Any()) { - locations = Environment.NewLine + string.Join(Environment.NewLine, SearchedLocations); + locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations); + } + + if (SearchedLocations.Any()) + { + locations += Environment.NewLine + string.Join(Environment.NewLine, SearchedLocations); } throw new InvalidOperationException(Resources.FormatViewEngine_ViewNotFound(ViewName, locations)); diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs index 30f682a39a..88c40932ea 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs @@ -544,6 +544,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures ViewContext.ExecutingFilePath, partialViewName, isPartial: true); + var originalLocations = viewEngineResult.SearchedLocations; if (!viewEngineResult.Success) { viewEngineResult = _viewEngine.FindView(ViewContext, partialViewName, isPartial: true); @@ -552,10 +553,15 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures if (!viewEngineResult.Success) { var locations = string.Empty; - if (viewEngineResult.SearchedLocations != null) + if (originalLocations.Any()) { - locations = Environment.NewLine + - string.Join(Environment.NewLine, viewEngineResult.SearchedLocations); + locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations); + } + + if (viewEngineResult.SearchedLocations.Any()) + { + locations += + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations); } throw new InvalidOperationException( diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs index 5a90cf8eec..4358f20a1a 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs @@ -2,7 +2,9 @@ // 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 System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Infrastructure; @@ -70,11 +72,31 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: true); + var originalResult = result; if (!result.Success) { result = viewEngine.FindView(actionContext, viewName, isPartial: true); } + if (!result.Success) + { + if (originalResult.SearchedLocations.Any()) + { + if (result.SearchedLocations.Any()) + { + // Return a new ViewEngineResult listing all searched locations. + var locations = new List(originalResult.SearchedLocations); + locations.AddRange(result.SearchedLocations); + result = ViewEngineResult.NotFound(viewName, locations); + } + else + { + // GetView() searched locations but FindView() did not. Use first ViewEngineResult. + result = originalResult; + } + } + } + if (result.Success) { DiagnosticSource.ViewFound(actionContext, true, viewResult, viewName, result.View); diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs index 2dd06ae420..f40b3d02fb 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs @@ -2,12 +2,13 @@ // 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 System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.ViewEngines; -using Microsoft.AspNet.Mvc.ViewFeatures.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.OptionsModel; @@ -69,11 +70,31 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false); + var originalResult = result; if (!result.Success) { result = viewEngine.FindView(actionContext, viewName, isPartial: false); } + if (!result.Success) + { + if (originalResult.SearchedLocations.Any()) + { + if (result.SearchedLocations.Any()) + { + // Return a new ViewEngineResult listing all searched locations. + var locations = new List(originalResult.SearchedLocations); + locations.AddRange(result.SearchedLocations); + result = ViewEngineResult.NotFound(viewName, locations); + } + else + { + // GetView() searched locations but FindView() did not. Use first ViewEngineResult. + result = originalResult; + } + } + } + if (result.Success) { if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound")) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs index 70400ae931..566d2f0f20 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc var executor = services.GetRequiredService(); var result = executor.FindView(context, this); - result.EnsureSuccessful(); + result.EnsureSuccessful(originalLocations: null); var view = result.View; using (view as IDisposable) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs index 0f726466f9..6bee8e5b34 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs @@ -368,7 +368,50 @@ namespace Microsoft.AspNet.Mvc.Razor } [Fact] - public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound() + public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound_MessageUsesGetPageLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The layout view 'Does-Not-Exist-Layout' could not be located. The following locations were searched:", + "path1", + "path2"); + + var layoutPath = "Does-Not-Exist-Layout"; + var page = new TestableRazorPage(v => + { + v.Layout = layoutPath; + }); + + var viewEngine = new Mock(MockBehavior.Strict); + var activator = new Mock(); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); + var viewContext = CreateViewContext(view); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, new[] { "path1", "path2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindPage(viewContext, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, Enumerable.Empty())) + .Verifiable(); + + // Act + var ex = await Assert.ThrowsAsync(() => view.RenderAsync(viewContext)); + + // Assert + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + + [Fact] + public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound_MessageUsesFindPageLocations() { // Arrange var expected = string.Join( @@ -410,6 +453,51 @@ namespace Microsoft.AspNet.Mvc.Razor viewEngine.Verify(); } + [Fact] + public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound_MessageUsesAllLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The layout view 'Does-Not-Exist-Layout' could not be located. The following locations were searched:", + "path1", + "path2", + "path3", + "path4"); + + var layoutPath = "Does-Not-Exist-Layout"; + var page = new TestableRazorPage(v => + { + v.Layout = layoutPath; + }); + + var viewEngine = new Mock(MockBehavior.Strict); + var activator = new Mock(); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); + var viewContext = CreateViewContext(view); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, new[] { "path1", "path2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindPage(viewContext, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, new[] { "path3", "path4" })) + .Verifiable(); + + // Act + var ex = await Assert.ThrowsAsync(() => view.RenderAsync(viewContext)); + + // Assert + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + [Fact] public async Task RenderAsync_ExecutesLayoutPages() { diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs index 84420f6358..745813e4cf 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs @@ -26,14 +26,51 @@ namespace Microsoft.AspNet.Mvc public class PartialViewResultTest { [Fact] - public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound() + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesGetViewLocations() { // Arrange var expected = string.Join( Environment.NewLine, "The view 'MyView' was not found. The following locations were searched:", "Location1", - "Location2."); + "Location2"); + + var actionContext = GetActionContext(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "MyView", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty())) + .Verifiable(); + + var viewResult = new PartialViewResult + { + ViewEngine = viewEngine.Object, + ViewName = "MyView", + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesFindViewLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2"); var actionContext = GetActionContext(); @@ -62,6 +99,45 @@ namespace Microsoft.AspNet.Mvc viewEngine.Verify(); } + [Fact] + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesAllLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2", + "Location3", + "Location4"); + + var actionContext = GetActionContext(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "MyView", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location3", "Location4" })) + .Verifiable(); + + var viewResult = new PartialViewResult + { + ViewEngine = viewEngine.Object, + ViewName = "MyView", + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + [Fact] public async Task ExecuteResultAsync_FindsAndExecutesView() { diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs index 35fdf58775..49566f10cd 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs @@ -3,10 +3,12 @@ #if MOCK_SUPPORT using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.TestCommon; +using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewFeatures; using Moq; using Xunit; @@ -188,6 +190,184 @@ namespace Microsoft.AspNet.Mvc.Rendering Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(actual)); } + [Fact] + public async Task PartialAsync_Throws_IfViewNotFound_MessageUsesGetViewLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty())) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.PartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task PartialAsync_Throws_IfViewNotFound_MessageUsesFindViewLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.PartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task PartialAsync_Throws_IfViewNotFound_MessageUsesAllLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2" + Environment.NewLine + + "location3" + Environment.NewLine + + "location4"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location3", "location4" })) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.PartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task RenderPartialAsync_Throws_IfViewNotFound_MessageUsesGetViewLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty())) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.RenderPartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task RenderPartialAsync_Throws_IfViewNotFound_MessageUsesFindViewLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.RenderPartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task RenderPartialAsync_Throws_IfViewNotFound_MessageUsesAllLocations() + { + // Arrange + var expected = "The partial view 'test-view' was not found. The following locations were searched:" + + Environment.NewLine + + "location1" + Environment.NewLine + + "location2" + Environment.NewLine + + "location3" + Environment.NewLine + + "location4"; + + var model = new TestModel(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("test-view", new[] { "location3", "location4" })) + .Verifiable(); + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); + var viewData = helper.ViewData; + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => helper.RenderPartialAsync("test-view", model, viewData)); + Assert.Equal(expected, exception.Message); + } + private sealed class TestModel { public override string ToString() diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs index 95c72d64aa..74e7821a63 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -143,13 +143,51 @@ namespace Microsoft.AspNet.Mvc } [Fact] - public void Execute_ThrowsIfPartialViewCannotBeFound() + public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesGetViewLocations() { // Arrange var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "location1", - "location2."); + "location2"); + + var view = Mock.Of(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", Enumerable.Empty())) + .Verifiable(); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData, + TempData = _tempDataDictionary, + }; + + var viewComponentContext = GetViewComponentContext(view, viewData); + + // Act and Assert + var ex = Assert.Throws(() => result.Execute(viewComponentContext)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesFindViewLocations() + { + // Arrange + var expected = string.Join(Environment.NewLine, + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", + "location1", + "location2"); var view = Mock.Of(); @@ -180,6 +218,46 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(expected, ex.Message); } + [Fact] + public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesAllLocations() + { + // Arrange + var expected = string.Join(Environment.NewLine, + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", + "location1", + "location2", + "location3", + "location4"); + + var view = Mock.Of(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", new[] { "location3", "location4" })) + .Verifiable(); + + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); + + var result = new ViewViewComponentResult + { + ViewEngine = viewEngine.Object, + ViewName = "some-view", + ViewData = viewData, + TempData = _tempDataDictionary, + }; + + var viewComponentContext = GetViewComponentContext(view, viewData); + + // Act and Assert + var ex = Assert.Throws(() => result.Execute(viewComponentContext)); + Assert.Equal(expected, ex.Message); + } + [Fact] public void Execute_DoesNotWrapThrownExceptionsInAggregateExceptions() { @@ -305,20 +383,22 @@ namespace Microsoft.AspNet.Mvc var expected = string.Join(Environment.NewLine, "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "view-location1", - "view-location2."); + "view-location2", + "view-location3", + "view-location4"); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); viewEngine .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) - .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Returns(ViewEngineResult.NotFound("some-view", new[] { "view-location1", "view-location2" })) .Verifiable(); viewEngine .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) .Returns(ViewEngineResult.NotFound( "Components/Invoke/some-view", - new[] { "view-location1", "view-location2" })) + new[] { "view-location3", "view-location4" })) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -423,7 +503,10 @@ namespace Microsoft.AspNet.Mvc viewEngine.Verify(); } - private static ViewComponentContext GetViewComponentContext(IView view, ViewDataDictionary viewData, object diagnosticListener = null) + private static ViewComponentContext GetViewComponentContext( + IView view, + ViewDataDictionary viewData, + object diagnosticListener = null) { var diagnosticSource = new DiagnosticListener("Microsoft.AspNet"); if (diagnosticListener == null) diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs index adf334a0e4..1509e780a3 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs @@ -76,6 +76,111 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures Assert.Equal(viewName, viewEngineResult.ViewName); } + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithGetViewLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", expectedLocations)) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())); + + var viewResult = new PartialViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithFindViewLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", expectedLocations)); + + var viewResult = new PartialViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithAllLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2", "location3", "location4" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", new[] { "location1", "location2" })) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", new[] { "location3", "location4" })); + + var viewResult = new PartialViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + [Fact] public void FindView_WritesDiagnostic_ViewFound() { diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs index 4141ab07ab..9f100e10d8 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs @@ -76,6 +76,108 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures Assert.Equal(viewName, viewEngineResult.ViewName); } + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithGetViewLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", expectedLocations)); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())); + + var viewResult = new ViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithFindViewLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", expectedLocations)); + + var viewResult = new ViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + + [Fact] + public void FindView_ReturnsExpectedNotFoundResult_WithAllLocations() + { + // Arrange + var expectedLocations = new[] { "location1", "location2", "location3", "location4" }; + var context = GetActionContext(); + var executor = GetViewExecutor(); + + var viewName = "myview"; + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", new[] { "location1", "location2" })); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", new[] { "location3", "location4" })); + + var viewResult = new ViewResult + { + ViewName = viewName, + ViewEngine = viewEngine.Object, + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act + var result = executor.FindView(context, viewResult); + + // Assert + Assert.False(result.Success); + Assert.Null(result.View); + Assert.Equal(expectedLocations, result.SearchedLocations); + } + [Fact] public void FindView_WritesDiagnostic_ViewFound() { diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs index 9b0662d107..9a239674b3 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs @@ -26,14 +26,51 @@ namespace Microsoft.AspNet.Mvc public class ViewResultTest { [Fact] - public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound() + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesGetViewLocations() { // Arrange var expected = string.Join( Environment.NewLine, "The view 'MyView' was not found. The following locations were searched:", "Location1", - "Location2."); + "Location2"); + + var actionContext = GetActionContext(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty())) + .Verifiable(); + + var viewResult = new ViewResult + { + ViewEngine = viewEngine.Object, + ViewName = "MyView", + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + + [Fact] + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesFindViewLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2"); var actionContext = GetActionContext(); @@ -62,6 +99,45 @@ namespace Microsoft.AspNet.Mvc viewEngine.Verify(); } + [Fact] + public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesAllLocations() + { + // Arrange + var expected = string.Join( + Environment.NewLine, + "The view 'MyView' was not found. The following locations were searched:", + "Location1", + "Location2", + "Location3", + "Location4"); + + var actionContext = GetActionContext(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(e => e.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location3", "Location4" })) + .Verifiable(); + + var viewResult = new ViewResult + { + ViewEngine = viewEngine.Object, + ViewName = "MyView", + ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()), + TempData = Mock.Of(), + }; + + // Act and Assert + var ex = await Assert.ThrowsAsync( + () => viewResult.ExecuteResultAsync(actionContext)); + Assert.Equal(expected, ex.Message); + viewEngine.Verify(); + } + [Fact] public async Task ExecuteResultAsync_FindsAndExecutesView() {