Sunday, October 23, 2016

Building a web application using ASP.NET Core 1.0, Node.Js, Express/Hapi/Restify, TypeScript and Visual Studio Code - Part 6, Sec 3

Introduction

YouTube Video Link for Part 6, Section 1:

YouTube Video Link for Part 6, Section 2:

YouTube Video Link for Part 6, Section 3:

This is the third section of the sixth part of Building a web application using ASP.NET Core 1.0, Node.Js, Aurelia, TypeScript, Webpack in Visual Studio Code.

The source code for this tutorial is available on GitHub.

In this section of part 6, I’ll show how to enable CORS (Cross Origin Resource Sharing) on the ExpressApplication and  HapiApplication. Then, I’ll show how to include both Asp.Net Core and NodeJs backend REST API implementations to work with a single copy of front end Aurelia client code in a single folder structure.  Finally, I’ll show you how you can use either Asp.Net Core or NodeJs as your REST API endpoints by changing a configuration value in the front end code.

 

Enabling CORS in ExpressApplication and HapiApplication

Since our client application’s port and our server side REST API’s port will be different, we need to enable CORS to support cross-origin requests. We have already done this in our RestifyApplication class but we need to enable CORS in both ExpressApplication and HapiApplication.

To allow CORS, we need to add the headers “Access-Control-Allow-Origin”, “Access-Control-Allow-Headers” and “Access-Control-Allow-Methods”  to the allows origins like “http://localhost:9000”, allowed headers like “Origin, Accept, Authorization” and allowed HTTP methods/verbs like “GET, POST, PUT, DELETE” etc. For demonstration purposes, we can use “*” as the value to allow any origin, any header or any method, which is not recommended for production deployment. When I talk about production deployment in later part of the series, I’ll show how to restrict them to only the absolutely required origins, headers and HTTP Verbs.

Instead of manually setting these headers and their values, let us leverage the cors module for expressjs. To install the cors module for expressjs and its typescript typing definition, run the commands:

npm install cors --save
typings install dt~cors --save --global

We need to import the cors module like this:

import * as cors from "cors";

Let us refactor the constructor of ExpressApplication and add CORS support into the bootstrap method like this:

express-application.ts
 
    this.expressApplication.use(cors(), (request: express.Request, response: express.Response, next: express.NextFunction) => {
        console.log("CORS enabled for all routes");
        next();
    });

    // configure API and error routes   
    this.expressContactRouter.configApiRoutes(this.expressApplication);
    this.expressContactRouter.configErrorRoutes(this.expressApplication);
    this.expressApplication.set("port", port);
    ...
}

 

Let us refactor the constructor of HapiApplication and add CORS support into the bootstrap method like this:

hapi-application.ts
public bootstrap(port: number) {
    this.hapiApplication.connection({port: port, routes: { cors: true }});
    ...

Let us refactor the constructor of RestifyApplication and move CORS support into the bootstrap method like this:

restify-application.ts
public bootstrap(port: number) {
    this.restifyApplication.use(restify.CORS());
    this.restifyApplication.use(restify.pre.sanitizePath());
    this.restifyApplication.use(restify.acceptParser(this.restifyApplication.acceptable));
    this.restifyApplication.use(restify.bodyParser());
    this.restifyApplication.use(restify.queryParser());
    this.restifyApplication.use(restify.authorizationParser());
    this.restifyApplication.use(restify.fullResponse());

    // configure API and error routes
    this.restifyContactRouter.configApiRoutes(this.restifyApplication);
    this.restifyContactRouter.configErrorRoutes(this.restifyApplication);
    ...
}

Notice that I removed the line restify.CORS.ALLOW_HEADERS.push("authorization"); because we don’t need to add this header explicitly. We’ll revisit this header when I talk about authentication and authorization in a later part of this series.

 

Generating UML class diagrams from TypeScript modules

 

You can generate UML diagram from TypeScript modules using a simple tool named tsviz. The github repository for this tool is https://github.com/joaompneves/tsviz. To install the tool use the command:

npm install tsviz -g

To run the tool and create a diagram for an entire project, you use the command:

tsviz server/src/contact-manager/ -recursive contact-manager-class-diagram.png 

Modify the route for GET method to take an optional id parameter

Since the Aurelia client application will need to pass an optional id parameter to the GET endpoint, let us add that optional parameter to all three router classes.

Let us modify the configApiRoutes() method in ExpressContactRouter like this:

express-contact-router.ts
"use strict";
configApiRoutes(expressApplication: express.Application) {
        this.contactRouter.route("/contacts/:id?")
        .get((request: express.Request, response: express.Response) => {
            if(request.params.id) {
                this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                    response.json(contact);
                });
            } else {
                this.contactService.getAll().then((contacts : IContact[]) => {
                    response.json(contacts);
                });
            }
        })
	...
}

 

