Converting the ASP.NET Core MVC template to an Aurelia SPA – keeping the Razor views

This is the first blog post in a series of posts that covers how I’ve set up an ASP.NET MVC Core 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.

Prerequisites

All code I cover in this blog post is available in my asp-net-core-mvc-with-aurelia Github repository. In this blog post I will not cover the initial commits to that repository. These steps/commits include:

  • Create an ASP.NET Core MVC Application without authentication (for demo purposes, so will not bother with authentication) in Visual Studio 2017.
  • Removed bower and moved around the files under wwwroot/lib a bit.
  • Moved the included css in site.css to the new sass file ~/app/layout.scss.
  • Deactivated automatic Typescript build.
  • Installed Node.js and npm.
  • Global npm installs of jspm and typings.
  • npm installs of jspm, Aurelia (step 3 and 5 of this blog post).
    NOTE: When I got started with Aurelia I missed out on the fact that the Aurelia CLI actually has an ASP.NET Core path to get started with. If you’re starting a project now you could definitely go that route and still benefit from my posts. After creating your project through Visual Studio and the Aurelia CLI you just need to align your project to this commit, when it comes to the mvc stuff such as Controllers, Views etc. My suggestion would be to download that commit and do a folder compare with WinMerge and copy over the files needed.
  • npm install of gulp and packages needed for the following basic task setup:
    • A task for compiling all *.ts files in ~/app/ and output to ~/wwwroot/app/.
    • A task for compiling all *.scss files in ~/app/ and bundle them into a single *.css file at ~/wwwroot/dist/.
    • A task for copying all *.html files from ~/app/ to ~/wwwroot/app/.
    • A watcher that does all of the above as soon as any file of that type is saved.
    • A projectopen task that will run npm install, jspm install, typings install and start the watcher. This task will run automatically if you open the solution in VS 2017, so make sure you’ve made the global installs of  npm, jspm, typings and gulp first.
    • A ‘clean’ task that removes the compiled/copied files in ~/wwwroot/app and ~/wwwroot/typings/. Run this manually if anything gets messed up. 🙂
    • A debug task that runs the clean task and then all the “compilation” tasks.

If you run the app at this point you should still get the familiar asp.net mvc template website that has three pages; Home, Contact and About. To follow a long below, clone/download my code at the starting point SP001 tag (and make sure you’ve done the npm installs above before opening the project in VS 2017).

Before reading further you should have some basic knowledge about authoring Aurelia applications. I will not comment all the Aurelia stuff I’m taking advantage of here, as it is so well documented over here.

Rearrange _Layout.cshtml and Home/Index.cshtml to create the entry point for the Aurelia application

The first step we need to take is to move the contents of _Layout.cshtml to Home/Index.cshtml – this view file will be the entry point for the SPA application.

This commit covers

  • copying Home/Index.cshtml to a new  Home/Start.cshtml
  • Adding a Start action to the HomeController.
  • Copying the contents of _Layout.cshtml to Home/Index.cshtml
  • Replacing @RenderBody with “<div>TODO: Load content here</div>”, and remove the @RenderSection call.
  • Replaced all asp-action=”Index” with asp-action=”Start”.
  • At the top of our new Index.cshtml, add @{ Layout = null; } so that the layout page is not invoked for this view.

I will just leave the @RenderBody() call in _Layout.cshtml for now, as we will need to use this file again down the lane.

If you run the app now you will just get the master layout and  if you click one of the links in the top menu you will come to that page without any css or other resources loaded. Especially note the broken rendering of the Home/Start action as the contents of this view relies heavily on css and js resources that are not currently loaded. Let’s fix that.

Add Aurelia to the interface

In the Index.cshtml file, at the start of the body element, let’s add the aurelia-app root element:

<div aurelia-app=”app/main”></div>

 

And right at the end of the body, let’s add the init scripts:

<script src=”/jspm_packages/system.js”></script>
<script src=”/config.js”></script>
<script>
    SystemJS.import(‘aurelia-bootstrapper’);
</script>

 

After that we need to create the main entry file. I’ll be using TypeScript so let’s add a main.ts file in the /app/ folder. This file contains the following:

import { Aurelia } from ‘aurelia-framework’;

export function configure(aurelia: Aurelia) {
    aurelia.use
        .standardConfiguration()
        .developmentLogging();

    aurelia.start().then(() => aurelia.setRoot());
}

 

