Improve `SearchLocations` handling

- do not blindly use `FindPage()` / `FindView()` result in `Exception.Message` or returned results
  - failure scenarios involve new `Any()` calls but rarely additional `List<string>()` allocations
- change `ViewEngine_ViewNotFound` resource to be consistent with similar errors
  - remove trailing period at end of searched locations list

nit: remove remaining `null` checks of `SearchedLocations` in not found cases; never `null` then
This commit is contained in:
Doug Bunting 2015-11-15 20:08:31 -08:00
parent 08dd77d8c7
commit d1fe824b5d
17 changed files with 848 additions and 54 deletions

View File

@ -245,6 +245,7 @@ namespace Microsoft.AspNet.Mvc.Razor
private IRazorPage GetLayoutPage(ViewContext context, string executingFilePath, string layoutPath) private IRazorPage GetLayoutPage(ViewContext context, string executingFilePath, string layoutPath)
{ {
var layoutPageResult = _viewEngine.GetPage(executingFilePath, layoutPath, isPartial: true); var layoutPageResult = _viewEngine.GetPage(executingFilePath, layoutPath, isPartial: true);
var originalLocations = layoutPageResult.SearchedLocations;
if (layoutPageResult.Page == null) if (layoutPageResult.Page == null)
{ {
layoutPageResult = _viewEngine.FindPage(context, layoutPath, isPartial: true); layoutPageResult = _viewEngine.FindPage(context, layoutPath, isPartial: true);
@ -252,8 +253,18 @@ namespace Microsoft.AspNet.Mvc.Razor
if (layoutPageResult.Page == null) if (layoutPageResult.Page == null)
{ {
var locations = var locations = string.Empty;
Environment.NewLine + string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); 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)); throw new InvalidOperationException(Resources.FormatLayoutCannotBeLocated(layoutPath, locations));
} }

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
var executor = services.GetRequiredService<PartialViewResultExecutor>(); var executor = services.GetRequiredService<PartialViewResultExecutor>();
var result = executor.FindView(context, this); var result = executor.FindView(context, this);
result.EnsureSuccessful(); result.EnsureSuccessful(originalLocations: null);
var view = result.View; var view = result.View;
using (view as IDisposable) using (view as IDisposable)

View File

@ -507,7 +507,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view '{0}' was not found. The following locations were searched:{1}. /// The view '{0}' was not found. The following locations were searched:{1}
/// </summary> /// </summary>
internal static string ViewEngine_ViewNotFound internal static string ViewEngine_ViewNotFound
{ {
@ -515,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
/// <summary> /// <summary>
/// The view '{0}' was not found. The following locations were searched:{1}. /// The view '{0}' was not found. The following locations were searched:{1}
/// </summary> /// </summary>
internal static string FormatViewEngine_ViewNotFound(object p0, object p1) internal static string FormatViewEngine_ViewNotFound(object p0, object p1)
{ {

View File

@ -212,7 +212,7 @@
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value> <value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
</data> </data>
<data name="ViewEngine_ViewNotFound" xml:space="preserve"> <data name="ViewEngine_ViewNotFound" xml:space="preserve">
<value>The view '{0}' was not found. The following locations were searched:{1}.</value> <value>The view '{0}' was not found. The following locations were searched:{1}</value>
</data> </data>
<data name="HtmlHelper_TextAreaParameterOutOfRange" xml:space="preserve"> <data name="HtmlHelper_TextAreaParameterOutOfRange" xml:space="preserve">
<value>The value must be greater than or equal to zero.</value> <value>The value must be greater than or equal to zero.</value>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -82,10 +83,12 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
var isNullOrEmptyViewName = string.IsNullOrEmpty(ViewName); var isNullOrEmptyViewName = string.IsNullOrEmpty(ViewName);
ViewEngineResult result = null; ViewEngineResult result = null;
IEnumerable<string> originalLocations = null;
if (!isNullOrEmptyViewName) if (!isNullOrEmptyViewName)
{ {
// If view name was passed in is already a path, the view engine will handle this. // If view name was passed in is already a path, the view engine will handle this.
result = viewEngine.GetView(viewContext.ExecutingFilePath, ViewName, isPartial: true); result = viewEngine.GetView(viewContext.ExecutingFilePath, ViewName, isPartial: true);
originalLocations = result.SearchedLocations;
} }
if (result == null || !result.Success) if (result == null || !result.Success)
@ -111,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
result = viewEngine.FindView(viewContext, qualifiedViewName, isPartial: true); result = viewEngine.FindView(viewContext, qualifiedViewName, isPartial: true);
} }
var view = result.EnsureSuccessful().View; var view = result.EnsureSuccessful(originalLocations).View;
using (view as IDisposable) using (view as IDisposable)
{ {
if (_diagnosticSource == null) if (_diagnosticSource == null)
@ -121,7 +124,11 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
_diagnosticSource.ViewComponentBeforeViewExecute(context, view); _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); await view.RenderAsync(childViewContext);
_diagnosticSource.ViewComponentAfterViewExecute(context, view); _diagnosticSource.ViewComponentAfterViewExecute(context, view);

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
namespace Microsoft.AspNet.Mvc.ViewEngines namespace Microsoft.AspNet.Mvc.ViewEngines
@ -64,14 +65,30 @@ namespace Microsoft.AspNet.Mvc.ViewEngines
}; };
} }
public ViewEngineResult EnsureSuccessful() /// <summary>
/// Ensure this <see cref="ViewEngineResult"/> was successful.
/// </summary>
/// <param name="originalLocations">
/// Additional <see cref="SearchedLocations"/> to include in the thrown <see cref="InvalidOperationException"/>
/// if <see cref="Success"/> is <c>false</c>.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Success"/> is <c>false</c>.
/// </exception>
/// <returns>This <see cref="ViewEngineResult"/> if <see cref="Success"/> is <c>true</c>.</returns>
public ViewEngineResult EnsureSuccessful(IEnumerable<string> originalLocations)
{ {
if (!Success) if (!Success)
{ {
var locations = string.Empty; 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)); throw new InvalidOperationException(Resources.FormatViewEngine_ViewNotFound(ViewName, locations));

View File

@ -544,6 +544,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
ViewContext.ExecutingFilePath, ViewContext.ExecutingFilePath,
partialViewName, partialViewName,
isPartial: true); isPartial: true);
var originalLocations = viewEngineResult.SearchedLocations;
if (!viewEngineResult.Success) if (!viewEngineResult.Success)
{ {
viewEngineResult = _viewEngine.FindView(ViewContext, partialViewName, isPartial: true); viewEngineResult = _viewEngine.FindView(ViewContext, partialViewName, isPartial: true);
@ -552,10 +553,15 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
if (!viewEngineResult.Success) if (!viewEngineResult.Success)
{ {
var locations = string.Empty; var locations = string.Empty;
if (viewEngineResult.SearchedLocations != null) if (originalLocations.Any())
{ {
locations = Environment.NewLine + locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations);
string.Join(Environment.NewLine, viewEngineResult.SearchedLocations); }
if (viewEngineResult.SearchedLocations.Any())
{
locations +=
Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations);
} }
throw new InvalidOperationException( throw new InvalidOperationException(

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Diagnostics; using Microsoft.AspNet.Mvc.Diagnostics;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
@ -70,11 +72,31 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name;
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: true); var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: true);
var originalResult = result;
if (!result.Success) if (!result.Success)
{ {
result = viewEngine.FindView(actionContext, viewName, isPartial: true); 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<string>(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 (result.Success)
{ {
DiagnosticSource.ViewFound(actionContext, true, viewResult, viewName, result.View); DiagnosticSource.ViewFound(actionContext, true, viewResult, viewName, result.View);

View File

@ -2,12 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Infrastructure; using Microsoft.AspNet.Mvc.Infrastructure;
using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Logging;
using Microsoft.AspNet.Mvc.ViewEngines; using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures.Logging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.OptionsModel; using Microsoft.Extensions.OptionsModel;
@ -69,11 +70,31 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name;
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false); var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false);
var originalResult = result;
if (!result.Success) if (!result.Success)
{ {
result = viewEngine.FindView(actionContext, viewName, isPartial: false); 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<string>(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 (result.Success)
{ {
if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound")) if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound"))

View File

@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
var executor = services.GetRequiredService<ViewResultExecutor>(); var executor = services.GetRequiredService<ViewResultExecutor>();
var result = executor.FindView(context, this); var result = executor.FindView(context, this);
result.EnsureSuccessful(); result.EnsureSuccessful(originalLocations: null);
var view = result.View; var view = result.View;
using (view as IDisposable) using (view as IDisposable)

View File

@ -368,7 +368,50 @@ namespace Microsoft.AspNet.Mvc.Razor
} }
[Fact] [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<IRazorViewEngine>(MockBehavior.Strict);
var activator = new Mock<IRazorPageActivator>();
var view = new RazorView(
viewEngine.Object,
Mock.Of<IRazorPageActivator>(),
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<string>()))
.Verifiable();
// Act
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
// Assert
Assert.Equal(expected, ex.Message);
viewEngine.Verify();
}
[Fact]
public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound_MessageUsesFindPageLocations()
{ {
// Arrange // Arrange
var expected = string.Join( var expected = string.Join(
@ -410,6 +453,51 @@ namespace Microsoft.AspNet.Mvc.Razor
viewEngine.Verify(); 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<IRazorViewEngine>(MockBehavior.Strict);
var activator = new Mock<IRazorPageActivator>();
var view = new RazorView(
viewEngine.Object,
Mock.Of<IRazorPageActivator>(),
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<InvalidOperationException>(() => view.RenderAsync(viewContext));
// Assert
Assert.Equal(expected, ex.Message);
viewEngine.Verify();
}
[Fact] [Fact]
public async Task RenderAsync_ExecutesLayoutPages() public async Task RenderAsync_ExecutesLayoutPages()
{ {

View File

@ -26,14 +26,51 @@ namespace Microsoft.AspNet.Mvc
public class PartialViewResultTest public class PartialViewResultTest
{ {
[Fact] [Fact]
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound() public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesGetViewLocations()
{ {
// Arrange // Arrange
var expected = string.Join( var expected = string.Join(
Environment.NewLine, Environment.NewLine,
"The view 'MyView' was not found. The following locations were searched:", "The view 'MyView' was not found. The following locations were searched:",
"Location1", "Location1",
"Location2."); "Location2");
var actionContext = GetActionContext();
var viewEngine = new Mock<IViewEngine>(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<ActionContext>(), "MyView", /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty<string>()))
.Verifiable();
var viewResult = new PartialViewResult
{
ViewEngine = viewEngine.Object,
ViewName = "MyView",
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = Mock.Of<ITempDataDictionary>(),
};
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => 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(); var actionContext = GetActionContext();
@ -62,6 +99,45 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify(); 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<IViewEngine>(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<ActionContext>(), "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<ITempDataDictionary>(),
};
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => viewResult.ExecuteResultAsync(actionContext));
Assert.Equal(expected, ex.Message);
viewEngine.Verify();
}
[Fact] [Fact]
public async Task ExecuteResultAsync_FindsAndExecutesView() public async Task ExecuteResultAsync_FindsAndExecutesView()
{ {

View File

@ -3,10 +3,12 @@
#if MOCK_SUPPORT #if MOCK_SUPPORT
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Mvc.ViewEngines;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Moq; using Moq;
using Xunit; using Xunit;
@ -188,6 +190,184 @@ namespace Microsoft.AspNet.Mvc.Rendering
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(actual)); 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" }))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty<string>()))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
var viewData = helper.ViewData;
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty<string>()))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*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<InvalidOperationException>(
() => 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" }))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*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<InvalidOperationException>(
() => 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" }))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty<string>()))
.Verifiable();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
var viewData = helper.ViewData;
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", Enumerable.Empty<string>()))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*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<InvalidOperationException>(
() => 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<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("test-view", new[] { "location1", "location2" }))
.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*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<InvalidOperationException>(
() => helper.RenderPartialAsync("test-view", model, viewData));
Assert.Equal(expected, exception.Message);
}
private sealed class TestModel private sealed class TestModel
{ {
public override string ToString() public override string ToString()

View File

@ -143,13 +143,51 @@ namespace Microsoft.AspNet.Mvc
} }
[Fact] [Fact]
public void Execute_ThrowsIfPartialViewCannotBeFound() public void Execute_ThrowsIfPartialViewCannotBeFound_MessageUsesGetViewLocations()
{ {
// Arrange // Arrange
var expected = string.Join(Environment.NewLine, var expected = string.Join(Environment.NewLine,
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
"location1", "location1",
"location2."); "location2");
var view = Mock.Of<IView>();
var viewEngine = new Mock<IViewEngine>(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<ActionContext>(), "Components/Invoke/some-view", /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", Enumerable.Empty<string>()))
.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<InvalidOperationException>(() => 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<IView>(); var view = Mock.Of<IView>();
@ -180,6 +218,46 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(expected, ex.Message); 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<IView>();
var viewEngine = new Mock<IViewEngine>(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<ActionContext>(), "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<InvalidOperationException>(() => result.Execute(viewComponentContext));
Assert.Equal(expected, ex.Message);
}
[Fact] [Fact]
public void Execute_DoesNotWrapThrownExceptionsInAggregateExceptions() public void Execute_DoesNotWrapThrownExceptionsInAggregateExceptions()
{ {
@ -305,20 +383,22 @@ namespace Microsoft.AspNet.Mvc
var expected = string.Join(Environment.NewLine, var expected = string.Join(Environment.NewLine,
"The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "The view 'Components/Invoke/some-view' was not found. The following locations were searched:",
"view-location1", "view-location1",
"view-location2."); "view-location2",
"view-location3",
"view-location4");
var view = Mock.Of<IView>(); var view = Mock.Of<IView>();
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict); var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
viewEngine viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty<string>())) .Returns(ViewEngineResult.NotFound("some-view", new[] { "view-location1", "view-location2" }))
.Verifiable(); .Verifiable();
viewEngine viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "Components/Invoke/some-view", /*isPartial*/ true)) .Setup(v => v.FindView(It.IsAny<ActionContext>(), "Components/Invoke/some-view", /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound( .Returns(ViewEngineResult.NotFound(
"Components/Invoke/some-view", "Components/Invoke/some-view",
new[] { "view-location1", "view-location2" })) new[] { "view-location3", "view-location4" }))
.Verifiable(); .Verifiable();
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
@ -423,7 +503,10 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify(); 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"); var diagnosticSource = new DiagnosticListener("Microsoft.AspNet");
if (diagnosticListener == null) if (diagnosticListener == null)

View File

@ -76,6 +76,111 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
Assert.Equal(viewName, viewEngineResult.ViewName); 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<IViewEngine>(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<string>()));
var viewResult = new PartialViewResult
{
ViewName = viewName,
ViewEngine = viewEngine.Object,
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = Mock.Of<ITempDataDictionary>(),
};
// 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<IViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true))
.Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty<string>()))
.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<ITempDataDictionary>(),
};
// 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<IViewEngine>(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<ITempDataDictionary>(),
};
// Act
var result = executor.FindView(context, viewResult);
// Assert
Assert.False(result.Success);
Assert.Null(result.View);
Assert.Equal(expectedLocations, result.SearchedLocations);
}
[Fact] [Fact]
public void FindView_WritesDiagnostic_ViewFound() public void FindView_WritesDiagnostic_ViewFound()
{ {

View File

@ -76,6 +76,108 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
Assert.Equal(viewName, viewEngineResult.ViewName); 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<IViewEngine>(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<string>()));
var viewResult = new ViewResult
{
ViewName = viewName,
ViewEngine = viewEngine.Object,
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = Mock.Of<ITempDataDictionary>(),
};
// 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<IViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false))
.Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty<string>()));
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<ITempDataDictionary>(),
};
// 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<IViewEngine>(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<ITempDataDictionary>(),
};
// Act
var result = executor.FindView(context, viewResult);
// Assert
Assert.False(result.Success);
Assert.Null(result.View);
Assert.Equal(expectedLocations, result.SearchedLocations);
}
[Fact] [Fact]
public void FindView_WritesDiagnostic_ViewFound() public void FindView_WritesDiagnostic_ViewFound()
{ {

View File

@ -26,14 +26,51 @@ namespace Microsoft.AspNet.Mvc
public class ViewResultTest public class ViewResultTest
{ {
[Fact] [Fact]
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound() public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound_MessageUsesGetViewLocations()
{ {
// Arrange // Arrange
var expected = string.Join( var expected = string.Join(
Environment.NewLine, Environment.NewLine,
"The view 'MyView' was not found. The following locations were searched:", "The view 'MyView' was not found. The following locations were searched:",
"Location1", "Location1",
"Location2."); "Location2");
var actionContext = GetActionContext();
var viewEngine = new Mock<IViewEngine>(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<ActionContext>(), It.IsAny<string>(), /*isPartial*/ false))
.Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty<string>()))
.Verifiable();
var viewResult = new ViewResult
{
ViewEngine = viewEngine.Object,
ViewName = "MyView",
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = Mock.Of<ITempDataDictionary>(),
};
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => 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(); var actionContext = GetActionContext();
@ -62,6 +99,45 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify(); 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<IViewEngine>(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<ActionContext>(), It.IsAny<string>(), /*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<ITempDataDictionary>(),
};
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => viewResult.ExecuteResultAsync(actionContext));
Assert.Equal(expected, ex.Message);
viewEngine.Verify();
}
[Fact] [Fact]
public async Task ExecuteResultAsync_FindsAndExecutesView() public async Task ExecuteResultAsync_FindsAndExecutesView()
{ {