Aurelia

Using NSwag to Generate an Aurelia Client for an ASP.NET Core 3.1 API

This week we are going to add an Aurelia project that will utilize the contacts API we created a few weeks ago using a client-generated by NSwag. This post is part of the revamp of the ASP.NET Core Basics repo that was kicked off when .NET Core 3.0 was released which is now targeting .NET Core 3.1. For details on how the associated samples got to their current point check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

The sample code before any changes from this post can be found here.


 

Create the Aurelia Project

As with Vue there is no .NET CLI template from Microsoft that has Aurelia support so to crate the Aurelia project we will be using the Aurelia CLI. Before getting started ensure you have npm installed.

Install the Aurelia CLI using the following command from a command prompt.

npm install -g aurelia-cli

Next, use the following command to start the project creation process using the Aurelia CLI. Keep in mind that the CLI creates a directory with the project name.

au new

The above will result in a walkthrough of the project creation process. First is the name of the project, contacts-aurelia in this case. Next is the setup of the project and here we will be using the Default TypeScript App.

Finally, select how you would like to manage dependencies. The sample project is using npm, but Yarn is also an option. If you do go with Yarn some of the following steps will need the npm commands translated to Yarn commands.

After the project creation process is complete use the following command to change to the new directory created for the project.

cd contacts-aurelia

Now the project needs a few more dependencies installed. We are going to install a couple of UI related items, Bootstrap and Font Awesome, as well as the Aurelia Fetch Client which we will need to hit our API.

npm install bootstrap
npm install font-awesome
npm install aurelia-fetch-client

The application that the Aurelia CLI outputs is very basics so the Aurelia docs for creating a to-do application and creating a contact manager were used to build the basics of the sample application. I will be coving the contact related bits of the UI, but the application stops short of implementing the save functionality at this point.

Use NSwagStudio to Generate an API Client

NSwag provides multiple options for client generation including a CLI, code, or a Windows application. This post is going to use the Windows application which is called NSwagStudio. NSwagStudio can be downloaded and installed from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, using a local instance of the sample solution’s Contacts API the URL is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the TypeScript Client checkbox and then select the TypeScript Client tab. There are a lot of options to play with, the highlighted options are the ones that are important for this sample. For Template, we just need an Aurelia based client. The final option that needs to be set is the Output file path and this is the location you want the generated file to be. I output to the Aurelia project directory under /src/contactApi.ts. After all the options are set click Generate Files.

Create UI and Use Generated Client

Again the UI bit mostly comes from the docs, but I’m going to show the bits for the contact list here and the rest of the UI you can look at the sample code. All of the following will be taking place in the src directory of the Aurelia project.

First, add a file named contact-list.html which will hold the template for the UI of the contact list with the following contents. This is a mix of HTML and Aurelia’s syntax. We aren’t really going into the Aurelia specific bits, but even if you are new to Aurelia this should be readable.

<template>
  <div class="contact-list">
    <ul class="list-group">
      <li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
        <a route-href="route: contacts; params.bind: {id:contact.id}" click.delegate="$parent.select(contact)">
          <h4>${contact.firstName} ${contact.lastName}</h4>
          <p>${contact.email}</p>
        </a>
      </li>
    </ul>
  </div>
</template>

Next, add a contact-list.ts file which is what the template from above will be bound to. The lines specific to the usage of the NSwag generated client are highlighted.

import {ContactsClient, Contact} from './contactsApi';
import {inject} from 'aurelia-framework';

@inject(ContactsClient)
export class ContactList {
  contacts: Contact[];
  api: ContactsClient;
  selectedId: any;
  
  constructor(api: ContactsClient) {
    this.api = api;
    this.contacts = [];
  }

  created() {
    this.api.getContacts().then(contacts => this.contacts = contacts);
  }

  select(contact) {
    this.selectedId = contact.id;
    return true;
  }
}

As you can see from the above Aurelia is injecting an instance of the ContactsClient via the class’s construction and then that client is used in the created function to call the API client’s getContacts function and using the resulting data from the API to replace the contacts field with the results of the API call.

The application is displaying the contact list in app.html via the contact-list element. The import and usage of the contact list component are highlighted in the following chunk of code.