In addition to this, we will need an “app” module that will be the initial view to present. This consists of an .html file and a .ts file:

<template>
    <div textcontent.bind=”message”></div>
</template>
export class App {
    message: string = ‘Hello World!’;
}

 

So when we run the app now we will get the following output:

image

Notice that the value from the typescript class member “message” is bound to the markup through the Aurelia textcontent.bind attribute.

The next step is to move all the layout markup (one “nav” and one “div” element) from Index.cshtml to app.html. In this move I’ll also replace the “TODO text” with the “textcontent.bind” element I created initially in app.html. And after this we get:

image

The links in the top navigation still have the asp- TagHelper attributes that will obviously not work when residing in an .html file. That’s what we’ll take on next.

Set up a route rule and Aurelia route component that loads content through ASP.NET MVC

In this step we’ll change the markup for each navigation link from this:

<a asp-area=”” asp-controller=”Home” asp-action=”Start”>Home</a>

to this:

<a route-href=”route: MvcRoute; params.bind:
       { mvcController: ‘Home’, mvcAction: ‘Start’ }”>Home</a>   

 

The “route-href” is an Aurelia built in attribute that will invoke a configured route and set the actual href attribute. It will also capture the navigation to invoke the client side route-handler instead of actually doing the normal HTTP Get navigation.

We will create a route-rule called MvcRoute that will take care of these links, load the given mvc action and append the received html to the DOM.

The route configuration takes place in the configureRouter function in the app.ts module:

configureRouter(config: RouterConfiguration, router: Router) {
    config.map(
    [
        { route: , redirect: ‘Home/Start’ },
        {
            name: ‘MvcRoute’,
            route: ‘:mvcController/:mvcAction/:id?’,
            moduleId: ‘app/routing/mvc-route-navigation/mvc-route’
        }
    ]);
}

Read more about Aurelia route configuration here.

The MvcRoute rule points to a ts module that will take care of the navigation. Let’s have a look at this module:

<template>
    <div innerhtml.bind=”html”></div>
</template>
import ‘whatwg-fetch’;
import { autoinject } from ‘aurelia-framework’;
import { HttpClient } from ‘aurelia-fetch-client’;

@autoinject
export class MvcRoute {
    html: string;

    constructor(private httpClient: HttpClient) {
    }

    activate(params: any) {
        let url = this.resolveMvcUrl(params);
        this.loadHtml(url).then(html => {
            this.html = html;
        });
    }

    private resolveMvcUrl(params: any): string {
        let url = `/${params.mvcController}/${params.mvcAction}/${(params.id || ”)}`;
        delete params.mvcController;
        delete params.mvcAction;
        delete params.id;
        var queryString = $.param(params);
        if (queryString) {
            url += `?${queryString}`;
        }
        return url;
    }

    private loadHtml(mvcRoute: string): Promise<string> {
        const result = this.httpClient.fetch(mvcRoute).then(response => response.text());
        return result;
    }
}

 

In the html template we simply bind the innerhtml of a div to the html property of the MvcRoute class.

Switching over to the ts class; Aurelia will call the “activate” function for each navigation. The params parameter will contain the parameters we defined in the route rule – mvcController, mvcAction and id – and we use that object to put together the url to call. Any additional members found in the params object will be appended as query string parameters. (This covers the common routing setup for a standard MVC application but would need modifications for any other setup and/or if you are using Areas.)

Once we have the url ready we use the aurelia-fetch-client to call the url and the response – which will be html – is assigned to the html class member.

The final step is to replace our “textcontent.bind” element in app.html with a <router-view></router-view> element. This is an Aurelia built in custom element that marks the place where we want the route navigation to insert it’s markup into the DOM.

When running the app at this point we are back to a fully functioning navigation between the three pages but with the difference that there is no full page reloads. Notice the /#/ part of the url and that you can use the browsers back and forward buttons as normal.

This marks the end for this initial blog post. There are a few more boilerplate stuff needed to be able to actually use this setup. This is some of the stuff I’ll be covering in future blog posts, so stay tuned:

  • How to use aurelia built in and custom attributes/elements within the server side rendered markup (using the enhance functionality of Aurelias templating engine).
  • Sharing the model of the mvc view to the client so that Aurelia bindings can be used with application data.
  • Making validation and posting of forms working as expected (using MVC Validation attributes, jquery-validation, jquery-validation-unobtrusive  and jquery-ajax-unobtrusive).

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