Monday, September 5, 2016

Building a web application using ASP.NET Core 1.0, Aurelia, TypeScript, Webpack in Visual Studio Code – Part 5

Introduction

YouTube Video Link for Part 5, Section 1:

YouTube Video Link for Part 5, Section 2:

YouTube Video Link for Part 5, Section 3:

YouTube Video Link for Part 5, Section 4:

YouTube Video Link for Part 5, Section 5:

This is the fifth part of Building a web application using ASP.NET Core 1.0, Aurelia, TypeScript, Webpack in Visual Studio Code.

The source code for this tutorial is available on GitHub.

In Part 4 of this series, we setup a working Aurelia application by merging the aurelia-skeleton-navigation-webpack project and source code of part 3 of our tutorial. In this part, we’ll build a web application that encompasses 2 mini SPAs (Single Page Application): Contact Manager and Task Manager. Contact Manager application is provided as part of Aurelia tutorial in Aurelia’s DocHub documentation site. I have taken that tutorial as the base project on top of skeleton-navigation-webpack-typescript and added more features to help show case the features of Aurelia and how it works with the ASP.NET Core Web APIs on the back end. Task Manager is influenced by Microsoft Outlook Web Access (OWA) application. Task Manager is also built on top of the concepts provided in Aurelia tutorial. Since writing the blog post as well as recording the YouTube video for 2 mini-SPAs will make the blog post and video unusually long, I’d like to split the mini-SPAs into 2 parts. In this part i.e., part 5 of the series, I’ll give a walkthrough of creating the Contact Manager mini-application. In part 6, I’ll give a walkthrough of creating the Task Manager mini-application. I’ll also show how to create some common UI components that can be shared by both these mini applications in this part.

Prerequisites

Please create a new folder named “part5” under “C:\tutorial”. You’ll need the source code from part4 to practice this part of the series. You can pull the  source code from my github repository: https://github.com/rajajhansi/aspnetcore-aurelia-tutorial . After you got the source code, delete the “.git” directory.

Important Changes

There are a couple of important changes since I posted the last part of the series. Aurelia has been officially released on July 27th. You can read about that announcement here. As part of the release, Aurelia is now made available through many outlets like JSPM, Webpack, Aurelia CLI (their own new Command Line Interface tool) besides the other standard outlets like NPM, GitHub, Bower, CDNs etc. As with any new release of Aurelia, the Aurelia skeleton navigation application has been updated to use Aurelia 1.0 RTM, which means that we need to update our sample source code for part 5 as well. Visual Studio Code 1.5.1 was released as well. We have been using the nightly build of TypeScript of 2.0. Now, TypeScript 2.0 RC is available. I’d recommend you to install TypeScript 2.0 using:

npm install typescript@rc –g

npm install typescript@rc

Along with that, I’d also recommend you to install the latest version of tslint locally so Visual Studio Code code use it for static analysis of TypeScript code. You can install tslint using:

npm install tslint

Importance of TodoMVC application in learning a JS framework

If you were to develop a front end for web application, you have plenty of choices when it comes to JavaScript frameworks. Every framework comes with its own application structure, feature set and it is very confusing to chose the framework. So, a group of 7 developers have put together this great website at todomvc.com that showcases how a TodoMVC application can be implemented in popular JavaScript frameworks out there in the market now. The sample TodoMVC application implemented in different JavaScript frameworks allow you get up to speed quickly on that framework, understand how the framework can be used to quickly put together a web application using that framework but you should also do more reading and exploring of the framework yourself before you choose a framework.

Aurelia version of TodoMVC application is available in todomvc.com’s github page. Marcel Hoyer has implemented the Aurelia version of TodoMVC application and submitted into todomvc.com. There are also few other implementations of Aurelia version of TodoMVC.  I’m not going to use the TodoMVC application in this series although you’ll see that the concepts are very similar. I’d encourage you to check out the Aurelia version of TodoMVC application just for learning purposes.

Preparing the part 5 source code to use Aurelia 1.0 RTM

Like we did in part 4, we are going to create a staging folder and get the latest aurelia skeleton navigation source code. Please refer to part 4 for the steps to get the latest aurelia skeleton navigation source code.

After getting skeleton source code, please follow these steps to successfully add skeleton-navigation into our source to create the source code for part5:

1. Copy the contents of “C:\staging\skeleton-navigation\skeleton-typescript-webpack” into C:\tutorial\part5\modern” except:

.awcache, .vscode, node_modules

fig0.

2. Copy all files and folders from C:\tutorial\part4\modern except the following files/folders into C:\tutorial\part5\modern:

.awcache, custom_typings, node_modules, obj, src, styles, test, favicon.ico, index.html, index.js, package.json, project.lock.json, tsconfig.json and webpack.config.js

fig1

3. While you are copying, if you get conflicts about the files: README.md, just go ahead and overwrite them.

4. Copy greeter.html and greeter.cs from part4/modern/src and paste them into part5/modern/src

5. Open VS Code and edit project.json to exclude “node_modules” folder as follows:

"buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true,
    "debugType": "portable",
    "compile": {
      "exclude": [
        "node_modules"
      ]
    }
  },

6. Run the project by hitting F5. Once the browser is launched, type the url http://localhost:5000/api/greetings in the address bar to make sure that our Greetings Web API runs without any issues.

fig2

7. Change the argument to aurelia.setRoot() function in main.ts to point to greeter component like this:

aurelia.setRoot(“greeter”);

8. Open up cmder and change your directory to “C:\tutorial\part5\modern”. Then, run the following commands in the same order:

npm install 
npm start

That should start the webpack-dev-server on localhost:9000 after bundling the modules. You can launch chrome browser and visit the url: http://localhost:9000 to see the greeter class in action.

fig3

Now that we have upgraded part 5 of the source code to use Aurealia 1.0 RTM, it is time to move on from the “Hello World” type application into a cool working web application that demonstrates the power of Aurelia and ASP.NET Core.

Building the Application Layout aka Shell

I thought about creating the mockup UI of the application UI using some online wireframe tool. But, I chose to take the screenshot of the completed application to show the application layout aka shell.

fig5

fig6

In the pictures above, notice that the application has a top navigation bar with 2 menu items: Contact Manager and Task Manager to indicate the 2 mini-SPAs that we’ll be building. Down below the navigation bar, there are two panes: list and details. As their names imply, the list pane shows the list of Contacts or Tasks and the right pane shows the details of the selected Contact or Task. Let us start putting together this layout and build the features incrementally.

Setting up the Project Structure

Let us define a project structure that will help us organize our server side and client side code for readability and maintainability. ASP.NET core has already defined the folder structure like Controllers, Views etc. For Aurelia, there are some guidelines like isolating the global resources, features etc. If you use the new Aurelia CLI i.e., au tool and create a new project using “au new projectname”, it will give you a nice folder structure. I’m also a big fan of John Papa’s HotTowel Angular template, that organizes folder structures within a SPA (Single Page Application) neatly. So, I’m going to take a hybrid approach here i.e., picking and choosing the folder structures from au tool as well as hottowel template.

project_structure

Creating views as Aurelia Global Resources

While all other folders are self-explanatory, I’d like to explain the concepts of Aurelia Global Resources and Feature. Adding our components into global resources will allow us to user them in Views without having to add <require from=”path of the resource”></require> tag.

Grouping related components as Aurelia Feature

