Add optional property to PartialTagHelper (#7991)

* Add optional to PartialTagHelper

Addresses #7268
This commit is contained in:
Alexej Timonin 2018-06-30 01:02:38 +02:00 committed by Pranav K
parent f6befb9ed3
commit c2fcfabdf3
2 changed files with 71 additions and 12 deletions

View File

@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
private const string ForAttributeName = "for";
private const string ModelAttributeName = "model";
private const string OptionalAttributeName = "optional";
private object _model;
private bool _hasModel;
private bool _hasFor;
@ -74,6 +75,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
}
/// <summary>
/// When optional, executing the tag helper will no-op if the view cannot be located.
/// Otherwise will throw stating the view could not be found.
/// </summary>
[HtmlAttributeName(OptionalAttributeName)]
public bool Optional { get; set; }
/// <summary>
/// A <see cref="ViewDataDictionary"/> to pass into the partial view.
/// </summary>
@ -96,16 +104,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
throw new ArgumentNullException(nameof(context));
}
var model = ResolveModel();
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
{
await RenderPartialViewAsync(writer, model);
var viewEngineResult = FindView();
// Reset the TagName. We don't want `partial` to render.
output.TagName = null;
output.Content.SetHtmlContent(viewBuffer);
if (viewEngineResult.Success)
{
var model = ResolveModel();
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
{
await RenderPartialViewAsync(writer, model, viewEngineResult.View);
output.Content.SetHtmlContent(viewBuffer);
}
}
// Reset the TagName. We don't want `partial` to render.
output.TagName = null;
}
// Internal for testing
@ -139,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
return ViewContext.ViewData.Model;
}
private async Task RenderPartialViewAsync(TextWriter writer, object model)
private ViewEngineResult FindView()
{
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
var getViewLocations = viewEngineResult.SearchedLocations;
@ -148,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
}
if (!viewEngineResult.Success)
if (!viewEngineResult.Success && !Optional)
{
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
var locations = string.Empty;
@ -161,7 +174,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Resources.FormatViewEngine_PartialViewNotFound(Name, locations));
}
var view = viewEngineResult.View;
return viewEngineResult;
}
private async Task RenderPartialViewAsync(TextWriter writer, object model, IView view)
{
// Determine which ViewData we should use to construct a new ViewData
var baseViewData = ViewData ?? ViewContext.ViewData;
var newViewData = new ViewDataDictionary<object>(baseViewData, model);

View File

@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
[Fact]
public async Task ProcessAsync_Throws_IfGetViewAndFindReturnNotFoundResults()
public async Task ProcessAsync_Throws_If_NotOptional_And_GetViewAndFindReturnNotFoundResults()
{
// Arrange
var bufferScope = new TestViewBufferScope();
@ -596,6 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Name = partialName,
ViewContext = viewContext,
ViewData = viewData,
Optional = false
};
var tagHelperContext = GetTagHelperContext();
var output = GetTagHelperOutput();
@ -605,6 +606,47 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
() => tagHelper.ProcessAsync(tagHelperContext, output));
Assert.Equal(expected, exception.Message);
}
[Fact]
public async Task ProcessAsync_IfOptional_And_ViewIsNotFound_WillNotRenderAnything()
{
// Arrange
var expected = string.Empty;
var bufferScope = new TestViewBufferScope();
var partialName = "_ThisViewDoesNotExists";
var model = new object();
var viewContext = GetViewContext();
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) =>
{
v.Writer.Write(expected);
})
.Returns(Task.CompletedTask);
var viewEngine = new Mock<ICompositeViewEngine>();
viewEngine.Setup(v => v.GetView(It.IsAny<string>(), partialName, false))
.Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty<string>()));
viewEngine.Setup(v => v.FindView(viewContext, partialName, false))
.Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty<string>()));
var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
{
Name = partialName,
ViewContext = viewContext,
Optional = true
};
var tagHelperContext = GetTagHelperContext();
var output = GetTagHelperOutput();
// Act
await tagHelper.ProcessAsync(tagHelperContext, output);
// Assert
var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder());
Assert.Empty(content);
}
private static ViewContext GetViewContext()
{