<template>
  <require from="./styles.css"></require>
  <require from="./contact-list"></require>

  <nav class="navbar navbar-light bg-light fixed-top" role="navigation">
    <a class="navbar-brand" href="#">
      <i class="fa fa-user"></i>
      <span>Contacts</span>
    </a>
  </nav>

  <div class="container">
    <div class="row">
      <contact-list class="col-md-4"></contact-list>
      <router-view class="col-md-8"></router-view>
    </div>
  </div>
</template>

At this point, I tried out the application and it wasn’t pulling back any data. After doing some digging in the network tab of my browser’s dev tool I noticed that the API call was missing the base part of the URL. This hasn’t come up before for the other times I have used the NSwag generated client and if you look at the constructor of the client it defaults the base URL to the endpoint that was used to generate the client, see the following code.

constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
    this.http = http ? http : <any>window;
    this.baseUrl = baseUrl ? baseUrl : "https://localhost:5001";
}

It turns out that the Aurelia dependency injection system calls the constructor with an empty string instead of null. One option would have been to change the above constructor to handle an empty string, but that would mean any time the client got regenerated I would have to remember to follow it up with the constructor modification which would be too easy to screw up. After some digging, I found out that Aurelia provides a way to control how an instance of a class is created. Open main.ts and make the following highlighted changes. I’m injecting the URL, but using null instead would also work and the base URL from the ContactClient would get used.

import { HttpClient } from 'aurelia-fetch-client';
import {Aurelia} from 'aurelia-framework'
import * as environment from '../config/environment.json';
import {PLATFORM} from 'aurelia-pal';
import 'bootstrap/dist/css/bootstrap.css';
import 'font-awesome/css/font-awesome.css';
import { ContactsClient } from 'contactsApi';

export function configure(aurelia: Aurelia) {
  aurelia.use
    .standardConfiguration()
    .feature(PLATFORM.moduleName('resources/index'))
    .instance(ContactsClient, 
              new ContactsClient("https://localhost:5001",
                                 aurelia.container.get(HttpClient)));

  aurelia.use.developmentLogging(environment.debug ? 'debug' : 'warn');

  if (environment.testing) {
    aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
  }

  aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}

After all the change from a command prompt set to the root of the Aurelia project, you can use the following command to run the application. If you drop the open it will run the application without opening a browser.

au run --open

Wrapping Up

As always NSwag makes it very easy to create a client to interact with an API. Hopefully, this was useful even if my Aurelia code might not be idiomatic.

The sample projects after all the changes in this post can be found here.

Using NSwag to Generate an Aurelia Client for an ASP.NET Core 3.1 API Read More »

JavaScriptServices: What happen to Aurelia, Vue, and Knockout Templates

With the release of Visual Studio 2017 version 15.3 there are now built in web application templates for Angular, React, and React and Redux.

This is an awesome addition and makes creating new applications with the most popular frameworks simple. What do you do if your framework isn’t one of the top 3 options? JavaScriptServices can help out here and has provided templates for Aurelia, Vue, and Knockout in the past.

Where have they gone?

This is where it gets a little tricky as the templates still exist and are still being maintained, but finding them is a little harder at the moment for some reason. Yeoman was how JavaScriptServices was used in the past, but that has now transitioned to using the .NET CLI and can be added using the following.

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

After that is complete you will see a list of templates that are now available.

Now you can create an Aurelia project using the following command.

dotnet new aurelia

Wrapping up

I was very happy to see that support for the other frameworks hadn’t been dropped. There are a ton of other templates available for dotnet new which can be found here.

 

JavaScriptServices: What happen to Aurelia, Vue, and Knockout Templates Read More »

Aurelia Contact Creation and Post to an API

Expanding on this post where a placeholder was added for contact creation the placeholder will be replaced with an actual UI. As part of the contact creation process, Aurelia’s fetch client will be used to make a post request to the ASP.NET API. The code at the starting point can be found here. If using the sample code keep in mind all the changes in this post takes place in the Aurelia project.

Contact service changes to allow post

In this project a service is used to keep all the Http bits isolated from the rest of the application. In the contactService.ts file found in the ClientApp/app/components/contacts/ directory a couple of changes need to be made. First the fetch import needs to expose json in addition to HttpClient.

