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)
{
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));
}

View File

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

View File

@ -507,7 +507,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
/// <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>
internal static string ViewEngine_ViewNotFound
{
@ -515,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
}
/// <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>
internal static string FormatViewEngine_ViewNotFound(object p0, object p1)
{

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -212,7 +212,7 @@
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
</data>
<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 name="HtmlHelper_TextAreaParameterOutOfRange" xml:space="preserve">
<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.
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<string> 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);

View File

@ -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()
/// <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)
{
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));

View File

@ -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(

View File

@ -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<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)
{
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.
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<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 (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound"))

View File

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

View File

@ -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<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
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<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]
public async Task RenderAsync_ExecutesLayoutPages()
{

View File

@ -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<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();
@ -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<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]
public async Task ExecuteResultAsync_FindsAndExecutesView()
{

View File

@ -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<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
{
public override string ToString()

View File

@ -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<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>();
@ -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<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]
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<IView>();
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
viewEngine
.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();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "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)

View File

@ -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<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]
public void FindView_WritesDiagnostic_ViewFound()
{

View File

@ -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<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]
public void FindView_WritesDiagnostic_ViewFound()
{

View File

@ -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<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();
@ -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<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]
public async Task ExecuteResultAsync_FindsAndExecutesView()
{