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

Tag Helpers: Redesign of Tag Helpers content mode #221

Closed
DamianEdwards opened this issue Nov 13, 2014 · 6 comments
Closed

Tag Helpers: Redesign of Tag Helpers content mode #221

DamianEdwards opened this issue Nov 13, 2014 · 6 comments

Comments

@DamianEdwards
Copy link
Member

This redesign unifies the various content modes of Tag Helpers and facilitates new features like conditional execution of Tag Helper contents, e.g. to support the proposed CacheTagHelper aspnet/Mvc#1552.

  1. Get rid of the ContentBehaviorAttribute
  2. Change members of TagHelperOutput:
    1. Add public string PreContent { get; set;}: content to be rendered before child block or Content
    2. Add public string PostContent { get; set;}: content to be rendered after child block or Content
    3. Setting the Content property prevents the calling page from executing the child block and using its contents.
    4. Add public bool ContentSet { get; }: indicates the Content property has been explicitly set and the hosting view/page will not execute the child block and use the resulting value for the element content
    5. Add public Task<string> GetChildBlockContentAsync(): executes the child block and returns the resulting string value
      • This is memoized such that only the first call actually executes the child block, subsequent calls simply return the cached result
    6. Add public void SuppressOutput(): sets the Content and TagName properties to null

This design assumes all child blocks of Tag Helpers are emitted in delegates to support delayed execution. We could potentially introduce a new attribute ModifyContent that could be placed on Tag Helper classes to avoid the delegate creation except for the cases where the Tag Helper explicitly states it wants to modify content by presence of the attribute (by calling TagHelperOutput.GetChildBlockContentAsync) if it's determined that the performance impact of always generating closure delegates is significant.

The GetChildBlockContentAsync method should be protected from calls to Flush and Write in the child block by effectively delaying requested writes and flushes until after the Tag Helper has finished being written to the output. This will avoid inadvertent flushes to the output which would potentially reorder the content from what was intended.

Usage Examples

Prepending Content

Tag Helper class

public class TestTagHelper : TagHelper {
    public void Process(TagHelperContext context, TagHelperOutput output) {
        output.PreContent = "I will appear before the content<br />";
    }
}

Razor file

<test>
    Some content
</test>

HTML output

<test>
    I will appear before the content<br />
    Some content
</test>

Appending Content

Tag Helper class

public class TestTagHelper : TagHelper {
    public void Process(TagHelperContext context, TagHelperOutput output) {
        output.PostContent = "<br />I will appear after the content";
    }
}

Razor file

<test>
    Some content
</test>

HTML output

<test>
    Some content
    <br />I will appear after the content
</test>

Replacing Content

Tag Helper class

public class TestTagHelper : TagHelper {
    public void Process(TagHelperContext context, TagHelperOutput output) {
        output.Content = "I will replace the content";
    }
}

Razor file

<test>
    Some content run at @DateTime.Now
</test>

HTML output

<test>
    I will replace the content
</test>

Modifying Content

Tag Helper class

public class TestTagHelper : TagHelper {
    public async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) {
        var childContent = await output.GetChildBlockContentAsync();
        output.Content = childContent.ToUpperCase();
    }
}

Razor file

<test>
    Some content
</test>

HTML output

<test>
    SOME CONTENT
</test>

Conditionally Suppressing Output

Tag Helper class

public class TestTagHelper : TagHelper {
    public bool Render { get; set; }
    public Task ProcessAsync(TagHelperContext context, TagHelperOutput output) {
        if (!Render) {
            output.SuppressContent();
        }
    }
}

Razor file

<p>Hello</p>
<test render="false">
    Some content
</test>
<p>World</p>

HTML output

<p>Hello</p>
<p>World</p>
@DamianEdwards DamianEdwards added this to the 4.0.0-beta2 milestone Nov 13, 2014
@DamianEdwards DamianEdwards changed the title Tag Helpers need a conditional content mode Redesign of Tag Helpers content mode Nov 14, 2014
@NTaylorMullen NTaylorMullen changed the title Redesign of Tag Helpers content mode Tag Helpers: Redesign of Tag Helpers content mode Nov 14, 2014
@pebezo
Copy link

pebezo commented Nov 20, 2014

If we have PreContent and PostContent we should also have PreTag and PostTag. With this last two we could wrap the initial tag inside other tags (based on some conditions). Imagine being able to spit out some HTML/JS for each tag but only in debug mode (or some other condition).

(Of course you could go all in and have objects similar to web forms controls. My crystal ball says we would eventually have them anyway. And without the state tracking shackles it could stay simple, but powerful and useful at the same time)

@DamianEdwards
Copy link
Member Author

