Connect Angular with WebAPI

--- ---

Connect Angular with Web API

In this exercise we’re creating a DotNet Web API service that uses Entity Framework to connect to a MS SQL Server instance to get some data, we’re also creating an Angular app to display data from this service, in the process we’re including an Http interceptor to fix the server’s url for every request and adding some Http error handling.

Requirements

Works with

  • Dot NET Framework 4.8
  • Microsoft Asp Net Web API 5.2.7
  • Entity Framework 6.4.4
  • Visual Studio 2019

Steps

Web API project

  1. Create an ASP Net Web API project, and for this tutorial:
    • Don’t enable Windows authentication
    • Don’t enable HTTPS
    • If asked, include MVC and Web API references

Entity Framework

  1. Install EntityFramework, in the Nuget Package Manager Console run:
    install-package entityframework
    
  2. Let’s create the Employee entity. Create a folder and name it Entitities
  3. Inside Entities create a file and name it Employee.cs
  4. In Employee.cs add this code:
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
    
  5. Create the DbContext. Inside Entities create a file and name it DataContext.cs
  6. Inside DataContext.cs include this code
    using System.Data.Entity;
    ...
    public class DataContext : DbContext
    {
        public DbSet<Employee> Employees { get; set; }
    }
    
  7. We’ll need to include the connection string inside the web.config file. Open web.config and include a connectionStrings element with a connection string inside at the root level of the xml, modify the connection string to fit your development environment:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    	...
    	  <connectionStrings>
    	    <add name="DataContext"
    	       providerName="System.Data.SqlClient"
    	       connectionString="Data Source=(local)\SQLEXPRESS;Initial Catalog=AngularWebAPITestDB;Integrated Security=True"/>
    	  </connectionStrings>
    </configuration>
    
  8. To enable the Entity Framework migrations: In the console run
    enable-migrations
    
  9. Add the first migration. In the console run:
    add-migration "initial migration"
    
  10. To create the database in SQL Server run:
    update-database
    
  11. Verify in your SQL Management Studio that the database was created correctly
  12. In order to do some tests later, manually insert a record into the Employees table

Add a Controller

  1. Create the Employees controller by right clicking the Controllers folder and selecting Add Controller

  2. In the Add new Scaffolded Item window, select Web API 2 Controller - Empty

  3. In Controller name, write Employees Controller

  4. Open EmployeesController.cs and alter it like this:

    
    using YOUR_NAMESPACE.Entities;
    using System.Threading.Tasks;
    ...
    
    [RoutePrefix("api/Employees")]
    public class EmployeesController : ApiController
    {
    
        [HttpGet]
        [Route("GetEmployees")]
        public async Task<IHttpActionResult> GetEmployeesAsync()
        {
            try
            {
                using (DataContext db = new DataContext())
                {
                    return Ok(await db.Employees.ToListAsync());
                }
            }
            catch (Exception ex)
            {
                return InternalServerError(ex);
            }
        }
    }
    

    In this code we’re creating the Employees Controller with one action that retrieves a list of employees using Entity Framework.

    • Fix reference for YOUR_NAMESPACE.Entities
    • RoutePrefix and Route: We’re setting the routing of the controller and action by using attributes. Read Attribute Routing in WebAPI 2

Setup WebApi Routing

  1. Run the app, and try to call the action by pasting this in the browser (don’t forget change the port)
    http://localhost:[port]/api/Employees/GetEmployees
    
  2. You’ll get this error:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <Error>
    <Message>No HTTP resource was found that matches the request URI 'http://localhost:58780/api/Employees/GetEmployees'.</Message>
    <MessageDetail>No action was found on the controller 'Employees' that matches the request.</MessageDetail>
    </Error>
    
    The browser can’t find the Employees Controller
  3. Let’s adjust the routing in Web API, open App_Start/WebApiConfig.cs and change:
    ...
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional}
            );
    ...   
    
    to this
    ...
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new
                {
                    action = RouteParameter.Optional,
                    id = RouteParameter.Optional
                }
            );
    ...   
    
    Here we’re telling Web API to consider the action as a component of the route to be part of the requests calls
  4. Run the app and try again
    http://localhost:[port]/api/Employees/GetEmployees
    
  5. You should get some XML like this:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <ArrayOfEmployee xmlns="http://schemas.datacontract.org/2004/07/Angular_EF_Web_API.Entities" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Employee>
    <Email>jdoe@email.com</Email>
    <Id>1</Id>
    <Name>Juan Doe</Name>
    </Employee>
    </ArrayOfEmployee>
    

