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 ForAttributeName = "for";
private const string ModelAttributeName = "model"; private const string ModelAttributeName = "model";
private const string OptionalAttributeName = "optional";
private object _model; private object _model;
private bool _hasModel; private bool _hasModel;
private bool _hasFor; 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> /// <summary>
/// A <see cref="ViewDataDictionary"/> to pass into the partial view. /// A <see cref="ViewDataDictionary"/> to pass into the partial view.
/// </summary> /// </summary>
@ -96,16 +104,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
var model = ResolveModel(); var viewEngineResult = FindView();
var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
{
await RenderPartialViewAsync(writer, model);
// Reset the TagName. We don't want `partial` to render. if (viewEngineResult.Success)
output.TagName = null; {
output.Content.SetHtmlContent(viewBuffer); 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 // Internal for testing
@ -139,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
return ViewContext.ViewData.Model; 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 viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
var getViewLocations = viewEngineResult.SearchedLocations; var getViewLocations = viewEngineResult.SearchedLocations;
@ -148,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false); viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
} }
if (!viewEngineResult.Success) if (!viewEngineResult.Success && !Optional)
{ {
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations); var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
var locations = string.Empty; var locations = string.Empty;
@ -161,7 +174,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Resources.FormatViewEngine_PartialViewNotFound(Name, locations)); 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 // Determine which ViewData we should use to construct a new ViewData
var baseViewData = ViewData ?? ViewContext.ViewData; var baseViewData = ViewData ?? ViewContext.ViewData;
var newViewData = new ViewDataDictionary<object>(baseViewData, model); var newViewData = new ViewDataDictionary<object>(baseViewData, model);

View File

@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
[Fact] [Fact]
public async Task ProcessAsync_Throws_IfGetViewAndFindReturnNotFoundResults() public async Task ProcessAsync_Throws_If_NotOptional_And_GetViewAndFindReturnNotFoundResults()
{ {
// Arrange // Arrange
var bufferScope = new TestViewBufferScope(); var bufferScope = new TestViewBufferScope();
@ -596,6 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Name = partialName, Name = partialName,
ViewContext = viewContext, ViewContext = viewContext,
ViewData = viewData, ViewData = viewData,
Optional = false
}; };
var tagHelperContext = GetTagHelperContext(); var tagHelperContext = GetTagHelperContext();
var output = GetTagHelperOutput(); var output = GetTagHelperOutput();
@ -605,6 +606,47 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
() => tagHelper.ProcessAsync(tagHelperContext, output)); () => tagHelper.ProcessAsync(tagHelperContext, output));
Assert.Equal(expected, exception.Message); 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() private static ViewContext GetViewContext()
{ {