In a previous blog post we talked about how to create a simple tag helper in MVC 6. In today's post we take this one step further and create a more complex tag helper that is made up of multiple parts.
A Tag Helper for Bootstrap Modal Dialogs
Creating a modal dialog in bootstrap requires some verbose html.
1 | <div class="modal fade" tabindex="-1" role="dialog"> |
Using a tag helper here would help simplify the markup but this is a little more complicated than the Progress Bag example. In this case, we have HTML content that we want to add in 2 different places: the <div class="modal-body"></div>
element and the <div class="modal-footer"></div>
element.
The solution here wasn't immediately obvious. I had a chance to talk to Taylor Mullen at the MVP Summit ASP.NET Hackathon in November and he pointed me in the right direction. The solution is to use 3 different tag helpers that can communicate with each other through the TagHelperContext
.
Ultimately, we want our tag helper markup to look like this:
1 | <modal title="Modal title"> |
This solution uses 3 tag helpers: modal
, modal-body
and modal-footer
. The contents of the modal-body
tag will be placed inside the <div class="modal-body"></div>
while the contents of the <modal-footer>
tag will be placed inside the <div class="modal-footer"></div>
element. The modal
tag helper is the one that will coordinate all this.
Restricting Parents and Children
First things first, we want to make sure that <modal-body>
and <modal-footer>
can only be placed inside the <modal>
tag and that the <modal>
tag can only contain those 2 tags. To do this, we set the RestrictChildren
attribute on the modal tag helper and the ParentTag
property of the HtmlTargetElement
attribute on the modal body and modal footer tag helpers:
1 | [ ] |
Now if we try to put any other tag in the <modal>
tag, Razor will give me a helpful error message.
Getting contents from the children
The next step is to create a context class that will be used to keep track of the contents of the 2 child tag helpers.
1 | public class ModalContext |
At the beginning of the ProcessAsync method of the Modal tag helper, create a new instance of ModalContext
and add it to the current TagHelperContext
:
1 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) |
Now, in the modal body and modal footer tag helpers we will get the instance of that ModalContext
via the TagHelperContext
. Instead of rendering the output, these child tag helpers will set the the Body
and Footer
properties of the ModalContext
.
1 | [ ] |
Back in the modal tag helper, we call output.GetChildContentAsync()
which will cause the child tag helpers to execute and set the properties on the ModalContext
. After that, we just set the output as we normally would in a tag helper, placing the Body
and Footer
in the appropriate elements.
1 | public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) |
Conclusion
Composing complex tag helpers with parent / child relationships is fairly straight forward. In my opinion, the approach here is much easier to understand than the "multiple transclusion" approach used to solve the same problem in Angular 1. It would be easy to unit test and as always, Visual Studio provides error messages directly in the HTML editor to guide anyone who is using your tag helper.
You can check out the full source code on the Tag Helper Samples repo.