Let us modify the configApiRoutes() method in HapiContactRouter like this:

  public configApiRoutes(hapiApplication: Hapi.Server) {
      var endpoint = "/api/contacts/{id?}";
        hapiApplication.route({
            path: endpoint,
            method: "GET",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
                if(request.params.id) {
                    this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                        reply(contact);
                    });
                } else {
                    this.contactService.getAll().then((contacts : IContact[]) => {
                        reply(contacts);
                    });
                }
            }});
	...
}

We need to add an additional endpoint that takes id parameter inside the configApiRoutes() method in RestifyContactRouter like this:

public configApiRoutes(restifyApplication: restify.Server) {
      let endpoint = "/api/contacts";
        restifyApplication.get(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
        {
            this.contactService.getAll().then((contacts : IContact[]) => {
                response.json(contacts);
                return next();
            });
        }));

        ...

        let idEndpoint = `${endpoint}/:id`;
        restifyApplication.get(idEndpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
        if(request.params.id) {
            this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                response.json(contact);
                return next();
            });
        }

 

Merging source code from part5 into part6 to have both Asp.Net Core and NodeJs backend APIs with a single Aurelia front end source code

We are interested to keep our ASP.NET Core backend and the Node.js backend server code in the same project structure with the Aurelia client code shared between them. We need to first bring the source code from part5 into part6 using the following steps:

1. Create a client folder under part6\modern folder

2. Copy part5/modern/src folder into part6/modern/client folder

3. Copy all files and folders in part5/modern except the src folder into part6/modern folder

merge-part5-and-part6-source

4. We need to use the symbolic link to share the client code between Asp.Net core and NodeJs projects. Check out http://www.howtogeek.com/howto/windows-vista/using-symlinks-in-windows-vista/. We need to run the following command from within the part6/modern/nodejs folder:

cmd /c 'mklink /J client "../client"'

5. Make sure to exclude “client” folder in the “exclude” setting in bunder nodejs folder like this:

"exclude" : [
        "client",
        "node_modules"
    ]

6. Make sure to change the srcDir variable in webpack.config.js to point to ‘client/src’ like this:

const srcDir = path.resolve('client/src');

7. Change the webpack version in package.json

"webpack": "2.1.0-beta.22"

8. Run the following commands from part6\modern folder

npm install
typings install

9. Make sure to install TypeScript locally using the command:

npm install typescript@2.0 --save-dev

10. Make sure to exclude “nodejs” folder in tsconfig.json like this:

"exclude": [ 
	"nodejs", 
	...
]

 

11. Change the relative path in the import statements from line 3 to 8 in client/src/main.ts like this:

import "../../styles/styles.css";
import "font-awesome/css/font-awesome.css";
import "bootstrap/dist/css/bootstrap.css";
import "../../styles/bootstrap-flat.css";
import "../../styles/bootstrap-flat-extras.css";
import "../../styles/awesome-bootstrap-checkbox.css";

12. Run the client application using the command:

npm start

 

 

Running both Asp.Net Core 1.0 and NodeJs REST Service from 2 instances of VS Code