Return Json instead of XML

  1. For this tutorial I prefer using JSON instead of XML, so let’s ask WebAPI to send JSON. Open App_Start/WebApiConfig.cs* and include these lines:
     using System.Net.Http.Headers;
     ...
    
     public static void Register(HttpConfiguration config)
        {
            
            config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
            config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
            ...
            
    
  2. Run you app again
    http://localhost:[port]/api/Employees/GetEmployees
    
    You should see the same data, but now in Json format
    [{"Id":1,"Name":"Juan Doe","Email":"jdoe@email.com"}]
    

Enable CORS

Since the client app and the web service run from different domains, we will need to enable CORS in the web service
25. To enable CORS follow this tutorial.

Angular App

  1. Create the Angular app in a separate folder by running in your terminal:
    ng new angular-app
    

Add Http support

  1. Open src/app/app.module.ts and import the Angular HttpClientModule.
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { HttpClientModule } from '@angular/common/http';
    @NgModule({
    imports: [
            BrowserModule,
            // import HttpClientModule after BrowserModule.
            HttpClientModule,
        ],
    declarations: [
            AppComponent,
        ],
    bootstrap: [ AppComponent ]
    })
    export class AppModule {}

Create a model

  1. Create the folder src/app/models
  2. Inside src/app/models create a file and name it employee.ts
  3. Add this code in employee.ts
    export interface Employee {
    Id: number;
    Name: string;
    Email: string;
    }
    

Employee service

  1. Create the folder src/app/services

  2. In the terminal navigate to src/app/services and create the EmployeesService

    ng g service Employees
    
  3. Inside employees-service.ts include this code:

    import { HttpClient} from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { Employee } from '../models/employee';
    import { catchError, retry } from 'rxjs/operators';
    import { HttpErrorHandler } from './http-error-handler.service';
    
    
    @Injectable({
    providedIn: 'root'
    })
    export class EmployeesService {
    
    constructor(
        private httpClient: HttpClient,
        private httpErrorHandlerService: HttpErrorHandler) {
    }
    
    url = 'api/employees/';
    
    
    getEmployees = (): Observable<Employee[]> => {
    
        return this.httpClient.get<Employee[]>(
        this.url + 'GetEmployees'
        )
        .pipe(
            // retry 3 times
            retry(3),
            catchError(this.httpErrorHandlerService.handleError)
        );
    }
    }
    
    
  4. You’ll have to provide the service by adding an entry into the providers array in app.module.ts

    providers: [
        httpInterceptorProviders,
        HttpErrorHandlerService,
        EmployeesService
    ],
    

Employees component

  1. Create folder src/app/components
  2. In your terminal, navigate to src/app/components and run
    ng g component Employees
    
    The employees component will be created
  3. Replace the code in employees-component.html
    <p>Employees</p>
    <ul>
        <li *ngFor="let employee of Employees">
            {{employee.Name}}
        </li>
    </ul>
    
  4. Replace the code in employees-component.ts
    import { EmployeesService } from './../../services/employees.service';
    import { Component, OnInit } from '@angular/core';
    import { Employee } from 'src/app/models/employee';
    
    @Component({
    selector: 'app-employees',
    templateUrl: './employees.component.html',
    styleUrls: ['./employees.component.css']
    })
    export class EmployeesComponent implements OnInit {
    
    Employees: Employee[];
    headers: any;
    constructor(private employeeService: EmployeesService) { }
    
    ngOnInit(): void {
        this.employeeService.getEmployees()
        .subscribe((emps:Employee[]) => {
            this.Employees = emps;
        });
    }
    }
    

Routing

Add routing support in the angular app

  1. Add src/app/app-routing.module.ts and include this code

    import { EmployeesComponent } from './components/employees/employees.component';
    import { AppComponent } from './app.component';
    import { NgModule, Component } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    const routes: Routes = [
    { path: '*', component: AppComponent },
    { path: 'employees', component: EmployeesComponent }
    ];
    
    @NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
    })
    export class AppRoutingModule { }
    
  2. Import the routing module in app.module.ts

    ...
    imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ],
    ....    
    
  3. Replace the code in app.component.html with this

    <a [routerLink]="['/employees']" routerLinkActive="active">Employees</a>
    <router-outlet></router-outlet>
    

Error handling in Angular

