Creating the AureliaBindTagHelper to render Aurelia bindings for ASP.NET MVC model members

This is the sixth blog post in a series of posts that covers how I’ve set up an ASP.NET Core MVC project with Aurelia in a way that will allow me to render razor views and make use of MVC features such as model binding and form validation while at the same time using Aurelia to give the UI that SPA touch that modern web applications of today so often require. The goal is a setup where most experienced ASP.NET MVC Developers can go on and develop their views sort of as always, while the designated front end developer (=me) can leverage Aurelia to enhance the user experience with all sorts of client side features. All the code can be found in this Github repo.

I’m back again after a period too busy for blogging. In this post we’ll create a TagHelper that will enable us to refactor the client side bindings from the previous post from

<input asp-for=FirstName class=“form-control” value.bind=“data.firstName” />

into this:

<input asp-for=FirstName class=“form-control” th-au-bind-value=FirstName />

The TagHelper will be designed in a way so that the value of the attribute actually will be a “ModelExpression”, meaning that Visual Studio/Razor will understand that FirstName is a reference to the FirstName property of the model in the razor view and will complain if you mistype it.

Naming conventions

First let’s talk a little about the naming conventions for my TagHelper attributes. As this architecture will include both TagHelpers as well as Aurelia custom elements and attributes I thought it would be desirable that the markup reveals what the custom “thing is” – an MVC TagHelper or an Aurelia component? For this demoapp I’m using the au- prefix for any Aurelia compontents and the th- prefix for any TagHelper components. In addition to this I’ll use the th-au-prefix for any TagHelper attributes that in some way will result in an Aurelia native attribute.

For a real world app this might be too general, there is a small risk some external library you’d like to use actually would use th- for its collection of TagHelpers, so as with namespacing I would recommend to combine this prefix with some app or company specific acronym. At work I use vkau- and vkth- the same way, as the company name is “Vklass”. (If you do find a library that uses the th- prefix for TagHelpers, it means they’re doing it wrong too, using a too general prefix. :-D)

Aurelia bindings recap

Before we continue let’s remind ourselves about some more advanced features of the aurelia bindings, namely the use of value converters and binding behaviors. This means that the native aurelia binding could look like this:

<input /…/ value.bind=“data.firstName | capitalize & throttle” />

Please bear with me as I’m using some contrived examples – capitalization would probably be done with css. In addition it could actually even look like this:

<input /…/ value.bind=“data.firstName | conv1 | conv2 & beh1 & beh2” />

AureliaBindTagHelper specs

Here is the starting point for this blog post. After the previous post I did a couple of bug fix commits (related posts are updated) as well as a commit where all Nuget packages were updated.

Since we want the value of the TagHelper bind attribute to be an MVC ModelExpression, we’ll need to design the TagHelper so that the native aurelia expression can be split up into multiple attributes:

<input /…/ th-au-bind-value=“FirstName
   
th-au-value-converters-value=“conv1 | conv2
    th-au-binding-behaviors-value=beh1 & beh2
 />

The TagHelper is responsible for adding the inital | character for value converters, and the initial & character for binding behaviors, so when only using one of each only the name is needed. But when multiple are needed the consumer can just use the Aurelia syntax. For sending arguments to a converter/behavior you can also just follow the Aurelia docs. The TagHelper will just do plain string concatenation to produce the final complete aurelia bind expression.

The TagHelper will default to use “.bind” as binding type, defaulting by tag type if the binding is one-way or two-way. If you want to control that, you can append the binding type to the th-au-bind-* TagHelper attribute, ie “th-au-bind-value.one-time”.

The TagHelper is NOT locked down to only binding to the value attribute, as a matter of fact the TagHelper uses a dictionary feature and any attribute name can be appended at the end of the attributes;

<input /…/ th-au-bind-value=“FirstName
   
th-au-value-converters-value=“conv1 | conv2
    th-au-binding-behaviors-value=beh1 & beh2

    th-au-bind-title.one-time=“FirstNameTooltip
   
th-au-value-converters-title=“conv1 | conv2
    th-au-binding-behaviors-title=beh1 & beh2
/>

This is also great when you start creating your own aurelia custom elements/attributes and want to connect some attribute to a value in the model, just use the TagHelper, and append your attribute name to the end of the TagHelper attributes. For example, if you’ve created an “expansion-panel” component in Aurelia that as a boolean @bindable named expanded you could do the following, assuming your model has a “ProvideDetails” boolean flag;

<au-expansion-panel th-au-bind-expanded=ProvideDetails>

    @*Content goes here*@

</au-expansion-panel>

Here you can see the benefit of the naming conventions I talked about earlier, and also the power of mixing Aurelia with MVC TagHelpers. This markup fully reveals that “au-expansion-panel” is an Aurelia custom element, while the th-au-bind-expanded attribute is a TagHelper (that will output expanded.bind=”data.provideDetails”).

The full code for the AureliaBindTagHelper can be found here. And here is the full commit for all changes this blog post resulted in.

Now, when changing the markup in Person.cshtml I get intellisense for the model when using the TagHelper attribute:

image

If you liked this post, please click thumbs up and don’t hesitate to make any comments, questions or other remarks.

Leave a Reply

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


CAPTCHA Image
Play CAPTCHA Audio
Reload Image