import { HttpClient, json } from 'aurelia-fetch-client';

Then a save function is added that makes a post request to the ASP.NET API and return a new contact based on the response from the post request. The contact in the post response will contain the ID assigned by the API.

save(contact: Contact): Promise<Contact> {
    return this.http.fetch('',
        {
            method: 'post',
            body: json(contact)
        })
        .then(response => response.json())
        .then(contact => new Contact(contact))
        .catch(error => console.log(error));
}

Notice the usage of json to serialize the contact being create to JSON before sending to the server. Also, note that just logging an error to the console isn’t a best practice and should be handling in a different way in a production application.

Contact detail view model

The view model that backs the contact detail view needed a function to allow saving of a contact. The following code uses the contact service to save a contact and then replace its local contact with the new one returned from the API. Finally, the class level variable indicating if the view model is in create or detail mode is set to true which triggers the UI to change out of create mode.

save() {
     this.contactService.save(this.contact)
         .then(contact => this.contact = contact)
         .then(() => this.hasContactId = true);
}

You will see in the sample code that a second function was added for reset which is a quick way to reset the create UI.

reset() {
    this.contact = new Contact();
}

Contact model

The constructor of the contact class changed to make the data parameter optional to allow for the creation of an empty contact.

constructor(data?) {
    if (data == null) return;
    Object.assign(this, data);
}

Contact detail view

The view under when the most changes and the following is the entirety of the UI file which can be found in the contactDetail.html file. The code will be followed up with call outs for the important bits.

 <template>
      <h1>Contact Details</h1>
      <hr />
      <div if.bind="hasContactId">
          <dl class="dl-horizontal">
              <dt>ID</dt>
              <dd>${contact.id}</dd>
             <dt>Name</dt>
             <dd>${contact.name}</dd>
             <dt>Address</dt>
             <dd>${contact.getAddress()}</dd>
             <dt>Phone</dt>
             <dd>${contact.phone}</dd>
             <dt>Email</dt>
              <dd>${contact.email}</dd>
          </dl>
      </div>
     <div if.bind="!hasContactId">
         <div>
             <form role="form" class="form-horizontal">
                 <div class="form-group">
                     <label class="col-sm-2 control-label">Name</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="name" 
                                class="form-control" 
                                value.bind="contact.name" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">Address</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="address" 
                                class="form-control" 
                                value.bind="contact.address" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">City</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="city" 
                                class="form-control" 
                                value.bind="contact.city" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">State</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="state" 
                                class="form-control" 
                                value.bind="contact.state" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">Zip</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="zip" 
                                class="form-control" 
                                value.bind="contact.postalCode" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">Phone</label>
                     <div class="col-sm-10">
                         <input type="text" 
                                placeholder="phone" 
                                class="form-control" 
                                value.bind="contact.phone" />
                     </div>
                 </div>
                 <div class="form-group">
                     <label class="col-sm-2 control-label">Email</label>
                     <div class="col-sm-10">
                         <input type="email" 
                                placeholder="email" 
                                class="form-control" 
                                value.bind="contact.email" />
                     </div>
                 </div>
             </form>
         </div>
         <div class="text-center">
             <button class="btn btn-success btn-lg" 
                     click.delegate="save()">Save</button>
             <button class="btn btn-danger btn-lg" 
                     click.delegate="reset()">Reset</button>
         </div>
     </div>
     <div>
         <a route-href="route: contactlist">Back to List</a>
     </div>
      <hr />
  </template>

All control of content rendering has been changed to use hasContactId.

Default view:
<div if.bind="hasContactId">

Create view:
<div if.bind="!hasContactId">

For the creation UI, the data is bound using Aurelia’s value converters for more detail see the docs. The value converter is the value.bind bit.

<input type="text" 
       placeholder="name" 
       class="form-control" 
       value.bind="contact.name" />

The last thing to point out is the click delegates that are used to call the associate save and rest functions with the Save and Reset buttons are clicked.

<button class="btn btn-success btn-lg" 
        click.delegate="save()">Save</button>
<button class="btn btn-danger btn-lg" 
        click.delegate="reset()">Reset</button>

Wrapping up

The application now has the ability to add contact instead of only viewing existing contact which brings it close to a more realistic application. The code in its finished state can be found here.

