ViewViewComponentResult doesn't handle `~/` in view paths correctly

Fixes #2856
This commit is contained in:
Pranav K 2015-08-12 16:26:18 -07:00
parent 18c3ad25fb
commit 93d07147b2
2 changed files with 46 additions and 11 deletions

View File

@ -5,7 +5,6 @@ using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewComponents;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
@ -18,6 +17,7 @@ namespace Microsoft.AspNet.Mvc
{
// {0} is the component name, {1} is the view name.
private const string ViewPathFormat = "Components/{0}/{1}";
private const string DefaultViewName = "Default";
/// <summary>
/// Gets or sets the view name.
@ -63,9 +63,11 @@ namespace Microsoft.AspNet.Mvc
{
var viewEngine = ViewEngine ?? ResolveViewEngine(context);
var viewData = ViewData ?? context.ViewData;
var isNullOrEmptyViewName = string.IsNullOrEmpty(ViewName);
string qualifiedViewName;
if (ViewName != null && ViewName.Length > 0 && ViewName[0] == '/')
if (!isNullOrEmptyViewName &&
(ViewName[0] == '~' || ViewName[0] == '/'))
{
// View name that was passed in is already a rooted path, the view engine will handle this.
qualifiedViewName = ViewName;
@ -83,11 +85,13 @@ namespace Microsoft.AspNet.Mvc
// Areas/Blog/Views/Shared/Components/Cart/Default.cshtml
//
// This supports a controller or area providing an override for component views.
var viewName = isNullOrEmptyViewName ? DefaultViewName : ViewName;
qualifiedViewName = string.Format(
CultureInfo.InvariantCulture,
ViewPathFormat,
context.ViewComponentDescriptor.ShortName,
ViewName ?? "Default");
viewName);
}
var view = FindView(context.ViewContext, viewEngine, qualifiedViewName);

View File

@ -275,22 +275,53 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(expected, ex.Message);
}
[Fact]
public void Execute_CallsFindPartialView_WithExpectedPath()
[Theory]
[InlineData(null)]
[InlineData("")]
public void Execute_CallsFindPartialView_WithExpectedPath_WhenViewNameIsNullOrEmpty(string viewName)
{
// Arrange
var shortName = "SomeShortName";
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var componentContext = GetViewComponentContext(new Mock<IView>().Object, viewData);
componentContext.ViewComponentDescriptor.ShortName = shortName;
var expectedViewName = $"Components/{shortName}/Default";
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(), expectedViewName))
.Returns(ViewEngineResult.Found(string.Empty, new Mock<IView>().Object))
.Verifiable();
var componentResult = new ViewViewComponentResult();
componentResult.ViewEngine = viewEngine.Object;
componentResult.ViewData = viewData;
componentResult.ViewName = viewName;
// Act & Assert
componentResult.Execute(componentContext);
viewEngine.Verify();
}
[Theory]
[InlineData("~/Home/Index/MyViewComponent1.cshtml")]
[InlineData("~MyViewComponent2.cshtml")]
[InlineData("/MyViewComponent3.cshtml")]
public void Execute_CallsFindPartialView_WithExpectedPath_WhenViewNameIsSpecified(string viewName)
{
// Arrange
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(),
It.Is<string>(view => view.Contains("Components"))))
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(), viewName))
.Returns(ViewEngineResult.Found(string.Empty, new Mock<IView>().Object))
.Verifiable();
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var componentContext = GetViewComponentContext(new Mock<IView>().Object, viewData);
var componentResult = new ViewViewComponentResult();
componentResult.ViewEngine = viewEngine.Object;
componentResult.ViewData = viewData;
var componentResult = new ViewViewComponentResult
{
ViewEngine = viewEngine.Object,
ViewData = viewData,
ViewName = viewName
};
// Act & Assert
componentResult.Execute(componentContext);