@pebezo you can do that already using the current API and this proposed change by setting TagHelperOutput.TagName to null (which turns off outer tag rendering) and setting Content to whatever HTML you want. Some of the MVC Tag Helpers do this already and they build the HTML with the aid of the TagBuilder class. We're trying very carefully to only introduce as much complexity as needed for Tag Helpers as I'm mindful of what web forms controls became.

@pebezo
Copy link

pebezo commented Nov 20, 2014

Couldn't you use the same technique to work around not having PreContent and PostContent? I can't speak about the implementation complexity, but from a user's point of view I find the Pre/PostTag more useful than Pre/PostContent.

What would be nice is if we could somehow inject code into the controls that are about to render. For example, being able to wrap a div around all tag helpers that render an <input>.

@DamianEdwards
Copy link
Member Author

@pebezo PreContent/PostContent is more inline with the common case for Tag Helpers while optimizing the rendering to avoid unnecessary buffering.

The scenario you talk about (wrapping all <input /> with a <div>) is totally doable, you just have to do that work inside your Process method, it's not as first class. Right now I'm leaning to keeping the API smaller but we can always introduce a change like that later without breaking people.

@NTaylorMullen
Copy link
Member

I'm thinking SupressOutput should also set PreContent/PostContent to null, thoughts?

NTaylorMullen added a commit that referenced this issue Dec 2, 2014
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Dec 2, 2014
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Dec 2, 2014
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Dec 2, 2014
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit that referenced this issue Dec 11, 2014
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Dec 11, 2014
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Dec 11, 2014
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Dec 11, 2014
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit that referenced this issue Dec 16, 2014
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Dec 16, 2014
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Dec 16, 2014
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Dec 16, 2014
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Dec 18, 2014
- React to aspnet/Razor#221
- Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Dec 18, 2014
- React to aspnet/Razor#221
- Modified existing TagHelper tests to no longer rely on ContentBehavior.
- Updated signatures of TagHelperExecutionContext and TagHelperContext pieces.
NTaylorMullen added a commit that referenced this issue Jan 12, 2015
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Jan 12, 2015
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Jan 12, 2015
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Jan 12, 2015
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 13, 2015
- React to aspnet/Razor#221
- Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 13, 2015
- React to aspnet/Razor#221
- Modified existing TagHelper tests to no longer rely on ContentBehavior.
- Updated signatures of TagHelperExecutionContext and TagHelperContext pieces.
NTaylorMullen added a commit that referenced this issue Jan 14, 2015
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Jan 14, 2015
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Jan 14, 2015
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Jan 14, 2015
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 14, 2015
- React to aspnet/Razor#221
- Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 14, 2015
- React to aspnet/Razor#221
- Modified existing TagHelper tests to no longer rely on ContentBehavior.
- Updated signatures of TagHelperExecutionContext and TagHelperContext pieces.
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Removed all tests and instances of ContentBehavior in preparation for moving to a non-ContentBehavior based design.
- Removed ContentBehavior specific CodeGeneration.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
…nt modes.

- Added PreContent, PostContent and ContentSet properties to TagHelperOutput.
- Added GeneratePreContent, GeneratePostContent and SupressOutput methods to TagHelperOutput.
- Added multile ExecuteChildContentAsync and GetChildContentAsync to the rendering phase, ultimately only exposing GetChildContentAsync to a TagHelper author.
- Added more knowledge of StartWritingScope and EndWritingScope to the TagHelper runtime components. This is to enable the runtime components to utilize the RazorPage's infrastructure to render a delegate to a writer and retrieve its value to ultimately expose it to the user.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Added a new internal ctor for TagHelperExecutionContext since it's used in multiple tests to allow for less friction testing.

#221
NTaylorMullen added a commit that referenced this issue Jan 16, 2015
- Modified the CSharpTagHelperCodeRenderer to understand a single line of TagHelper rendering (instead of doing different things based on ContentBehavior).
- Modified existing CodeGen output to reflect new content changes.

#221
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 16, 2015
- React to aspnet/Razor#221
- Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 16, 2015
- React to aspnet/Razor#221
- Modified existing TagHelper tests to no longer rely on ContentBehavior.
- Updated signatures of TagHelperExecutionContext and TagHelperContext pieces.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 17, 2015
- React to aspnet/Razor#221
- Modified existing TagHelpers to no longer rely on ContentBehavior and to instead utilize GetChildContentAsync, PreContent, Content and PostContent.
NTaylorMullen added a commit to aspnet/Mvc that referenced this issue Jan 17, 2015
- React to aspnet/Razor#221
- Modified existing TagHelper tests to no longer rely on ContentBehavior.
- Updated signatures of TagHelperExecutionContext and TagHelperContext pieces.
@NTaylorMullen
Copy link
Member

Razor commits:
a658c13
0eb614b
1ef8c08
c38761f

Mvc commits:
aspnet/Mvc@7b52559
aspnet/Mvc@7b52559

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants