ViewComponents with children
Asked Answered
P

4

5

Can I use ViewComponents in a page like this.

<vc:parent-component>
     <some random html>
</parent-component>

when I try this it renders out only parent component. In cshtml of parent-component I am looking for something like below

<h2>Parent component markup</h2>
@RenderChildren()??
<h2>Parent component markup end</h2> 

So result will be

<h2>Parent component markup</h2>
@RenderChildren()
<h2>Parent component markup end</h2>
Perspicacious answered 17/3, 2019 at 11:59 Comment(0)
T
3

You can use tag helpers when you need to pass children by having the following lines in your ProcessAsync function.

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

They do have limitations, because inside tag helpers you don't instantly have access to razor templates, but there is a way to overcome that:

Check this other answer:

How to render a Razor template inside a custom TagHelper in ASP.NET Core?

and this for additional context:

https://github.com/aspnet/Mvc/issues/5504

When you combine these two approaches you can actually have a full server rendered component without using Blazor if you don't need to.

Edit:

Since giving this answer I've come up with a cleaner approach to doing this, you can check out this repository.

Tojo answered 15/2, 2022 at 17:38 Comment(0)
H
3

Carlos solution of using TagHelper is awesome. I simplified his code down to a single class RazorTagHelper, (as I think it is a bit more aligned with the aspnetcore nomenclature.)

Here's the class

    /// <summary>
    /// Implements a tag helper as a Razor view as the template
    /// </summary>
    /// <remarks>
    ///     uses convention that /TagHelpers/ has razor template based views for tags
    ///     For a folder /TagHelpers/Foo
    ///     * FooTagHelper.cs -> Defines the properties with HtmlAttribute on it (derived from ViewTagHelper)
    ///     * default.cshtml -> Defines the template with Model=>FooTagHelper
    /// </remarks>
    public class RazorTagHelper : TagHelper
    {
        private string _viewPath;

        public RazorTagHelper()
        {
            _viewPath = $"~/TagHelpers/{GetType().Namespace.Split('.').Last()}/Default.cshtml";
        }

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

        public TagHelperContent? ChildContent { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            throw new Exception("Use ProcessAsync()");
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            if (ViewContext is null)
            {
                throw new ArgumentNullException(nameof(ViewContext));
            }

            // get child content and capture it in our model so we can insert it in our output
            ChildContent = await output.GetChildContentAsync();

            IHtmlHelper? htmlHelper = ViewContext.HttpContext.RequestServices.GetService<IHtmlHelper>();
            ArgumentNullException.ThrowIfNull(htmlHelper);

            (htmlHelper as IViewContextAware)!.Contextualize(ViewContext);
            var content = await htmlHelper.PartialAsync(_viewPath, this);

            output.TagName = null;
            output.Content.SetHtmlContent(content);
        }
    }

NOTE: I default the view path to a convention which is ~/TagHelpers/TagNameFolder/default.cshtml

A sample TagHelper then looks like this: ~/TagHelpers/Person/PersonTagHelper.cs

namespace Project1.TagHelpers.Person
{
    [HtmlTargetElement("Person")]
    public class PersonTagHelper : ViewTagHelper
    {
        [HtmlAttributeName]
        public string? Name { get; set; } = string.Empty;

        [HtmlAttributeName]
        public string? ImageUrl { get; set; } = string.Empty;
    }
}

and template ~/TagHelpers/Person/default.cshtml

@using Project1.TagHelpers.Person
@model PersonTagHelper
<h1>@Model.Name</h1>
<table>
   <tr>
     <th colspan=2>@Model.Name</th>
   </tr>
  <tr>
    <td><img src="@Model.ImageUrl"/></td>
    <td>@Model.ChildContent</td>
  </tr>
</table>

And of course this creates the Person tag which can be used in templates:

<Person Name="Bob">
    <b>The thing about bob ...</b>
</Person>

Don't forget to include a @addTagHelper directive pointing to the assembly containing your taghelpers in your _ViewImports.cshtml file.

Again, thanks to Carlos and his ServerComponent library, it was very informative.

Hildahildagard answered 11/9, 2022 at 0:0 Comment(0)
G
2

You cannot use random html inside ViewComponent tag helper as you did. May be your are considering ViewComponent tag helper as like as html element. Actually its not html element, rather its a razor code.

According to the Invoking a view component as a Tag Helper documentation, Your ViewComponent tag helper should be like as follows

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>
Ginetteginevra answered 17/3, 2019 at 12:8 Comment(0)
W
0

I have little bit changed and simplified another answer:

public class RandomViewTagHelper : TagHelper
{
    //For my use case I needed a condition based rendering so I used this.
    //If your use case if not like this skip this property and the condition
    //from `processasync` method 
    [HtmlAttributeName]
    public bool ShowChildContent { get; set; }

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

    private DBConnection _connection;

    //Injecting any dependency like db connection if at all needed
    public RandomViewTagHelper(DBConnection connection)
    {
      _connection = connection;
    }

    public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        ArgumentNullException.ThrowIfNull(ViewContext);

        IHtmlHelper htmlHelper = ViewContext.HttpContext.RequestServices.GetService<IHtmlHelper>();
        ArgumentNullException.ThrowIfNull(htmlHelper);

        (htmlHelper as IViewContextAware)!.Contextualize(ViewContext);

        output.TagName = null;
        output.Content.Clear();

        if (ShowChildContent) 
        {
            output.Content.SetHtmlContent(await output.GetChildContentAsync());
        }
    }
}

The actual use case will be:

<random-View show-child-content="@(<Condition-to-Show-or-hide>)">
   <-- Randome HTML -->
   <h1>Hello World</h1>
</random-view>

The above code is implemented with razor pages, ASP.NET Core and C#.

Wilkes answered 13/9, 2023 at 7:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.