Aurelia “Features” feature Smile allows us to organize a whole group of components or related functionalities. To create a “Feature”, create a folder, organize all components and other code, add an index.ts (or .js) file, export a “configure()” function, add your components into the globalResources using config.globalResources() function and finally turn on this feature using “aurelia.use.feature(“name_of_the_feature” in main.ts.

Creating the CSS styles and helper classes

I’d like to show how you can build a full featured application that encompasses Contact Manager and Task Manager using Aurelia. The objective of this blog post series is to show how you can develop a modern web application using ASP.Net Core and Aurelia. We’ll keep that as our primary focus instead of spending too much time on styling, CSS frameworks etc. We’ll use Twitter Bootstrap CSS framework to design the layout and other UI elements but you’re not restricted to use Bootstrap. One of the most impressive CSS frameworks is Semantic UI 2.x, which is gaining more popularity amongst the web developers these days.

Downloading the support files

Instead of spending time on creating styles.css and a few other support classes: utlitity.ts and in-memory-contact-service.ts that are required for our modern web application, I prepared them already and made it available for download here. Please download them individually or get the entire project by cloning the repository, then copy styles/styles.css into styles folder and src/services/utility.ts and src/services/in-memory-contact-service.ts into src/services folder. I’m going to explain how I created the look and feel of the application in the next section.

Bootstrap template and other CSS snippets

The bootstrap template I chose for this sample application is Bootstrap Flat developed by Scott Dorman, which you can download from here. After you downloaded the Bootstrap Flat as a .zip file, extract its contents and just copy bootstrap-flat.css and bootstrap-flat-extras.css into styles folder. Then, make sure to import those 2 css files in main.ts like this:

import "../styles/bootstrap-flat.css";
import "../styles/bootstrap-flat-extras.css";

I expected the check boxes in BootStrap Flat to be flat but they aren’t. So, I asked my friend Bogdan urs for help and he graciously pointed out to this nice flat checkbox named “awesome-bootstrap-checkbox”, which can be downloaded here. Like we did for Bootstrap Flat, copy awesome-bootstrap-checkbox.css into styles folder. Then, make sure that it is imported in main.ts like this:

import "../styles/awesome-bootstrap-checkbox";

I needed some effects on hover of a table row and on selection of a table row, which aren’t available out of the box. Especially, it is a pain in the neck to deal with bootstrap tables that are of type table-striped. So, I added some custom styles under styles/styles.css like this:

/* Custom styles */
.table-hover>tbody>tr:hover>td{
    background-color: rgba(41, 103, 150, 0.89);
    color: #FFF;
}
.table-hover>tbody>tr.selected {bootstrap-flat-extras.css
  background-color: rgba(41, 103, 182, 0.89) !important;
    color: #FFF;
}
.strike-out {
  text-decoration: line-through;
}

Note: you don’t need to add these custom styles since I already added them into styles.css

Bootstrap Searchbar

I like the look and feel of the Bootstrap Searchbar in https://codepen.io/ace-subido/pen/Echvr and decided to use the same. So, I downloaded the CSS, added it into styles/styles.css and customized the background color to match the blue theme that I have been following throughout the application. Here’s the section of the CSS where I modified the background color:

.search-box .search-btn:hover {
  color: #fff;
  background-color: #337ab7;
}

Note: you don’t need to add these custom styles since I already added them into styles.css

Setting up the client router

In SPA applications built using frameworks like Aurelia, client router is a a very important component that allows us to define that route that contains the url segments and the components, which constitute the screen for that route. Since shell.ts is the defined as the root component in main.ts, we’ll configure the routes in shell.ts like this:

import {Router, RouterConfiguration, NavigationInstruction} from "aurelia-router";
export class Shell{
  router: Router;
  configureRouter(config: RouterConfiguration, router: Router) {
    config.title = "Modern";
    config.addPipelineStep("authorize", ToastNavResult);
    config.map([
      { route: "", redirect: "contactmanager"},
      { route: "contactmanager", name:"contactmanager", moduleId: "contact-manager/index", nav: true, title: "Contact Manager" },
      { route: "taskmanager", name: "taskmanager",      moduleId: "task-manager/index",    nav: true, title: "Task Manager" }
    ]);
    this.router = router;
  }
}
class ToastNavResult {
    run(navigationInstruction: NavigationInstruction, next: Function) {
        console.log("Inside run");
        return next().then((a: any) => { console.log(a.status); /*toastr.info(a.status);*/ return a;});
    }
}
}