To implement some error handling, follow the recommendations provided in the Angular HTTP guide

  1. In your terminal navigate to src/app/services and create a new service
    ng g service HttpErrorHandler
    
  2. Include this code in http-error-handler-service.ts
    import { HttpErrorResponse } from '@angular/common/http';
    import { EventEmitter, Injectable } from '@angular/core';
    import { Observable, of, throwError } from 'rxjs';
    
    @Injectable({
    providedIn: 'root'
    })
    export class HttpErrorHandler {
    constructor() { }
    public onError: EventEmitter<string> = new EventEmitter<string>();
    
    handleError = (error: HttpErrorResponse) => {
        if (error.error instanceof ErrorEvent) {
        // A client-side or network error occurred. Handle it accordingly.
        console.error('An error occurred:', error.error.message);
        } else {
        // The backend returned an unsuccessful response code.
        // The response body may contain clues as to what went wrong.
        console.error(
            `Backend returned code ${error.status}, ` +
            `Error body next:`);
        console.error(error.error);
    
        }
        // Return an observable with a user-facing error message.
        this.onError.emit('Something bad happened; please try again later.');
        return throwError(
        'Something bad happened; please try again later.');
    }
    }
    
  3. Provide this service in app.module.ts
    ...
         providers: [
        httpInterceptorProviders,
        HttpErrorHandler
        ],
    ...        
    
Global error message

Lets configure the app so anytime there’s an Http error in any of the services, a generic friendly error to the user is shown.
Actually, in the Angular - HTTP Sample the errors are ignored, I think we should show some kind of error to the end user, in this case we show an alert, but in a real project you would add a CSS popup or something.
20. Open app.component.ts and alter it like this

import { HttpErrorHandler } from './services/http-error-handler.service';
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private httpErrorHandler: HttpErrorHandler) {
}

ngOnInit(): void {
    this.httpErrorHandler.onError.subscribe((err) => {
    alert(err);
    });
}
}

Http Interceptor to set the server URL in every request

We haven’t specified the server’s URL anywhere, we could create a service to provide the url to all the data services (like EmployeesService), we could repeat the URL in each data service, and there might be many ways to achieve this, for this example, we’re going to use an HTTP Interceptor that will intercept each HTTP request and will attach the server’s URL before it gets sent to the server.
21. Create the folder src/app/http-interceptors
22. Inside this folder create the file server-url-interceptor.ts and include this code

import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
/*
Intercepts the HTTP request to fix the server URL
https://angular.io/guide/http#intercepting-requests-and-responses
*/
@Injectable()
export class ServerURLInterceptor implements HttpInterceptor {
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Server API url
    const serverUrl = 'http://localhost:58780/';
    /*
    * The verbose way:
    // Clone the request and replace the original headers with
    // cloned headers, updated with the authorization.
    const authReq = req.clone({
    headers: req.headers.set('Authorization', authToken)
    });
    */
    // Clone the request and set the new header in one step.
    const authReq = req.clone({
    url: serverUrl + req.url
    });
    // send cloned request with header to the next handler.
    return next.handle(authReq);
}
}

Don’t forget to replace the port in the server’s URL

  1. You’ll need to provide the Http interceptor to the app, create the file src/app/http-interceptors/index.ts and include this code:

    /* "Barrel" of Http Interceptors */
    /*https://stackblitz.com/angular/oevaymgooko?file=src%2Fapp%2Fhttp-interceptors%2Findex.ts*/
    
    import { HTTP_INTERCEPTORS } from '@angular/common/http';
    
    import { ServerURLInterceptor } from './server-url-interceptor';
    
    
    /** Http interceptor providers in outside-in order */
    export const httpInterceptorProviders = [
    { provide: HTTP_INTERCEPTORS, useClass: ServerURLInterceptor, multi: true }
    ];
    
  2. Open app.module.ts and add an entry for httpInterceptorProviders in the providers array

    ...
      providers: [
    httpInterceptorProviders
    ],
    ...
    
  3. Run both the web service and the angular app, navigate to employees, you should see the data

  4. Simulate an error from the service and run again, you should get an error alert, press F12 to see the error details in the console

    [HttpGet]
    [Route("GetEmployees")]
    public async Task<IHttpActionResult> GetEmployeesAsync()
    {
        try
        {
            using (DataContext db = new DataContext())
            {
                return InternalServerError(new InvalidProgramException("FAKE ERROR"));
                return Ok(await db.Employees.ToListAsync());
            }
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }
    


    I think we’re getting the error messages 4 times, because we’re retrying the request in the employees service

Keep playing with Angular, post an employee, should feel very straightforward

References

Source Code

https://github.com/atorres16/angular_webapi_ef_errorhandling

Comments

  1. Thanks for sharing such worthy content, this information is useful for knowledge seekers. Waiting for a more upcoming post like this.
    AngularJS Course in Chennai
    AngularJS Online Training
    AngularJS Course in Coimbatore

    ReplyDelete

Post a Comment

Popular posts from this blog

WPF - Combobox with a null item

Add Cordova and Electron to an Angular App

How to enable CORS between an Angular app and an ASP.Net Web API Service