Connect the markup to a client side model when aurelia enhancing the server side rendered razor view

This is the third 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.

Our next step in creating this ASP.NET Core MVC/Aurelia hybrid application framework is to take the Aurelia enhancement of the razor views one tiny step further. In the previous post we made it possible to use any built in and custom aurelia attributes and elements within the razor views by “aurelia enhancing” the html we got back from the server. But what we didn’t do in our enhance call is to include a model object that the markup can interact with through Aurelia data and event bindings.

If you want to follow along, here is the starting point. And make sure to check out the prerequisites in the first post to get everything set up correctly.

Let’s start off simple with the following home-about.ts module that we’ll eventually associate with the About.cshtml view:

export function create() {
     return new HomeAboutClientModel();
}

class HomeAboutClientModel {
     showMessage() {
         alert(‘Hello World!’);
     }
}

At the bottom of the file we have a *ClientModel class that contains a single simple method alerting a well known message. An instance of this class is what we’ll give the enhance function.

At the top we export a “create” function that returns this instance. The name “create” will be a convention for this kind of modules, as we now will make the AureliaEnhanceTagHelper depend on it:

private const string AuModuleAttributeName = “th-aurelia-enhance-module”;
[HtmlAttributeName(AuModuleAttributeName)]
public string Module { get; set; }

public override void Process(TagHelperContext context, TagHelperOutput output)
{
     /…/

    if(!String.IsNullOrEmpty(Module))
     {
         output.PostElement.AppendHtml($@”
         <script>
             SystemJS.import(‘app/core/aurelia-enhancer’).then(function(enhancer) {{
                 SystemJS.import(‘{Module}‘).then(function (module) {{
                     var clientModel = module.create();
                     enhancer.enhance(clientModel, document.getElementById(‘{elementId}‘));
                 }});
             }});
         </script>”);
     }
     else
     {
         output.PostElement.AppendHtml($@”
         <script>
             SystemJS.import(‘app/core/aurelia-enhancer’).then(function(enhancer) {{
                 enhancer.enhance({{}}, document.getElementById(‘{elementId}‘));
             }});
         </script>”);
     }
}

Above you can see the vital changes to the AureliaEnhanceTagHelper. We add a second attribute – th-aurelia-enhance-module” and connect it to the string property Module. This attribute/property will be set to the path of the ts module we just created.

In the Process method we check if Module is set and if so emit a slightly different version of the script block. This script block imports both the aurelia-enhancer and the module from the Module property. It calls create on the module and sends the result to the enhance method.

In the aurelia-enhancer module I’ve changed the enhance function to receive a client model and pass it on as the property “bindingContext” of the enhanceInstruction we give the enhance function of Aurelias TemplatingEngine:

export function enhance(clientModel: any, element: HTMLElement): void {
     let enhanceInstruction: EnhanceInstruction = {
         container: aurelia.container,
         resources: aurelia.resources,
         bindingContext: clientModel,
        element: element
     };
     let templatingEngine: TemplatingEngine = aurelia.container.get(TemplatingEngine);
     let view: View = templatingEngine.enhance(enhanceInstruction);
}

Finally we’ll change the attribute of the container div in About.cshtml…

<div th-aurelia-enhance-module=”app/views/home/home-about”>

…and add a section with a button that gets its click event bound to the showMessage method through aurelias click.delegate binding:

<section>
     <button type=”button” click.delegate=”showMessage()”>Show message</button>
</section>

Running the app now and clicking this button will give us the “Hello World!” alert.

See the diff for all changes covered in this blog post here.

With this in place we’re one step closer to start working with some data. That’s my plan for the next post so stay tuned!

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

Update 2017-06-29: In the first version of this blog post I had a mistake in the AureliaEnhanceTagHelper, using the new lambda function syntax in the script block the TagHelper emits to the browser. This does not work in IE11. The fix is to replace “enhancer =>” with “function (enhancer)”  and “module =>”  with “function(module)”. I’ve done so in the code block above and also made a commit fixing this in the current version of AureliaEnhanceTagHelper. (It’s worth pointing out that using the lambda syntax in any .ts file is OK, as the TypeScript will compile to cross browser friendly javascript.)

Leave a Reply

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


CAPTCHA Image
Play CAPTCHA Audio
Reload Image