3

In tag helpers, we can access the tags inner HTML.

<!-- Index.cshtml -->  
<my-first>
    This is the original Inner HTML from the *.cshtml file.
</my-first>

// MyFirstTagHelper.cs > ProcessAsync
var childContent = await output.GetChildContentAsync();
var innerHtml = childContent.GetContent();

If we invoke a view component as a tag helper, how can we access the inner HTML?

<vc:my-first>
    This is the original Inner HTML from the *.cshtml file.
</vc:my-first>

// MyFirstViewComponent.cs > InvokeAsync()
var innerHtml = DoMagic();

A few further thoughts:

I appreciate that we can pass arbitrary content to a view component via HTML attributes. That can become impractical, though, in the case of very large amounts of text, in which case inner HTML would be more readable and have better tooling support.

Some might also say, "Why not just use a tag helper, then?" Well, we want to associate a view with the tag helper, and tag helpers do not support views; instead, tag helpers require us to build all the HTML programmatically.

So we're stuck in a bit of bind. On the one hand, we want a tag helper, but they don't support views; on the other hand, we can use a view component as a tag helper, but view components might not let us access the inner HTML.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467

2 Answers2

2

Sorry, but the answer is "Just use a tag helper"

See the following issue: https://github.com/aspnet/Mvc/issues/5465

Being able to invoke a ViewComponent from a tag helper is just a different way to call it instead of using @Component.Invoke().

I can see where this is misleading, and I do recall seeing in the ASP.NET Core 2.1 tutorials a statement to the effect of "View Components are like Tag Helpers, but more powerful."

Adam Vincent
  • 3,281
  • 14
  • 38
  • 3
    The crux for me is that we cannot have our cake and eat it too. If we use a tag helper, then we cannot have a razor view. If we use a view component, then we cannot access the inner HTML. Bummer. – Shaun Luttin Jun 17 '18 at 15:30
  • All above considered, I think the next question concerning your issue would be, why do you need to access the inner html of the view component? Is there another way to process what you need to do with the inner html and send it to the view component, perhaps the `ViewComponent.Content(string)` method? This stuff is pretty new to me. – Adam Vincent Jun 17 '18 at 18:47
  • 2
    I need to access the inner HTML because I am building a code sample tag. Ideally, it will look something like this: `var foo = "bar";`. I know of no other way to process the code block and send it to the view component. – Shaun Luttin Jun 18 '18 at 01:02
  • And you can't simply pass in the `var foo = "bar";` as another parameter? e;g; `@{var payload = var foo = "bar;";}` -- `` ? – Adam Vincent Jun 18 '18 at 11:13
  • I meant ``. For some reason I can't edit my comment to fix it. – Adam Vincent Jun 18 '18 at 12:14
  • 1
    We could simply pass the code sample as another parameter, but it becomes quite a bit harder to read the mark-up with large code samples. E.g. https://gist.github.com/shaunluttin/e517f08f68510642079034d64def7afb – Shaun Luttin Jun 18 '18 at 15:56
  • 1
    I figured as much. Have you considered the possibility of incorporating one of the Javascript frameworks? ReactJS/KnockoutJS/VueJS/AngularJS (Not Angular) might suit this use case – Adam Vincent Jun 18 '18 at 21:31
  • But, you can't use another `TagHelper` inside a `TagHelper` easily - but you can use a `TagHelper` inside a Razor view easily. – Ted Nyberg Mar 22 '23 at 16:06
0

Finally, a way to have our cake and eat it too! Allow a tag-helper to use a Razor view as its source of HTML, yet still wrap markup when used in a Razor page.

Use a tag-helper to get the inner HTML as a string. Then directly operate the Razor view engine to render a partial view to a string. Finally, use string replacement to place the inner HTML string into the right place in the partial view string.

The key is to use the high-quality StackOverflow answers available on rendering a Razor view as a string. See the IRazorViewToStringRenderer service here (it says ASP.NET Core 3.1 but worked for me in 2.2), or elsewhere as Mvc.RenderViewToString.

The tag-helper:

// RazorViewTagHelper.cs
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace GetSafetyCone.TagHelpers
{
    public class RazorViewTagHelper : TagHelper
    {
        private IRazorViewToStringRenderer RazorViewToStringRenderer { get; }
        
        public ViewName { get; set; }


        public RazorViewedTagHelperBase(
            IRazorViewToStringRenderer razorViewToStringRenderer)
        {
            this.RazorViewToStringRenderer = razorViewToStringRenderer;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var childContent = await output.GetChildContentAsync();

            var childContentString = childContent.GetContent();
        
            var viewHtmlTemplateString = await this.RazorViewToStringRenderer.Render<object>(this.ViewName, null);
            
            var viewHtmlString = viewHtmlTemplateString.Replace("BODY_GOES_HERE", childContentString);

            output.Content.SetHtmlContent(viewHtmlString); // Set the content.
        }
    }
}

The partial view you want to use as the source of HTML for the tag-helper:

// ViewXYZ.cshtml
@*
    ViewXYZ to be rendered to string.
*@
// No model specified, so ok model was a null object.
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
    <div class="max-w-3xl mx-auto">
        BODY_GOES_HERE
    </div>
</div>

And here's the tag-helper in use:

// YourPage.cshtml
<razor-view view-name="ViewXYZ">
    <p>You should see this content wrapped in the ViewXYZ divs.</p>
</razor-view>

If you want, you can simplify the string replacement and use the childContent TagHelperContent directly as the model of the view:

// RazorViewTagHelper.cs
...
var childContent = await output.GetChildContentAsync();

var viewHtmlString = await this.RazorViewToStringRenderer.Render("viewName", childContent);
...

// ViewXYZ.cshtml
...
@model Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent;
...
<div class="max-w-3xl mx-auto">
    @(this.Model)
</div>
...
David C.
  • 1
  • 2