Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Add a <partial /> tag helper
Browse files Browse the repository at this point in the history
Fixes #5916
  • Loading branch information
pranavkm committed Nov 29, 2017
1 parent 008a562 commit 6ed589f
Show file tree
Hide file tree
Showing 7 changed files with 608 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
/// <summary>
/// Renders a partial view.
/// </summary>
[HtmlTargetElement("partial", Attributes = "name", TagStructure = TagStructure.WithoutEndTag)]
public class PartialTagHelper : TagHelper
{
private readonly ICompositeViewEngine _viewEngine;
private readonly IViewBufferScope _viewBufferScope;

public PartialTagHelper(
ICompositeViewEngine viewEngine,
IViewBufferScope viewBufferScope)
{
_viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine));
_viewBufferScope = viewBufferScope ?? throw new ArgumentNullException(nameof(viewBufferScope));
}

/// <summary>
/// The name of the partial view used to create the HTML markup.
/// </summary>
public string Name { get; set; }

/// <summary>
/// A model to pass into the partial view.
/// </summary>
public object Model { get; set; }

/// <summary>
/// A <see cref="ViewDataDictionary"/> to pass into the partial view.
/// </summary>
public ViewDataDictionary ViewData { get; set; }

[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }

/// <inheritdoc />
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

if (output == null)
{
throw new ArgumentNullException(nameof(context));
}

var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize);
using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8))
{
await RenderPartialViewAsync(writer);

// Reset the TagName. We don't want `partial` to render.
output.TagName = null;
output.Content.SetHtmlContent(viewBuffer);
}
}

private async Task RenderPartialViewAsync(TextWriter writer)
{
var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false);
var getViewLocations = viewEngineResult.SearchedLocations;
if (!viewEngineResult.Success)
{
viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false);
}

if (!viewEngineResult.Success)
{
var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations);
var locations = string.Empty;
if (searchedLocations.Any())
{
locations += Environment.NewLine + string.Join(Environment.NewLine, searchedLocations);
}

throw new InvalidOperationException(
Resources.FormatViewEngine_PartialViewNotFound(Name, locations));
}

var view = viewEngineResult.View;
using (view as IDisposable)
{
// Determine which ViewData we should use to construct a new ViewData
var baseViewData = ViewData ?? ViewContext.ViewData;
var model = Model ?? ViewContext.ViewData.Model;

var newViewData = new ViewDataDictionary<object>(baseViewData, model);
var partialViewContext = new ViewContext(ViewContext, view, newViewData, writer);

await view.RenderAsync(partialViewContext);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,7 @@
<data name="ArgumentCannotContainHtmlSpace" xml:space="preserve">
<value>Value cannot contain HTML space characters.</value>
</data>
<data name="ViewEngine_PartialViewNotFound" xml:space="preserve">
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<form action="/HtmlGeneration_Product" method="post">
<div>
<label class="product" for="z0__product_HomePage">HomePage</label>
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z0__product_HomePage" name="[0].product.HomePage" value="http://www.contoso.com/" />
</div>

<div>
<label class="product" for="z0__Number">Number</label>
<input type="number" data-val="true" data-val-required="The Number field is required." id="z0__Number" name="[0].Number" value="0" />
</div>
<div>
<label class="product" for="z0__ProductName">ProductName</label>
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z0__ProductName" name="[0].ProductName" value="Product_0" />
</div>
<div>
<label class="product" for="z0__Description">Description</label>
<textarea rows="4" cols="50" class="product" id="z0__Description" name="[0].Description">
</textarea>
</div>
<div>
<label class="product" for="z1__product_HomePage">HomePage</label>
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z1__product_HomePage" name="[1].product.HomePage" value="" />
</div>

<div>
<label class="product" for="z1__Number">Number</label>
<input type="number" data-val="true" data-val-required="The Number field is required." id="z1__Number" name="[1].Number" value="1" />
</div>
<div>
<label class="product" for="z1__ProductName">ProductName</label>
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z1__ProductName" name="[1].ProductName" value="Product_1" />
</div>
<div>
<label class="product" for="z1__Description">Description</label>
<textarea rows="4" cols="50" class="product" id="z1__Description" name="[1].Description">
</textarea>
</div>
<div>
<label class="product" for="z2__product_HomePage">HomePage</label>
<input type="url" size="50" disabled="disabled" readonly="readonly" id="z2__product_HomePage" name="[2].product.HomePage" value="" />
</div>

<div>
<label class="product" for="z2__Number">Number</label>
<input type="number" data-val="true" data-val-required="The Number field is required." id="z2__Number" name="[2].Number" value="2" />
</div>
<div>
<label class="product" for="z2__ProductName">ProductName</label>
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z2__ProductName" name="[2].ProductName" value="Product_2" />
</div>
<div>
<label class="product" for="z2__Description">Description</label>
<textarea rows="4" cols="50" class="product" id="z2__Description" name="[2].Description">
Product_2 description</textarea>
</div>
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden" value="{0}" /></form>
</body>
</html>
Loading

0 comments on commit 6ed589f

Please sign in to comment.