Aurelia, ASP.NET and server-side templates

Aurelia is a great framework for a SPA, but one thing that is missing, I feel, is getting server-generated templates. By default, Aurelia uses the moduleId to fetch a static HTML file.

Take this configuration:

router.configure(config => {
    config.map([
        { route: ['','Welcome'],  moduleId: './welcome' },
        { route: ['About'],  moduleId: './about }
    ]);
});

When you navigate to #About, two calls will be made:

  1. http://www.example.com/about.html
  2. http://www.example.com/about.js

If your view is at some other location, you can customize the ConventionalViewStrategy.convertModuleIdToViewUrl function:

ConventionalViewStrategy.convertModuleIdToViewUrl = function(moduleId){
    return moduleId.replace('view-models', 'views') + '.html';
}

We can use this technique if we want to call an ASP.NET Controller and have it return a view. Why would you want to do this? Translations, performance, templates that differ per user, etc. I don't know what your reason would be, but there are all kinds of web-apps out there. It's unrealistic to think it wouldn't come in handy or be necessary.

Back to the code.

Override the function as you see fit. Do this as part of your bootstrapping. This is how I did it, in my main app.js file:

import {ConventionalViewStrategy} from 'aurelia-framework';

ConventionalViewStrategy.convertModuleIdToViewUrl = function(moduleId){
    if (moduleId === 'modules/invoice-generator') {
         return 'InvoiceGenerator';
    }

    return moduleId + '.html';
};  

export class App {
    configureRouter(config, router) {
        // your router config here
    }
}

So now, if the path is #invoice-generator, I will get the following two requests:

  1. http://www.example.com/invoice-generator.js
  2. http://www.example.com/InvoiceGenerator

On the ASP.NET side, I now want to return the View associated with this InvoiceGeneratorController. The controller is easy enough:

public IActionResult Index()
{
    var viewModel = new InvoiceGeneratorViewModel();
    viewModel.SomeProperty = "SomeValue";
    return View(viewModel);
}

The View is a little bit special though. Notice how we set the Layout property to null:

@{
    Layout = null;
}

<template>
    <h1>Invoice generator</h1>
    @Model.SomeProperty
</template>

Setting the Layout to null is important. This ensures the response of the /InvoiceGenerator call is the HTML template like you see it there, without the _Layout.cshtml wrapped around it. But the @Model.SomeProperty will still be replaced by the value you provided in your Controller.

Forget the Layout = null line, and you will probably get errors like X was detected as System.Register but didn't execute.

The one thing that I don't like is the if-check in the convertModuleIdToViewUrl-function. I find the way Angular handles this, with the templateUrl-property, a cleaner solution. I've created a GitHub issue for this. There should be a viewstrategy property in the setup of your router, but I couldn't get it to work. Rob is looking into it.