Since Aurelia is a conventions based framework, to define routes you just need to add a function named configureRouter that takes an instance of RouterConfiguration and Router classes as parameters. We define the routes using RouterConfiguration’s map function. I have defined 3 routes: an empty aka default route, contactmanager route and taskmanager route. The empty or default route is invoked when you just access the base url (for e.g., http://localhost:9000) of the application without any other url segments. As you can see, I’m using the “redirect” property to redirect the control to “contactmanager” route, which points to the contactmanager module. The contactmanager module is the entry point of the contact manager mini-SPA. Similarly, the taskmgr route points to the tasksmanager module, which is the entry point of the task manager mini-SPA.

Let us take a look at how to build the shell view using shell.html.


<template>
  <require from="./nav-bar.html"></require>
 
  <nav-bar router.bind="router"></nav-bar>

  <div class="page-host">
    <router-view></router-view>
  </div>
</template>

Just like how we import the modules in TypeScript using import statement, we need to use <require> to import the views i.e., .html files. In app.html, <require from=”./nav-bar.html> will allows us to use the nav-bar component like a custom element. The navigation bar is not hard coded instead it reads the router object and renders the top menu items based on the route map we defined in the router configuration. The markup that connects the router property of Shell class into shell.html is:

  <nav-bar router.bind="router"></nav-bar>

We are defining a custom attribute named router and use Aurelia’s data binding to bind that attribute to the “router” property defined in the viewmodel i.e. shell.ts.


export class Shell {
  router: Router;
   configureRouter(config: RouterConfiguration, router: Router) {
    …
     this.router = router;
  }
}

I have highlighted the relevant lines of code in bold font. Since nav-bar is used like a custom HTML element with the router.bind syntax, the view of nav-bar component i.e., nav-bar.html has to know to receive that router property somehow. This is where we leverage the power of Aurelia’s bindable attribute. When you don’t have a top level container HTML element like <div>, feel free to use the <template> tag. That’s what is being done in nav-bar.html.


<template bindable="router">
<ul class="nav navbar-nav">
        <li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
          <a data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse.in" href.bind="row.href">${row.title}</a>
        </li>
      </ul>

We use the bindable attribute on <template> to bind the router attribute that we pass on from <nav-bar>. Now, you can use the data binding expressions on “router” to access its properties to construct the top navigation bar. We use the “repeat.for” to enumerate the “router.navigation” property and render the <a> tag using the href and title properties of navigation property. We are also setting up a CSS class “active” if the route is the active route by inspective the isActive property.

Here’s a picture that shows the connection of how the router property from the ViewModel is bound to the views shell.html and nav-bar.html:

fig9

Building the Contact Manager mini-SPA

In this part, let us focus on creating the Contact Manager mini-SPA both the front end as well as the backend. Let us start with the contact-service.ts that implements a client side in-memory contact manager with nice API methods that provide the CRUD operations. After building the front end UI using Aurelia, I’ll show you how to create an ASP.NET Web API that wraps an in memory contact manager and write a Service class in Aurelia that will talk to the Web API to perform the CRUD operations.

Creating the Contact Model

We are going to leverage the power of TypeScript interfaces to declare the Contact Model like this:

export interface IContact {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    phoneNumber: string;
    birthDate: Date;
}

Since we have defined the model, we can now define the ViewModel and View that constitute the contact list component to be rendered on the left pane. Before I start showing how to write the ViewModel, let us spend some time on DI (Dependency Injection) a powerful object oriented technique to construct the object graph without having to explicitly invoke the construction of the object and its dependent objects.

Power of DI (Dependency Injection)

Before we talk about DI, we need to understand the meaning of “D” in SOLID principles. SOLID is an acronym for a collection of advanced object oriented principles. They are Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation and Dependency Inversion principles. We are not going to spend time on all of those principles but I suggest you can do some reading of those principles online. I’ll only focus on “D” i.e., Dependency Inversion Principle abbreviated as DIP.

DIP is defined as:

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

If you have a ViewModel class that depends on another Service class, instead of explicitly instantiating the Service class, we use a technique known as DI, which will create the instance of the dependent classes i.e., the Service class in this class and inject it into the ViewModel class. The main idea of DI technique is to avoid tight coupling and encourage loosely coupled design.

On the server side, we have quite a few IoC (Inversion of Control) containers that are out there for .NET. Some of the popular ones are StructureMap, Ninject, Autofac, Unity etc. ASP.NET Core comes with support for IoC out of the box i.e. you no longer have to depend on 3rd party IoC containers. IoC is a generic term that refers to a programming style where a framework or runtime controls the program flow. People use IoC and DI interchangeablym, which is fine and we don’t need get into arguments about DI vs IoC because DI is just a technique while IoC is a generic term. It is enough to understand that they help developers decouple the dependencies and automate the construction of objects (aka object graph instantiation) along with their dependent objects.

Aurelia’s DI Container

Aurelia framework supports a DI container out of the box. We can use the DI container to inject our dependencies without having to explicitly instantiate them using new operator. There are two ways of injecting dependencies in Aurelia: using static inject method and using @inject decorator. If you are using plain old ES2015 JavaScript, you can only use the first way:

1. Using static inject method:
import {MyService} from ‘./my-service’;
export class MyViewModel {
static inject() { return [MyService]; }
} 

If you use transpilers like Babel or TypeScript, you can use the second way:

2. Using @inject decorator:
import {inject} from 'aurelia-framework'; 
import {MyService} from ‘./my-service’;
@inject(MyService)
export class MyViewModel {
private myViewModel: MyViewModel;
constructor(myService: MyService){  this.myViewModel = myViewModel;
}
}

In TypeScript, you can use the Parameter properties syntax in constructor to create and initialize the member at the same time like this:

constructor(private myViewModel: MyViewModel) {}

Using Parameter properties syntax, you can avoid the declaration of private member and then later assigning that member using this.member = parameter pattern.

In a future part of this series, I’ll write in detail about the object lifetime with examples. If you are really interested to know more about Aurelia’s DI, please check out the documentation at http://aurelia.io/hub.html#/doc/article/aurelia/dependency-injection/latest/dependency-injection-basics/2 

Creating the Contact List Component

Like I mentioned earlier, an Aurelia component constitutes a ViewModel and a View. For developing the contact list component, we’ll define a ViewModel in contact-list.ts and the associated View in contact-list.html. Before we jump into the implementation of Contact List component, I’d like to introduce the Aurelia Component Lifecycle and the method hooks that allow us to hook into the component lifecycle. The lifecycle methods happen in this order: constructor(), created(), bind(), attached(), detached() and unbind(). You can implement any of these methods that are optional in your component and they will be invoked if they are present. It is a good idea to implement the related pairs of methods i.e., if you implement bind(), consider implementing unbind() although it is not mandatory. The same goes for attached() and detached(). I’d like to introduce the created() hook, which we’ll implement in contact list component. The created() method takes 2 parameters: owningView and thisView. As their names imply, the owningView is the view in which this component is declared and thisView is the view of the component itself. For e.g., if you declare the contact list component inside index view (i.e. index.html) and the contact list itself has its own view (i.e., contact-list.html)  to render the list of contacts, then owningView would be index and thisView would be contact-list.

Contact List ViewModel

Let us build the contact list ViewModel class now. Before we start building the ViewModel, let us list our dependencies that need to be injected into this ViewModel. Based on the screenshot of the finished application, let us figure out what features do we need to build and what support classes aka dependencies we’ll required to be injected. In order for us to get a list of contacts, we need the fake in-memory WebApi class. The search functionality also depends on the fake in-memory WebApi class. When the user clicks on one of the contacts, we need to set that contact as the selected/active contact. So, let us list out the methods we need to define in our ViewModel.

loadContacts(), search() and select() methods would suffice for the implementation. Instead of defining a loadContacts() method, we can leverage the created() hook to load the contacts. Here’s the ContactList ViewModel class:

import {inject} from "aurelia-framework";
import {Router} from "aurelia-router";
import {InMemoryContactService} from "../services/in-memory-contact-service";
import {ContactViewed, ContactUpdated} from "../resources/messages/contact-messages";
import {EventAggregator} from "aurelia-event-aggregator";
import {ContactService} from "../services/contact-service";
import {IContact} from "./contact"; @inject(Router, ContactService, EventAggregator)
export class ContactList{
    contacts;
    selectedId = 0;
    keyword: string;     constructor(private router: Router, private contactService: ContactService,
        ea: EventAggregator) {
        ea.subscribe(ContactViewed, msg => this.select(msg.contact));
        ea.subscribe(ContactUpdated, msg => {
            let id = msg.contact.id;
            let found = this.contacts.find(x => x.id === id);
            Object.assign(found, msg.contact);
        });
    }     created() {
        this.contactService.getAll().then(contacts => this.contacts = contacts);
    }     async search() {
        if(this.keyword) {
            await this.contactService.search(this.keyword).then(contacts => this.contacts = contacts);
        } else {
           await this.contactService.getAll().then(contacts => this.contacts = contacts);
        }
        if(this.contacts && this.contacts.length > 0)
        {
            this.select(this.contacts[0]);
            this.router.navigate(`contacts/${this.selectedId}`);
        }
    }     select(contact) {
        this.selectedId = contact.id;
        return true;
    }     async create() {
        await this.contactService.create().then(
            createdContact => {
                this.contacts.push(createdContact);
                console.log(createdContact);
                this.selectedId = createdContact.id;
                this.router.navigate(`contacts/${this.selectedId}`);
            }
         );
        return true;
    }     async delete(id) {
        await this.contactService.delete(id).then(
            response => {
                console.log(response);
                this.selectedId = 0;
                this.contactService.getAll().then(contacts => this.contacts = contacts);
                this.router.navigate("");
            }
         );
        return true;
    }
}

 

Contact List View

In the contact-list view, we need to show the search box and then the list of contacts. Each contact in the list has a link to the contact-detail url with the contact id as a a parameter. This allows us to build a nice master detail form.

<template>
   <div class="panel panel-primary">
    <div class="panel-heading">
      <h3 class="panel-title">Contacts</h3>
    </div>
    <div class="panel-body">
      <div class='search-box'>
        <form class='search-form'>
          <input class='form-control' placeholder='e.g.: Raja, Jhansi' type='text' value.bind="keyword" input.delegate="search() & debounce:500">
          <button class='btn btn-link search-btn' click.delegate="search()">
            <i class='glyphicon glyphicon-search'></i>
          </button>
        </form>
      </div>
      <br/>
      <div class="form-group">
        <div class="col-sm-12">
          <div class="pull-right">
          <br/>
          <button click.delegate="delete(selectedId)" disabled.bind="selectedId === 0" class="btn btn-danger btn-striped btn-sm" aria-label="Left Align">
              <span class="glyphicon glyphicon-trash" aria-hidden="true" title="Remove Contact"></span>Remove Contact
          </button>
          <button class="btn btn-primary btn-striped btn-sm" click.delegate="create()">
              <span class="glyphicon glyphicon-user" aria-hidden="true" title="New Contact"></span>New Contact
          </button>
          </div>
          <br/>
        </div>
      </div>
      <br/>
      <div class="form-group">
        <div class="col-sm-12">
          <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 class="list-group-item-heading">${!contact.firstName && !contact.lastName ? "New Contact" : contact.firstName + " " + contact.lastName }</h4>
                  <p class="list-group-item-text">${contact.email}</p>
                </a>
              </li>
            </ul>
          </div>
        <div>
      </div>
    </div>
</template>

If you notice we are using Aurelia’s binding syntax “delegate” and binding behavior “debounce” on the search input box element. We use input.delegate binding to indicate that we want to handle the DOM event “input” to call the “search()” function in the ViewModel. We are also using Aurelia’s binding behavior “debounce” to prevent the binding from being updated until a specified interval has passed without any changes to the input text. If we don’t specify the “debounce” binding behavior then the binding will happen pretty much for every character we input on the search text box, which is a needless performance hit. We have also wired the DOM event “click” on the “Remove Contact” and “New Contact” buttons to call “delete(selectedId)” and “create()” functions in the ViewModel. We don’t want the “Remove Contact” button to be enabled if no contact is selected i.e., if the selectedId is 0. We use disabled.bind to the expression “selectedId === 0”, which will be true or false depending on if a contact is selected or not.

For each contact in the contact list, we generate an <a> tag with Aurelia’s route-href attribute, which is very powerful. If you just pass the parameters “route” and bind “params”, route-href will generate a proper url. This will come in handy when we start using HTML5 push states which will eliminate the “#” sign in the url to make it look like a server side url. If we don’t use route-href and hard code “#” in the url, then it will be a nightmare to find and replace all of those client side urls with the “#” prefix.

We use ternary operator ?: to decide if either firstName and lastName are empty, which implies that it is a new contact and then change the message to either “New Contact” or the concatenated values of firstName, a space character and lastName properties.

Creating the Contact Detail Component

The contact details component constitutes the Contact Detail ViewModel and View. The contact details pane will be on the right side and shows the contact details form.  Now, we’ll focus on creating some common UI components required to implement a details form.

Building the Common Components

Let us bring in some Aurelia plugins and interesting 3rd party JavaScript libaries that implements some UI components into our project now.

Dialog Boxes

Aurelia Dialog plugin is very handy for showing message boxes as well as simple forms that require user input. To install the plugin, run the command:

npm install aurelia-dialog --save

To install the typings for aurelia-dialog, run the command:

typings install github:aurelia/dialog –save

Wire the dialog plugin into our application using the plugin() fluent API in main.ts like this:

aurelia.use.plugin("aurelia-dialog");

After we installed Aurelia Dialog component and wired it using the plugin() fluent API, we need to build the message box and popup components using the Aurelia dialog component. If you’re interested to know how is Aurelia dialog plugin implemented and how to use it, please visit https://github.com/aurelia/dialog. Let us take a look at how to build a MessageBox component.

Aurelia dialog plugin has a DialogService and a DialogController. The DialogService exposes the methods like open and openAndYieldController to open the dialog box and accept the inputs. The controller is responsible for handling the events and returning the results. To defined our MessageBox componet that uses the Dialog Plugin, we’ll define a MessageBox ViewModel ad View. If you notice, throughout the Aurelia framework, the ViewModel and View pair is the convention consistently followed to create anything be it a component, custom element, custom attribute etc.

MessageBox ViewModel
import {inject} from "aurelia-framework";
import {DialogController} from "aurelia-dialog"; @inject(DialogController)
export class MessageBox {
    message: string;
    answer: string;
    constructor(private controller: DialogController) {
        this.controller.settings.centerHorizontalOnly = true;
    }
    activate(message: string) {
        this.message = message;
    }
}

The view model accepts DialogController using DI via Constructor Injection. It defines a message property that will receive the model argument passed in DialogService.open function from the consuming class. Inside the constructor, we are setting some values for the dialog using the settings property. In this case, we don’t want the dialog centered both vertically and horizontally but just horizontally.

MessageBox View
<template>
    <ai-dialog>
        <ai-dialog-body>
            <h2>${message}</h2>
        </ai-dialog-body>
        <ai-dialog-footer>
            <button click.trigger="controller.cancel()">Stay</button>
            <button click.trigger="controller.ok(message)">Leave</button>
        </ai-dialog-footer>
    </ai-dialog>
</template>

Notice the <ai-*> custom element tags that are implemented by Aurelia Dialog Plugin being used in the MessageBox view. The tags are self-explanatory as they define the body and footer within the dialog. We display 2 buttons: “Stay” which triggers the cancel() function and “Leave” which triggers the “ok()”  function with the message that is being displayed as the result back to the code that invoked the DialogService.open() function.

 

Progress Indicator

We’ll use nprogress progress indicator to display the progress above the top navigation bar. Since nprogress is a legacy library that doesn’t have TypeScript definitions shipped and doesn’t support modules, it is important to get the type definition file i.e. nprogress.d.ts using the typings tool. We need to run these commands to get nprogress library and its type definition file:

npm install nprogress --save

typings install dt~nprogress -- global –save

We are going to create a component to wrap NProgress because it is not a typical Aurelia component. Since NProgress is a self contained library i.e., it takes care of rendering its view, we have to let Aurelia know to not look for a View using @noView() decorator. Since we are mainly going to use NProgress to show a loading indicator, let us name our component LoadingIndicator.

import * as nprogress from "nprogress";
import {bindable, noView} from "aurelia-framework"; @noView()
export class LoadingIndicator {
    @bindable loading = false;     loadingChanged(newValue) {
        if(newValue) {
            nprogress.start();
        } else {
            nprogress.done();
        }
    }
}

We have defined a bindable property named “loading”, which will be assigned in the containing view. Whenever the value of loading changes, the progress bar will start and and it will be done right after the change is completed.

The containing view would be shell.html because that is where we defined the layout of our application. We would like to see a NProgress progress bar above the top navigation bar whenever the route url changes. We do this by defining the <loading-indicator> element like this:

<nav-bar router.bind="router"></nav-bar>
<loading-indicator loading.bind="router.isNavigating"></loading-indicator>

LoadingIndicator component’s “loading” property is bound to “router.isNavigation” property, which becomes true when Aurelia is navigating from one route to another. Whenever the navigation occurs, a global progress bar is displayed right above the top navigation menu.

Notifications

We’ll use the popular toastr library for UI notifications like alerts, info, warning etc. We need to run these commands to get toastr library and its type definition file:

npm install toastr --save

typings install dt~toastr -- global –save

Using toastr is very straightforward since it does not require a component and can directly be invoked from your ViewModel using toastr.function() syntax. For e.g., to display a success message, you would call it like this:

toastr.success(`Contact info of ${this.contact.firstName} saved successfully!`);
Importance of moment.js for date manipulation

The default date manipulation in JavaScript/TypeScript is very cumbersome and even displaying a formatted date string requires some work. So, let us bring in the powerful moment js library into our project. Install momentjs by running the command:

npm install moment -- save

Moment.js has an issue with its d.ts file because it uses "export = moment" syntax. TypeScript supports default exports that is meant to replace “export =”, which is used by CommonJS and AMD. TypeScript also supports “export =” syntax, which specifies a single object that is exported from the module. We have to use TypeScript-specific import variable = require(“module”) syntax to import the module exported using “export =” syntax.

In order for TypeScript compiler to allow import moment from "moment"; syntax, we should add "allowSyntheticDefaultImports": true in tsconfig.json file.

Bootstrap DatePicker

We’ll use bootstrap datepicker library for showing a calendar control that allows us to select a date instead of entering the date. We need to run these commands to get bootstrap datepicker library and its type definition file:

npm install bootstrap-datepicker --save

typings install dt~bootstrap-datepicker --global –save

Just like what we did with NProgress, we need to define an Aurelia component to wrap Bootstrap DatePicker. In this case, we are going to define a custom element.

date-picker.ts
import {inject, customElement, bindable} from "aurelia-framework";
import "bootstrap-datepicker";
import moment from "moment";

@customElement("date-picker")
@inject(Element)
export class DatePicker {
@bindable value;
@bindable format = "mm/dd/yyyy";

  constructor( private element) {
  }

  private updateAndShowDatePicker() {
    $(".datepicker").datepicker("update", this.value);
    $(".datepicker").datepicker("show");
  }
  dateClicked() {
    this.updateAndShowDatePicker();
  }
  attached() {
    console.log("attached");
    $(this.element).find(".datepicker")
      .datepicker({
        format: this.format,
        startDate: "01/01/1900",
        autoclose: true,
        todayBtn: true,
        showOnFocus: false
      })
      .on("click", (e) =>{
        this.updateAndShowDatePicker();
      })
      .on("changeDate", (e) => {
        this.value = moment(e.date).format("MM/DD/YYYY");
      });
  }
}

Notice the first line where we imported customElement decorator, which is used to decorate the DatePicker class to indicate that it is a custom element. We also inject “Element” which is going to give access to the DOM element that defines the DatePicker in the View. Since Bootstrap DatePicker() leverages jQuery to invoke the datepicker() function with arguments, we’ll wire that function inside the activate() hook, a function in Component lifecycle that fires when our component is attached to DOM. We have defined 2 bindable properties: “value” that passes the selected value from the View to ViewModel and vice versa, “format”, that has the date format to render in the View. We wired the “click” event on the actual DOM element representing the datepicker to update itself i.e., to show the month of the date value we bound to the element from the containing view. Since I disabled the automatic display of the month on focus using “showOnFocus: false”, it needs to be programmatically shown using “show” method. We have also defined a dateClicked() funcion, which would be invoked upon clicking the small icon <span> right next to the <input> text box>. Both the jQuery’s “click” event and the <span> element’s click will use a private helper function updateAndShowDatePicker(). Finally, we wire the custom jQuery event “changeDate()” to convert the selected date value to a proper JavaScript Date object.

date-picker.html
<template>
  <div class="input-group date">
    <input class="form-control datepicker" value.bind="value" data-provide="datepicker">
    <div class="input-group-addon" click.delegate="dateClicked()">
        <span class="glyphicon glyphicon-th"></span>
    </div>
</div>
</template>

Like I mentioned under the date-picker.ts view model, you can notice that I wired the click event on <span> element to invoke “dateClicked()” function.

Contact Detail ViewModel

It is time to focus on Contact Detail ViewModel since we have built the required common UI elements.

contact-detail.ts
import {inject} from "aurelia-framework";
import {areEqual} from "../services/utility";
import * as toastr from "toastr";
import {DialogService} from "aurelia-dialog";
import {MessageBox} from "../resources/dialogs/message-box";
import {IContact} from "./contact";
import {InMemoryContactService} from "../services/in-memory-contact-service"; @inject(DialogService, InMemoryContactService)
export class ContactDetail {
    routeConfig;
    contact: IContact;
    originalContact: IContact;     constructor(private dialogService: DialogService, private contactService: InMemoryContactService) {}     activate(params, routeConfig) {
        this.routeConfig = routeConfig;         return this.contactService.get(params.id).then(contact => {
            this.contact = <IContact>contact;
            this.routeConfig.navModel.setTitle(this.contact.firstName);
            this.originalContact = JSON.parse(JSON.stringify(this.contact));
        });
    }     get canSave() {
        return this.contact.firstName && this.contact.lastName && !this.contactService.isRequesting;
    }     save() {
        this.contactService.save(this.contact).then(contact => {
            this.contact = <IContact>contact;
            this.routeConfig.navModel.setTitle(this.contact.firstName);
            this.originalContact = JSON.parse(JSON.stringify(contact));
            toastr.success(`Contact info of ${this.contact.firstName} saved successfully!`);
        });
    }     async canDeactivate() {
        if(!areEqual(this.originalContact, this.contact)) {
          let result = await this.dialogService.open(
              {viewModel: MessageBox, model: "You have unsaved changes. Are you sure you wish to leave?"})
          .then(response => {
              console.log(response);               if(!response.wasCancelled){
                  return true;
              } else {
                  console.log("cancelled");
              }
              console.log(response.output);
              return false;
          });
          console.log(result);
          return result;
        }
        return true;
    }
}

Notice that we have imported all of those common UI components that we created in the last section.

Constructor

We are injecting DialogService and InMemoryContactService via constructor injection. In the “activate()” lifecycle hook, we use the contactService to get the contact detail relevant to the selected contact’s id in the contact list component. Then, we change the title to the contact’s firstName property. We also store a copy of the contact, which we’ll use to figure out if the contact’s properties have been modified when the use wants to navigate away to another contact or another route.

canSave() property accessor

This method checks if there are any changes to any of the fields in contact as well as make sure that we are not in the middle of an API service request.

save()

This function uses contactService.save() method to store the modified contact in memory and updates the originalContact to track any new changes from there.

canDeactivate()

We leverage the canDeactivate() method, which is a hook in “Screen Activation Lifecycle” to control if we want to navigate away to another route. This is an ideal hook where we can do some quick dirty checking to figure out if our ViewModel has been modified and show a dialog box asking the user if the user wants to save or ignore the changes and take an action based on the response. The DialogService plugin’s open method is used to fire the dialog and get the response. The response.wasCancelled property is true, we stay on the same page; otherwise we navigate away to the selected route.

Contact Detail View

Before we define the Contact Detail View, we need to understand the importance of Aurelia Value converters and how we are going to leverage them in this view.

Adding Aurelia Value Converters

Aurelia value converters are used to transform data during the data-binding process both ways i.e., to and from the view. In our view, we need the dates to be displayed in a friendly format like mm/dd/yyyy but when we want to bind them to the ViewModel, we need to convert them into a JavaScript date. This is a perfect feature to be implemented as a value converter.

DateFormat Value Converter
DateFormat.ts
import moment from "moment"; 
export class DateFormatValueConverter {
    toView(value, format) {
        if(!value) {
            return "N/A";
        }
        if(!format) {
            format = "MM/DD/YYYY";
        }
        return moment(value).format(format);
    }
    fromView(value) {
        return moment(value).toDate();
    }
}

You can easily figure out from the function names that toView() will be invoked when the value property needs to be converted to a format to be displayed in the view and fromView() will be invoked when the entered value in the view needs to be parsed into a JavaScript Date object.

contact-detail.html
<template>
  <div class="panel panel-primary">
    <div class="panel-heading">
      <h3 class="panel-title">Profile</h3>
    </div>
    <div class="panel-body">
      <form role="form" class="form-horizontal">
        <div class="form-group">
          <label class="col-sm-2 control-label">First Name</label>
          <div class="col-sm-10">
            <input type="text" placeholder="first name" class="form-control" value.bind="contact.firstName">
          </div>
        </div>

        <div class="form-group">
          <label class="col-sm-2 control-label">Last Name</label>
          <div class="col-sm-10">
            <input type="text" placeholder="last name" class="form-control" value.bind="contact.lastName">
          </div>
        </div>

        <div class="form-group">
          <label class="col-sm-2 control-label">Email</label>
          <div class="col-sm-10">
            <input type="text" placeholder="email" class="form-control" value.bind="contact.email">
          </div>
        </div>

        <div class="form-group">
          <label class="col-sm-2 control-label">Phone Number</label>
          <div class="col-sm-10">
            <input type="text" placeholder="phone number" class="form-control" value.bind="contact.phoneNumber">
          </div>
        </div>

        <div class="form-group">
        <label class="col-sm-2 control-label">Date of birth</label>
        <div class="col-sm-10">
            <date-picker value.two-way="contact.birthDate | dateFormat"></date-picker>
        </div>
      </div>
      <button type="submit" class="btn btn-primary btn-striped pull-right" click.delegate="save()"
         disabled.bind="!canSave">
          <span class="glyphicon glyphicon-save pull-left"></span>Save
      </button>
      </form>
    </div>
  </div>
</template>

ContactDetail View is a form with form fields for each of those properties in ContactDetail ViewModel. The HTML markup uses Bootstrap styles to make it look nicer. Notice that the dateFormat value converter is applied on contact.birthDate property. The pipe (|) symbol is used to invoke the value converter.

Communication between List and Detail views via messages using Pub/Sub pattern

When we try to connect two components and make them talk to each other, we don’t want to have direct dependencies on each of them. For e.g. we shouldn’t be wiring a click event on ContactList component to invoke a method on ContactDetail component or adding a new Contact in the ContactDetail View to update the ContactList. Both of these components are self-contained i.e. have their own properties and behaviors. The router decides when to show what screen. We can easily decouple the components but yet make them communicate to each other using the pub/sub pattern. Aurelia has implemented the pub/sub pattern using its EventAggregator class. In pub/sub terminology, the messages flow between different parts of the application to communicate changes. So, we need to define our messages that our ContactDetail component can publish and then let the ContactList subscribe to those messages and respond appropriately.

contact-messages.ts
export class ContactUpdated {
    constructor(public contact){}
} export class ContactViewed {
    constructor(public contact) {}
}
Publishing the messages

Now, we need to publish these messages from ContactDetail class. We’ll just import the messages from contact-messages and EventAggregator from “aurelia-event-aggregator”.

import {ContactViewed, ContactUpdated} from "../resources/messages/contact-messages";
import {EventAggregator} from "aurelia-event-aggregator";

We have to inject EventAggregator into ContactDetail’s constructor using @inject decorator and by defining a matching parameter in the constructor.

@inject(DialogService, InMemoryContactService, EventAggregator)
export class ContactDetail {
    …    constructor(private dialogService: DialogService, private contactService: InMemoryContactService, private ea: EventAggregator) {}

Then, publish ContactViewed message in activate() and deactivate hooks:

activate(params, routeConfig) {
       …         return this.contactService.get(params.id).then(contact => {
            …             this.ea.publish(new ContactViewed(this.contact));
        });
    } async canDeactivate() {
        if(!areEqual(this.originalContact, this.contact)) {
          let result = await this.dialogService.open(
              {viewModel: MessageBox, model: "You have unsaved changes. Are you sure you wish to leave?"})
          .then(response => {
              console.log(response);               if(!response.wasCancelled){
                  return true;
              } else {
                  this.ea.publish(new ContactViewed(this.contact));
              }
              console.log(response.output);
              return false;
          });
          console.log(result);
          return result;
        }
        return true;
    }

Finally, publish the ContactUpdated message in save().

save() {
        this.contactService.save(this.contact).then(contact => {
            …             this.ea.publish(new ContactUpdated(this.contact));
            toastr.success(`Contact info of ${this.contact.firstName} saved successfully!`);
        });
    }
Subscribing to the messages

Now, let us focus on how to subscribe to those messages that are published from ContactDetail inside of ContactList class. We’ll just import the messages from contact-messages and EventAggregator from “aurelia-event-aggregator”.

import {ContactViewed, ContactUpdated} from "../resources/messages/contact-messages";
import {EventAggregator} from "aurelia-event-aggregator";

We have to inject EventAggregator into ContactDetail’s constructor using @inject decorator and by defining a matching parameter in the constructor.

@inject(DialogService, InMemoryContactService, EventAggregator)
export class ContactDetail {

We can now subscribe to both the messages: ContactViewed and ContactUpdated inside of the ContactList’s constructor.

constructor(private router: Router, private contactService: InMemoryContactService,
        ea: EventAggregator) {
        ea.subscribe(ContactViewed, msg => this.select(msg.contact));
        ea.subscribe(ContactUpdated, msg => {
            let id = msg.contact.id;
            let found = this.contacts.find(x => x.id === id);
            Object.assign(found, msg.contact);
        });
    }

So, when the message is published the callback will be fires and the ContactList will be updated appropriately. That concludes the client side implementation of our Contact Manager mini-SPA. Now, let us switch to the Server side and implement the Contacts API controller in ASP.NET Core.

 

Server Side implementation of Contact Manager

I’m a firm believer in Domain Driven Design (DDD) for implementing the server side. In this part of the series, I’m not going to show how to properly implement Contact Manager using DDD because that would require a lot of time to explain. Moreover, there are plenty of online resources that explains DDD in detail. In a future part of the series, I’ll show you detailed DDD implementation. But for now, I’m just going to define a simple implementation of DDD using simple Generic Repository interface, a generic Entity base class, Generic InMemory Repository base class and then define the Contact Model class and ContactsRepository class using the base classes and interfaces.

Interfaces and Base Classes

IEntity.cs
using System;
namespace Modern.Models
{
    public interface IEntity<TKey> where TKey: IComparable
    {
        TKey Id { get; set;}
    }
}

Almost all entities that we want to define will have a primary key. So, it makes sense to define a generic interface that takes TKey i.e., type of the key as a parameter. We can then define an abstract base class that inherits this generic interface.

EntityBase.cs
using System;
namespace Modern.Models
{
    public abstract class EntityBase<TKey>  where TKey : IComparable
    {
        public virtual TKey Id { get; set;}
        public static bool operator ==(EntityBase<TKey> entity1, EntityBase<TKey> entity2)
        {
            return ReferenceEquals(entity1, entity2) || (((object)entity1 != null) && entity1.Equals(entity2));
        }
        public static bool operator !=(EntityBase<TKey> entity1, EntityBase<TKey> entity2)
        {
            return !(entity1 == entity2);
        }        public override bool Equals(object obj)
        {
            var entity = obj as EntityBase<TKey>;             return (entity != null) && (entity.GetType() == this.GetType()) && (entity.Id.CompareTo(this.Id) ==0);
        }
        public override int GetHashCode()
        {
            return this.Id.GetHashCode();
        }         public override string ToString()
        {
            return this.Id.ToString();
        }
    }
}

EntityBase is an abstract base class that implements IEntity generic interface and defines the standard operators == and != t for reference comparison and overrides the System.Object’s Equals and GetHashCode() methods for value comparison as well.

Contact Model

Contact.cs
using System;
namespace Modern.Models
{
    public class Contact : EntityBase<int>, IEntity<int>
    {
        public override int Id { get; set;}
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string PhoneNumber { get; set;}
        public DateTime BirthDate { get; set;}
    }
}

Generic Repository Interface

IGenericRepository.cs
using System;
using System.Linq;
using Modern.Models;
namespace Modern.Infrastructure
{
    public interface IGenericRepository<T, TKey> : IDisposable where T :  IEntity<TKey> where TKey : IComparable
    {
        T Get(TKey id);       
        IQueryable<T> GetAll();
        void Add(T entity);
        void Delete(TKey id);
        void Edit(T entity);
        void Save(T entity);
        T[] Search(string keyword);
    }
}

The generic parameter T is the type of the entity this repository handles and TKey is the type of the primary key. For e.g., if you want this repository to handle the Contact class with int as the type of the primary key, then you would declare a variable of type IGenericRepository<Contact, int>.

Generic Repository Implementation Class

Let us define a simple abstract base class that is also a generic implementation class i.e., not specialized to any particular type of entity or primary key.

InMemoryRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Modern.Models;
namespace Modern.Infrastructure
{
    public class InMemoryRepository<T, TKey> : IGenericRepository<T, TKey> where T :  IEntity<TKey> where TKey : IComparable
    {
        protected List<T> DbSet;
       
        public T Get(TKey id)
        {
            return DbSet.Find((item) => (item.Id.CompareTo(id) == 0));
        }         public IQueryable<T> GetAll()
        {
            return DbSet.AsQueryable();
        }
        public virtual void Add(T entity)
        {
            DbSet.Add(entity);
        }
        public virtual void Delete(TKey id)
        {
            DbSet.Remove(this.Get(id));
        }
        public virtual void Edit(T entity)
        {
            DbSet.Remove(entity);
            DbSet.Add(entity);
        }
        public virtual void Save(T entity)
        {
            Edit(entity);
        }
        public virtual void Dispose()
        {         }
        public virtual T[] Search(string keyword)
        {
            throw new NotImplementedException("Search must be implemented by the derived class");
        }
    }
}

This is a very simple in-memory repository that uses C# Generic collection List<T>. We purposely omitted the implementation of Save and Dispose, which can be implemented in the derived classes.

Contact Repository Class

ContactsRepository class is a specialized instance of InMemoryRepository that handles Contact entities with int primary keys.

ContactRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Modern.Models;
namespace Modern.Infrastructure
{
    public class ContactsRepository : InMemoryRepository<Contact, int>
    {
        public ContactsRepository ()
        {
          DbSet = new List<Contact> {
              new Contact { Id = 1, FirstName ="Raja", LastName = "Mani", Email = "rmani@gmail.com", PhoneNumber = "408-973-5050", BirthDate = new DateTime(1973, 5, 1) },
              new Contact { Id = 2, FirstName ="Jhansi", LastName = "Rani", Email = "jrani@gmail.com", PhoneNumber = "408-900-9875", BirthDate = new DateTime(1973, 5, 24) },
              new Contact { Id = 3, FirstName ="Aditi", LastName = "Raja", Email = "araja@gmail.com", PhoneNumber = "408-973-9006", BirthDate = new DateTime(2001, 10, 12) },
              new Contact { Id = 4, FirstName ="Mahati", LastName = "Raja", Email = "mraja@gmail.com", PhoneNumber = "408-973-8007", BirthDate = new DateTime(2006, 2, 15) }
          };
        }

        public override void Save(Contact contact)
        {
            var index = DbSet.FindIndex((c) => c.Id == contact.Id);
            if(index != -1)
            {
                DbSet[index] = contact;
            }
            else 
            {
                DbSet.Add(contact);
            }        
        }

        public override void Add(Contact contact)
        {
            contact.Id = DbSet.Max(c => c.Id) + 1;
            base.Add(contact);
        }

        public override Contact[] Search(string keyword)
        {
            var contact = DbSet.FindAll((c) => (c.FirstName.Contains(keyword) || c.LastName.Contains(keyword)) ).ToArray();
            return contact;
        }
    }
}

We seed this ContactsRepository with 4 contacts as a starting point. We are only doing this for demonstration purposes. In the next part of this series, I’ll show you how this can easily be changed to use Entity Framework Core 1.0 and read/write data from a SQL Server database.

 

Injecting the Generic Repository Interface and Contact Service using ASP.NET Core DI

My preference of IoC (or DI) container is AutoFac. Since I’d like to demonstrate the in-built ASP.NET Core DI feature, I chose not to use AutoFac in this part of the series. We need to make ASP.NET core’s DI container aware of our interfaces and concrete implementations. We do this by adding services.AddScoped() method calls for our interfaces and implementation classes inside of Startup.cs’s ConfigureServices() method.

Startup.cs
public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddMvc();nbsp;           services.AddSingleton(typeof(IGenericRepository<,>), typeof(InMemoryRepository<,>));
            services.AddSingleton(typeof(IGenericRepository<Contact, int>), typeof(ContactsRepository));
        }

We have added both the open ended generic IGenericRepository<,> interface as well as a very specialized IGenericRepository<’Contact,int>. The open ended one will return an instance of InMemoryRepository<,> based on the T and TKey parameters. The closed one will always return an instance of ContactsRepository. We have 3 choices for specifying the life time of the instance to be created by the DI container at the time of the registration. They are “Transient” – the instances are created each time they are requested, “Scoped” – created once per request and “Singleton” – created the first time it is requested and then every subsequent request will use the same instance.

For our ContactsRepository, it makes total sense to use a singleton because there is not point in creating the repository every time.

Using the Generic Repository inside Contacts API Controller

We are going to use the constructor injection to inject ContactsRepository into Contacts API controller.

ContractsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Modern.Infrastructure;
using Modern.Models;
namespace modern.Controllers
{
    [Route("api/[controller]")]
    public class ContactsController : Controller
    {
        private IGenericRepository<Contact, int> _contactsRepository;
        public ContactsController (IGenericRepository<Contact, int> contactsRepository)
        {
          _contactsRepository = contactsRepository;
        }
        // GET api/values
        [HttpGet]
        public IActionResult GetAll()
        {
            return Ok(_contactsRepository.GetAll());
        }            [HttpGet]
        [Route("{id}")]
        public IActionResult Get(int id)
        {
            var contact = _contactsRepository.Get(id);
            if(contact == null) {
                return NotFound(contact);
            }
            else {
                return Ok(contact);
            }
        }         [HttpPost]
        public IActionResult Add([FromBody] Contact contact)
        {           
            _contactsRepository.Add(contact);
            return Ok(contact);
        }
        [HttpPost]
        [RouteAttribute("search")]
        public IActionResult Search([FromBody] string keyword)
        {           
            var contacts = _contactsRepository.Search(keyword);
            if(contacts != null)
            {
                return Ok(contacts);
            }
            else{
                return NotFound("Search yielded no results");
            }           
        }         [HttpDelete] 
        public IActionResult Delete([FromBody] int id)
        {
            _contactsRepository.Delete(id);
            return new NoContentResult();
        }         [HttpPut]
        public IActionResult Edit([FromBody] Contact contact)
        {
            _contactsRepository.Edit(contact);
            return Ok(contact);
        }     }
}

 

Testing the API using Fiddler/PostMan

To test the Web API end points, there are two popular tools: Fiddler – a Windows desktop application, which has been around for a while and Postman – a powerful Chrome plugin, which can also be launched using Chrome launcher. I have both the tools installed on my computer and you can use either one of the tools or both.

Fetching the list of contacts

To fetch the list of contacts from the server, we’ll make a request to http://localhost:5000/api/contacts with the HTTP GET verb like this:

Postman

postman

Fiddler

Composer

fiddler_composer

Inspector

fiddler_response

 

Creating a new contact

To create a new contact, we’ll request the url http://localhost:5000/api/contacts with the HTTP POST verb and the contact as JSON in the HTTP body. To make it easier to avoid typing JSON, you can copy and paste one of the contacts in JSON from the previous request from either Postman or Fiddler and then change the “id” to 0 and remove the birthDate.

postman_post

 

Editing/Updating an existing contact

To edit a new contact, we’ll request the url http://localhost:5000/api/contacts with the HTTP PUT verb and the contact to be modified as JSON in the HTTP body.

postman_update

Searching a contact using keyword

To search the contact list using a keyword, we’ll request the url http://localhost:5000/api/contacts/search with the HTTP POST verb and the search string as JSON in HTTP body. For e.g., if you want to search all contacts with the keyword “Raja” in either firstName or lastName: simply use the string “Raja” in HTTP body.

postman_search

Deleting a contact using id

To delete a contact using it’s, we’ll request the url: http://localhost:5000/api/contacts with the HTTP DELETE verb and the contact id as JSON in HTTP body. For e.g., if you want to delete the contact with id 3, simply use the number 3 in HTTP body.

postman_delete

Replacing InMemoryContactService with ContactService class

We have defined our ASP.NET Web API endpoints and tested them using Postman/Fiddler. Now, let us replace the InMemoryContactService with ContactService class. Instead of deleting in-memory-contact-service from services folder, we’ll just copy and paste that file into contact-service.ts and invoke the ASP.NET Web APIs using the HttpClient class defined in aurelia-fetch-client module. I don’t want to hard code the Web API url in the ContactService class. So, I defined a class to store the configuration like the baseUrl of my Web APIs in config.ts under common folder.

Config.ts
export class Config {
    public static baseApiUrl = "http://localhost:5000/api/";
}
ContactService.ts
import {inject, Lazy} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import {IContact} from "../contact-manager/contact";
import {Config} from "../common/config";
import {IContactService} from "./icontact-service"; // polyfill fetch client conditionally
const fetch = !self.fetch ? System.import('isomorphic-fetch') : Promise.resolve(self.fetch); @inject(HttpClient)
export class ContactService implements IContactService {
private contacts : IContact[] ;
isRequesting = false;     constructor(private httpClient: HttpClient, private message: string = "World") {
        this.init();
    }
    async init() {
        // ensure fetch is polyfilled before we create the http client
        await fetch;
        this.httpClient.configure( (config: any) => {
        config
       .useStandardConfiguration()
       .withBaseUrl(Config.baseApiUrl);
        });
    }     getAll() {
        this.isRequesting = true;
        return new  Promise( async resolve => {
            const http = this.httpClient;
            const response = await http.fetch("contacts");
            this.contacts = await response.json();
            let results = this.contacts;
            resolve(results);
            this.isRequesting = false;
        });
    }     search(keyword: string) {
    this.isRequesting = true;
    return new  Promise( async resolve => {
            const http = this.httpClient;
            const response = await http.fetch("contacts/search", {
              method: "post",
              body: json(keyword)
            });
            this.contacts = await response.json();
            let results = this.contacts;
            resolve(results);
            this.isRequesting = false;
        });
  }   get(id: number) {
    this.isRequesting = true;
    return new  Promise( async resolve => {
            const http = this.httpClient;
            const response = await http.fetch(`contacts/${id}`);
            let foundContact = await response.json();
            let result = foundContact;
            resolve(result);
            console.log(result);
            this.isRequesting = false;
        });
  }   save(contact){
    this.isRequesting = true;
     return new  Promise( async resolve => {
            const http = this.httpClient;
            const response = await http.fetch("contacts", {
              method: "put",
              body: json(contact)
            });
            let result = await response.json();
            resolve(result);
            console.log(result);
            this.isRequesting = false;
        });
    }    create() {
    this.isRequesting = true;
    return new  Promise( async resolve => {
            const http = this.httpClient;
            let newContact = {
              id: 0,
              firstName:"",
              lastName:"",
              email:"",
              phoneNumber:""
            };
            const response = await http.fetch("contacts", {
              method: "post",
              body: json(newContact)
            });
            let justCreatedContact = <IContact> (await response.json());
            resolve(justCreatedContact);
            console.log(justCreatedContact);
            this.isRequesting = false;
        });
  }
  getContactIndex(id: number) : number {
       return this.contacts.findIndex((c:IContact) => c.id === id);
  }   delete(id: number) {
    this.isRequesting = true;
    return new  Promise( async resolve => {
            const http = this.httpClient;
            const response = await http.fetch("contacts", {
              method: "delete",
              body: json(id)
            });
            resolve();
            console.log(response);
            this.isRequesting = false;
        });
  }
}

We have retained the signature of the functions but replace their implementation to invoke the actual ASP.NET Web APIs defined on the server side using the HttpClient class from aurelia-fetch-client module. All API wrapper functions return a Promise so the consumer can invoke them asynchronously as well as handle any errors.

Wiring ContactService into ContactList and ContactDetail ViewModels

We just need to import ContactService and inject it into the ViewModel’s constructor using @inject decorator and constructor injection like this:

contact-list.ts
import {ContactService} from "../services/contact-service";
… @inject(Router, ContactService, EventAggregator)
export class ContactList{
    …     constructor(private router: Router, private contactService: ContactService,
        ea: EventAggregator) { … } … }
contact-details.ts
import {ContactService} from "../services/contact-service";
@inject(DialogService, ContactService, EventAggregator)
export class ContactDetail {
    …     constructor(private dialogService: DialogService, private contactService: ContactService,
        private ea: EventAggregator) {}    … }

Conclusion

In this part of the series, we have covered a lot of ASP.NET Core server side concepts and Aurelia client side concepts. We used those concepts to implement a Contact Manager mini-SPA. In the next part of the series, I’ll show you how we can build the Task Manager mini-SPA. Happy coding!


2 comments:

  1. Amazing series, thank you! Bookmarked your blog, is there going to be a part 6 where you introduce authentication? :)

    ReplyDelete
  2. @Unknown, Thank you for your appreciation. Part 6 will be all about enhancements to the Contact Manager like adding "Picture" field to contact, replacing Bootstrap DatePicker with Telerik Kendo DatePicker, using file input control to upload the "Picture", adding validation and if time permits pagination as well. I'll add a fully working application including ASP.NET Core 1.0 backend, authentication, profile etc., eventually. I haven't decided how many parts will I write yet. Please keep checking the blog and my twitter page.

    ReplyDelete