If we want to run multiple task runners from within a single instance of VS Code, we could do so using tasks.json and assigning custom key combinations to launch TypeScript compiler, DotNet compiler, npm, gulp etc. VS Code has launch.json to launch one type of application with “request” types like “launch” and “attach”. At the time of this writing, VS Code doesn’t allow us to define mulitple launch configurations say one for .NET Core and another for NodeJs. You can have one or another with both “request” types like “launch” and “attach” but not both project types with both “request” types.

 

The best way that I know of to run both Asp.Net Core and NodeJs applications within a single folder structure is to put them in different sub folders and launch 2 instances of VS Code.VS Code treats the folder from where you launch VS Code as the project folder. Technically, any folder where you have a .vscode folder is a project because .vscode folder is where you define tasks.json, launch.json and settings.json. Since we can’t and shouldn’t duplicate the client source code, we have already created a symlink inside of NodeJs folder pointing to the client source code within Asp.Net project folder. So, we don’t have to worry about maintaining 2 copies of the client code because synchronizing them would become a nightmare.

We should open up an instance of VS Code from part6/modern to debug the Asp.Net Core REST API as well as launching the Aurelia client web application. We should open up an instance of VS Code from part6/modern/nodejs to debug the NodeJs REST API.

 

Configuring the client code to use Asp.Net Core 1.0 or NodeJs REST Service endpoint

To configure the client code to user Asp.Net Core 1.0 or NodeJs REST Service endpoint, we’ll use the “baseApiUrl” variable under client/src/common/config.ts file. To use Asp.Net Core 1.0 REST Service endpoint, use this:

public static baseApiUrl = "http://localhost:5000/api/";

To use Asp.Net Core 1.0 REST Service endpoint, use this:

public static baseApiUrl = http://localhost:8080/api/;

 

Changing the implementation of Delete endpoint in Asp.Net core to take a JSON object

In my first implementation, I was sending the id of the contact to be deleted in the HTTP body to the Delete endpoint. Let us change it to a JSON object like this:

{ id: 1 }

Let us define a model class named DeletePayload under Models folder like this:

namespace Modern.Models
{
    public class DeletePayload
    {
        public int id { get; set; }
    }
}

Change the signature and implementation of Delete() method in ContactsController to take a DeletePayload object like this:

[HttpDelete]  
public IActionResult Delete([FromBody] DeletePayload payload) 
{
    _contactsRepository.Delete(payload.id);
    return new NoContentResult();
} 

 

Changing the implementation of delete method in ContactService class

Let us change the implementation of delete method in ContactService under client/src/services/ to send the deletePayload json object like this:

delete(id: number) {
    this.isRequesting = true;
    return new  Promise( async (resolve: any) => {
            const http = this.httpClient;
            let deletePayload = { "id": id};
            const response = await http.fetch("contacts", {
              method: "delete",
              body: json(deletePayload)
            });
            let justDeleteContact =  (await response.json());
            resolve(justDeleteContact);
            console.log(justDeleteContact);
            this.isRequesting = false;
        });
  }

Running the Aurelia client app with Asp.Net Core and NodeJs

Since we have launched 2 instances of VS Code, you can run the REST application using F5 key. Now, both instances of VS Code will be waiting to serve the requests coming from the client application. First, you can change the port number of baseApiUrl variable in config.ts to 8080 to use NodeJs. You can launch the client application from the browser using the url http://localhost:9000 . You can test different endpoints from the UI by selecting a contact, removing a contact, modifying a contact and by adding a new contact. After you have tested the NodeJs REST API endpoints, you can switch the port number of baseApiUrl variable in config.ts to 5000 to use Asp.Net Core 1.0. You’ll repeat the same set of operations from the UI to test the Asp.Net Core 1.0 REST API endpoints.

 

Conclusion

In section 3 of part 6, we have seen how to bring both Asp.Net Core 1.0 and NodeJs backend REST Service implementation into a single folder structure and leveraging the same Aurelia client application source code. Now, we can debug Asp.Net Core 1.0 and NodeJs backend REST Service implementation from 2 instances of VS Code but leveraging the same client side code. We are going to use both Asp.Net Core 1.0 and NodeJs backends in the future parts. This section concludes part 6. Happy Coding!

No comments:

Post a Comment