The plan is to continue iterating on this application and moving the Aurelia and Angular 2 projects in parallel. I hope this is useful and if you have any specific features you would like to see implemented leave a comment.

Aurelia Contact Creation and Post to an API Read More »

Aurelia Optional Route Parameter

This post expands on the Aurelia post from a couple of weeks ago that involved router links and routing parameters to optional route parameters. The use case for this post is if a user ends up on the contact detail page with no contact ID they will be presented with the option to add a new contact. The starting point for the code can be found here. Keep in mind any changes in this post are taking place in the Aurelia project.

Route with optional parameter

Making a route parameter optional is as simple as adding a question mark to the end of the parameter name. The following is the before and after of the contact detail route found in the app.ts file in the ClientApp/app/components/app folder.

Before:
route: 'contact-detail/:id'

After:
route: 'contact-detail/:id?'

For more information on routing see the Aurelia docs.

Contact detail changes

In the contact detail view model which is in the contactDetail.ts file a class level property is added for if the component was activated with a contact ID or not.

hasContactId: boolean;

The activate function is changed to set the new class level variable to false if the function is called parms.id is falsy as well as to only pull contact details if it has a contact ID.

activate(parms, routeConfig) {
    this.hasContactId = parms.id;

    if (this.hasContactId) {
        return this.contactService.getById(parms.id)
            .then(contact => this.contact = contact);          
    }

    return null;

}

A placeholder for creating a new contact was added to the contact detail view found in contactDetail.html file. This placeholder was added just above the link back to the contact list. This placeholder will only show if the detail components are loaded with no ID.

<h3 if.bind="!hasContactId">
    Placeholder for creating a new contact
</h3>

Add create link to the contact list

Finally, add a link in the contactList.html file that sends the user to the contact detail view, but without sending a contact ID.

<a route-href="route: contactdetail">Create New Contact</a>

In the example code this was added before the table of contacts, but of course, it could be anywhere.

Wapping up

The code in it’s completed state can be found here. The same functionality using Angular 2 will be coming up next week.

Aurelia Optional Route Parameter Read More »

Aurelia – Router links, click delegate, routing parameters

This post is going to cover multiple of topics that I hit while creating a contact detail page to go along with the contact list that is part of my ASP.NET Basics repo. The code before any changes can be found here. If you are following along with the sample application keep in mind all the changes in this post take place in the Aurelia project.

Creating a detail view and view model

Create contactDetail.html file inside of ClientApp\app\components\contacts. This is the view that will be used to display all the details of a specific contact as well as a link back to the contact list. The following image shows the folder structure with the view and view model already added.

View

The following is the full contents of the view. This will be followed up few a calls out of Aurelia specific things going on.

<template>
    <h1>Contact Details</h1>
    <hr />
    <div if.bind="contact">
        <dl class="dl-horizontal">
            <dt>ID</dt>
            <dd>${contact.id}</dd>
            <dt>Name</dt>
            <dd>${contact.name}</dd>
            <dt>Address</dt>
            <dd>${contact.getAddress()}</dd>
            <dt>Phone</dt>
            <dd>${contact.phone}</dd>
            <dt>Email</dt>
            <dd>${contact.email}</dd>
        </dl>
    </div>
    <a route-href="route: contactlist">Back to List</a>
    <hr />
</template>

if.bind will keep the contact details out of the DOM if the condition fails. In this case, if the contact is null.

${value} is one of Aurelia’s bind syntaxes. For more details check out their docs.

<a route-href=”route: contactlist”> is using Aurelia’s router to generate a link back to the contact list component.

View model

Next, create a contactDetail.ts file inside of ClientApp\app\components\contacts  which is the view model that goes with the view created above.

The view model gets an instance of the ContactService injected which is used to pull a contact’s detail information during the activate lifecycle hook. Notice the first parameter of the function (parms) which is where the route parameters are passed in.

import { inject } from 'aurelia-framework';
import { Contact } from './contact';
import { ContactService } from './contactService';

@inject(ContactService)
export class ContactDetail {
     contact: Contact;

    constructor(private contactService: ContactService) { }

    activate(parms, routeConfig) {
        return this.contactService.getById(parms.id)
            .then(contact => this.contact = contact);
    }
}

