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

Add a <partial /> tag helper #7089

Closed
wants to merge 2 commits into from
Closed

Conversation

pranavkm
Copy link
Contributor

Fixes #5916

@pranavkm pranavkm requested a review from dougbu November 29, 2017 01:55
Copy link
Contributor

@ajaybhargavb ajaybhargavb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

private readonly IViewBufferScope _viewBufferScope;

public PartialTagHelper(
IHtmlGenerator htmlGenerator,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could just put it on one line.

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

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inheritdoc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null-check both parameters.

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

await viewEngineResult.View.RenderAsync(partialViewContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await view.RenderAsync(...)

/// <summary>
/// Renders HTML markup for the specified partial view.
/// </summary>
/// <param name="partialViewName">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong order. Should be below ViewContext.

/// Renders HTML markup for the specified partial view.
/// </summary>
/// <param name="partialViewName">
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably remove Must not be null here or add it to both ViewContext and writer because we null check those.

.Verifiable();
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound("test-view", new[] { "location3", "location4" }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we have a test somewhere else where GetView fails but FindView succeeds? If not, it might be worth adding one.

}

/// <summary>
/// The name of the partial view used to create the HTML markup. Must not be <c>null</c>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this property must not be null, why doesn't the setter throw an ArgumentNullException?

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

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null-check both parameters.

{
"TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator : Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator",
"MemberId": "public .ctor(Microsoft.AspNetCore.Antiforgery.IAntiforgery antiforgery, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Mvc.MvcViewOptions> optionsAccessor, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider metadataProvider, Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory urlHelperFactory, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, Microsoft.AspNetCore.Mvc.ViewFeatures.ValidationHtmlAttributeProvider validationAttributeProvider)",
"Kind": "Removal"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make this breaking change? Can add a new constructor and deprecate the old.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah no breaking changes please.

{
"TypeId": "public interface Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator",
"MemberId": "System.Threading.Tasks.Task RenderPartialViewAsync(Microsoft.AspNetCore.Mvc.Rendering.ViewContext viewContext, System.String partialViewName, System.Object model, Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary viewData, System.IO.TextWriter writer)",
"Kind": "Addition"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding something to an interface seems like an impossible change to make before 3.0. What makes this 🆗?

{
throw new ArgumentNullException(nameof(antiforgery));
}

if (optionsAccessor == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewriting these null checks seems unnecessary. But, being inconsistent about it is Just Wrong:tm:

Also, did we change our mind about throw expressions? They were against our unofficial guidelines at one point.

Copy link
Contributor

@NTaylorMullen NTaylorMullen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also remove breaking changes.

ViewData,
writer);

output.SuppressOutput();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will nuke any other content added by other TagHelpers. If someone wants to add anything to the partial TagHelper i'd say let them. Instead of suppressing output just set the TagName to null to work nicely with anyone else.

public ViewDataDictionary ViewData { get; set; }

[ViewContext]
public ViewContext ViewContext { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HtmlAttributeNotBound]

@pranavkm pranavkm force-pushed the prkrishn/partial-tag-helper branch from 3002f4f to 092c3db Compare November 29, 2017 20:16
Copy link
Contributor Author

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆙 📅

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this before. Why isn't this

[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with @DamianEdwards and he doesn't think this should be a ModelExpression. He was fine leaving this property as is.

// Determine which ViewData we should use to construct a new ViewData
var baseViewData = ViewData ?? ViewContext.ViewData;

var newViewData = new ViewDataDictionary<object>(baseViewData, Model);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overloads of the PartialAsync(...) and RenderPartialAsync(...) helpers default the model they use to htmlHelper.ViewData.Model. This instead defaults to null, which is unexpected. Should do something similar to the HTML helpers here.

Always use ViewContext.ViewData.Model since setting the ViewData property should not change the Model property's default.

var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope)
{
Name = partialName,
Model = model,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests where Model (now For) isn't set or is explicitly set to null but a view is found.

foreach (var product in Model)
{
@* Update HtmlFieldPrefix so generated for, id and name attribute values are correct. *@
ViewData.TemplateInfo.HtmlFieldPrefix = fieldPrefix + string.Format("[{0}]", index++);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't be necessary when using asp-for="[index++]".

}
}

private async Task RenderPartialViewAsync(TextWriter writer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion: Could make this tag helper an HtmlHelper subclass which implements ITagHelper. Would have to make ViewContext a new property that calls Contextualize(...) in its setter but that should be the primary wrinkle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot the punchline: Then could use RenderPartialCoreAsync(...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample @DamianEdwards has essentially DIs a IHtmlHelper, Contextualizes it and then renders the view using it. That kinda felt odd since we don't have any other use cases of doing this in any other tag helper. Given we already have a similarish copy of the code in PartialViewExecutor, I didn't think it was terrible to have yet another copy of it here.

@pranavkm pranavkm force-pushed the prkrishn/partial-tag-helper branch from 092c3db to c6ea066 Compare November 30, 2017 17:20
@pranavkm
Copy link
Contributor Author

🆙 📅

@pranavkm pranavkm force-pushed the prkrishn/partial-tag-helper branch from c6ea066 to e5110fe Compare November 30, 2017 17:22
}

/// <summary>
/// The name of the partial view used to create the HTML markup.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this do just view "names" (e.g. _LoginPartial) or does it also do paths? Do we need to be clear about this in the docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any tests using a path, but would it work? And if paths aren't supported:

  1. Should they be?
  2. The docs need to be clearer about what a "partial view name" is (perhaps by giving an example)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like these properties are handled identically to e.g. viewName parameters in Controller.View(...) methods. Those are generally documented as The name of the view that is rendered to the response.. Same for the corresponding result properties e.g. ViewResult.ViewName.

If we need more documentation about what names, paths, et cetera mean, suggest we handle that in a separate issue. For now, let's be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does path - https://github.com/aspnet/Mvc/pull/7089/files#diff-883159b6acb104f0fb64afa28c27a9ccR2 . The name I think comes from the IHtmlHelper docs. I'd be happy to update it if you have suggestions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and "partial view name" doesn't appear anywhere. Isn't "name of the partial view" clear enough (assuming we stick with just "name" for now)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, let's be consistent.

I'd rather have correct information than have consistent-but-useless information

Oh, and "partial view name" doesn't appear anywhere.

These mean the same thing so I don't think this is critical.

...

So, let's come up with some good wording here and then apply elsewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How 'bout "The name or path of the [partial] view that is rendered to the response." We do lots of shenanigans under the covers handling absolute and relative paths as well as distinguishing paths from names. But, describing exactly how we choose to interpret "name" versus "path" doesn't help much -- especially because it's specific to the RazorViewEngine.

I'm not positive what extra words we need to inform readers what distinguishes a "partial" view. It's a view found with isMainPage: false but the interfaces say only that isMainPage "Determines if the page being found is the main page for an action.". The details are again specific to the IViewEngine implementation. (For RazorViewEngine, this parameter mainly controls whether or not _ViewStart.cshtml files are used.)

Copy link
Member

@dougbu dougbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but get final word from @Eilon on the doc comments.

if (For?.Name != null)
{
newViewData.TemplateInfo.HtmlFieldPrefix = newViewData.TemplateInfo.GetFullHtmlFieldName(For.Name);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move everything except var partialViewContext = ...; outside the using. Up to you.

@pranavkm pranavkm closed this Dec 1, 2017
@pranavkm pranavkm deleted the prkrishn/partial-tag-helper branch December 1, 2017 01:10
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants