View component or tag helper?

Suppose you are working on ASP.NET Core web application. To avoid views and layouts grow massive you plan to separate some parts of these to independent components. This way you don’t repeat code you wrote once. You find view components and tag helpers. Which one to use?

What’s the difference between view component and tag helper?

It’s important to understand what is what before doing any work.

  • View components are like partial views with no model binding. View components have backing component class with InvokeAsync() method. It supports dependency injection like controllers do. Similar to controllers they support multiple views.
  • Tag helpers are classes that allow server-side code to participate in rendering of HTML elements. Most famous tag helper is probably anchor tag helper that makes it possible to create MVC links using <a> tag. Tag helpers support dependency injection but there’s no support for external views. All markup is produced in tag helper code.

View component or tag helper?. If it’s something small, related to just few tags and no customizations are needed then it’s tag helper; otherwise it’s view component.

Example: Assembly version tag helper

I lately built simple assembly version tag helper I can use in projects where I have to display web application version in footer of all pages. Implementation is simple – just write out the version and that’s it.

[HtmlTargetElement("AssemblyVersion", TagStructure = TagStructure.NormalOrSelfClosing)]
public class AssemblyVersionTagHelper : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "";
        output.Content.Append(GetType().Assembly.GetName().Version.ToString());
    }
}

There’s no point to go with view component here as there’s no need for views.

Example: Pager view component

Pager view component is different beast and it comes with need for custom markup. Pager may come with default markup but in different projects we may have different markup for pager.

Tag helper doesn’t have support for multiple views and wrapping markup inside C# code would be crazy. Just take a look at the following fragment of view component view and think if you want to implement it pure C# code.

<ul class="pagination">
    <li class="paginate_button page-item first" id="kt_table_1_first">
        <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + "1")">
            <i class="la la-angle-double-left"></i>
        </a>
    </li>
    <li class="paginate_button page-item previous" id="kt_table_1_previous">
        <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + (Model.CurrentPage-1))">
            <i class="la la-angle-left"></i>
        </a>
    </li>
    @for (var i = startIndex; i <= finishIndex; i++)
    {
        @if (i == Model.CurrentPage)
        {
            <li class="paginate_button page-item active">
                <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + i)">@i</a>
            </li>
        }
        else
        {
            <li class="paginate_button page-item ">
                <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + i)">@i</a>
            </li>
        }
    }
    <li class="paginate_button page-item next" id="kt_table_1_next">
        <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + (Math.Min(Model.CurrentPage + 1, Model.PageCount)))">
            <i class="la la-angle-right"></i>
        </a>
    </li>
    <li class="paginate_button page-item last" id="kt_table_1_last">
        <a tabindex="0" class="page-link" aria-controls="kt_table_1" href="@Html.Raw(urlTemplate + Model.PageCount)">
            <i class="la la-angle-double-right"></i>
        </a>
    </li>
</ul>

Of course we can come out with a base tag helper for pager, define protected virtual method to output markup and create custom implementations based on it but it’s against the idea why tag helper was invented. Let’s say it’s anti-pattern.

Here is the example of pager view component that supports custom views.

public class PagerViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PagedResultBase result, string viewName = "Default")
    {
        result.LinkTemplate = Url.Action(RouteData.Values["action"].ToString(), new { page = "{0}" });

        return await Task.FromResult(View(viewName, result));
    }
}

Compared to tag helper pager view component is easier to maintain as view markup and view component code are separated.

Wrapping up

View components and tag helpers are nice features of ASP.NET Core that allow us to encapsulate some UI logic and avoid repeating same code in different views. We can use view components and tag helpers also in shared projects and libraries. Tag helpers are for extending HTML elements with server side logic. View components support custom views like controllers do and they are for cases when component has more markup than just few HTML tags.

Liked this post? Empower your friends by sharing it!

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    2 thoughts on “View component or tag helper?

    • May 19, 2019 at 10:44 pm
      Permalink

      A good summary I think. I’d be interested in another one comparing when you would use a view component versus a partial view. You could fairly easily implement your pager view component above as a partial view as well.

    • May 20, 2019 at 7:37 am
      Permalink

      Partial view is not recommended anymore AFAIK. View components and tag helpers work better with async. Also they don’t run to internal threading issues. I have seen few cases like this with MVC and there was nothing I could do.

    Leave a Reply

    Your email address will not be published. Required fields are marked *