Adding get by ID to the Contact Service

The existing ContactService doesn’t provide a function to get a contact by a specific ID so one needs to be added.

The following calls the API in the Contacts project and uses the result to create an instance of a Contact as a promise which is returned to the caller.

getById(id: string): Promise<Contact> {
    return this.http.fetch(id)
        .then(response => response.json())
        .then(contact => new Contact(contact))
        .catch(error => console.log(error));
}

Add a route with a parameter

Next, the router needs to be made aware of the new contact details. Open app.ts inside of the ClientApp/app/components/app folder. This file contains all the routes for the application. The bit we are interested in is the config.map which is an array of routes the application handles. Contact details is a new route which means adding a new object to the config.map array.

{
 route: 'contact-detail/:id',
 name: 'contactdetail',
 moduleId: '../contacts/contactDetail',
 nav: false,
 title: 'Contact Detail'
}

The route is the pattern used to match URLs. In addition, parameters can be used in the form of :parameterName. The above route will handle requests for http://baseurl/contact-detail/{id} where {id} is an ID of a contact. If a route has a parameter it will be made available to the activate function via the parms parameter in the view model.

moduleId is used to locate the view/view model that goes with the route.

nav controls if the route will be included in the routers navigation model. This is used to build the menu in this application. Contact details shouldn’t show in the menu which is why nav is set to false. For more details on Aurelia’s router check out the docs.

Integrating the detail view with the contact list

The contact list view and view model needed changes to support the contact detail page.

View model

The change to the view model was the simplest. A variable for the ID of the select contact was added as well as a function that gets called when a contact is selected. The following is the fully contact list view model.

import { inject } from 'aurelia-framework';
import { Contact } from './contact';
import { ContactService } from './contactService';

@inject(ContactService)
export class ContactList {
    contacts: Contact[];
    selectedContactId: number = null;

    constructor(private contactService: ContactService) {}

    created() {
        this.contactService.getAll()
            .then(contacts => this.contacts = contacts);
    }

    select(contact) {
        this.selectedContactId = contact.id;
    }
}

The changes to the view model were not really required to add the contact detail page, but they are there to show how to setup a click delegate on the view side. In the future, the selected contact could come in handy if the list and details were shown at the same time.

View

On the view, the amount of data being displayed was reduced to show just contact ID and name. A column was added with a link to the details page that is now used to show the rest of information about a contact. The following is the full view.

<template>
    <h1>Contact List</h1>
    <p if.bind="!contacts"><em>Loading...</em></p>

    <table class="table" if.bind="contacts">
        <thead>
        <tr>
            <th>IDs</th>
            <th>Name</th>
            <th></th>
        </tr>
        </thead>
        <tbody>
        <tr repeat.for="contact of contacts" 
          class="${contact.id === selectedContactId ? 'active' : ''" }>
            <td>${contact.id}</td>
            <td>${contact.name}</td>
            <td><a route-href="route: contactdetail; params.bind: {id:contact.id}" click.delegate="select($contact)">
                  Details
                </a>
            </td>
        </tr>
        </tbody>
    </table>
    <hr />
</template>

click.delegate=”select($contact)” will cause the select function of the view model to be called when the associated element clicked and passes the relevant contact object. The docs go into more depth on when to use delegates vs triggers.

The details link contains a lot of concepts. route-href is binding the anchor to Aurelia’s router.

route: contactdetail is telling the router which route to load.

params.bind: {id:contact.id} is telling the link to pass the contact’s ID to through the router to the view model that is being loaded. The following is the activate function of the contact detail view model as a reminder of the parameter’s usage.

activate(parms, routeConfig) {
    return this.contactService.getById(parms.id)
        .then(contact => this.contact = contact);
}

Wrapping up

This post cover a lot of topics, but they were all things I had to review in the course of adding contact details. My hope is this post will shortcut your own research and get you back to your task at hand. The finished code can be found here.

Leave a comment with any thoughts and/or questions.

Aurelia – Router links, click delegate, routing parameters Read More »

Aurelia with JavaScriptServices: Call to Node module failed

This is going to be a quick post on how to solve an error I go on a new Aurelia project created using JavaScriptServices. For a walk though of creating a project using JavaScripServices check out this post.

The problem

The project got created and would build with no issues, but when I tried to run it I got the following error.

Call to Node module failed with error: Error: Cannot find module ‘./wwwroot/dist/vendor-manifest.json’

The fix

After a little bit of searching I came across this issue on the JavaScriptServices repo with the same error. One of the comments on the issue suggested running the following command from a command prompt in the same directory as your project.

webpack --config webpack.config.vendor.js

After running that command everything worked like a charm! It is worth noting that the command above can be found in project.json in the scripts prepublish section. From the little bit of reading I did it looks like this command should be rerun anytime a new vendor dependency is added.

JavaScriptServices

I can’t get over how awesome JavaScriptServies is. Steve Sanderson (and team) have done an amazing job. Even with this little hiccup I got this new project up and running 10 times faster than I would have otherwise. It is also going to give me a push to look into webpack.

Aurelia with JavaScriptServices: Call to Node module failed Read More »

Aurelia with ASP.NET Core: Host Aurelia from a Controller

This is the forth entry in a series using Aurelia and ASP.NET Core together. Each post builds on the previous and all the code is available on Github.

Part 1 – Add Aurelia to an ASP.NET Core Project
Part 2 – Aurelia with an ASP.NET Core API
Part 3 – Aurelia with an ASP.NET Core API: Modeling and Displaying Data in Aurelia
Part 4 – Aurelia with ASP.NET Core: Host Aurelia from a Controller (this post)
Github repo with the code for all of the parts with a release to go with each post

The goal

So far the Aurelia application in this series has existed outside of the the ASP.NET Core application. This post is going move the Aurelia application to be hosted by a MVC controller and a razor view. This would allow an existing application to slowly be ported to Aurelia or allow portions of an application to be replaced by Aurelia as it made sense, etc.

The controller

The controller isn’t going to be doing much other than returning a view that contains the entry point for the Aurelia application. This example will be using a new Aurelia  action on the HomeController.

public IActionResult Aurelia()
{
    return View();
}

The view

Next create a view in the Views/Home folder named Aurelia.cshtml to match the name of the action added to the HomeController above. Right click on the Home folder and select Add > New Item.

addnewitem

This will show the Add New Item dialog. Using the search in the upper right corner serach for MVC and select MVC View Page. Enter Aurelia.cshtml as the name and click Add.

Enter the following in the newly created file.

<div aurelia-app="main">
    <script src="/scripts/vendor-bundle.js" data-main="aurelia-bootstrapper"></script>
</div>

This code defines a div that will host an Aurelia application named main.

Add a link to the Aurelia application

Inside of the Views/Shared folder open _Layout.cshtml which is where the MVC application’s navigation bar is defined. Locate the navigation bar code and add a list item and link that points to the HomeController Aurelia action defined above. The following is the full navigation bar for the MVC application including the new link for Aurelia.

<div class="navbar-collapse collapse">
    <ul class="nav navbar-nav">
        <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="Aurelia">Aurelia</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
        <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
    </ul>
</div>

Adjust Aurelia’s baseUrl

Finally inside the aurelia_project folder open aurelia.json and adjust the baseUrl property inside the targets section to look for the scripts folder to be up one directory. This is required now with the Aurelia application being hosted inside the HomeController which will cause the Aurelia application to look for its scripts in the Home/Scripts folder instead of the site’s main scripts folder. If you are going to have multiple Aurelia applications per MVC application then you may need to take a different path on this section.

Before:
"build": {
  "targets": [
    {
      "id": "aspnetcore",
      "displayName": "ASP.NET Core",
      "output": "wwwroot\\scripts",
      "baseUrl": "scripts"
    }
  ]

After:
"build": {
  "targets": [
    {
      "id": "aspnetcore",
      "displayName": "ASP.NET Core",
      "output": "wwwroot\\scripts",
      "baseUrl": "../scripts"
    }
  ]

Wrap up

Run the application and click the Aurelia link in the navigation bar and the Aurelia application from last week will run, but now it will still have the navigation bar from the MVC application showing. The index.html file located in the wwwroot folder that was previously used to host the Aurelia application can be deleted.

The code associated with this post can be found here.

Aurelia with ASP.NET Core: Host Aurelia from a Controller Read More »