Tuesday, November 1, 2016

[ASP.NET Core X Angular](6) - Http Module

 Angular   Http Module   MVC6    ASP.NET Core   Web api   Restiful Service




▌Introduction


Remember that in the previous tutorials, we mocked the customer data in the frontend but not the database. We will use Angular2 Http module and ASP.NET core – web api to fulfill the CRUD functions with data stored in database (in this example, it’s sql server).




▌Environment

▋Visual Studio 2015 Update 3
▋Sql Server 2012 R2
▋NPM: 3.10.3                                    
▋Microsoft.AspNetCore.Mvc 1.0.0
▋angular 2: 2.1.0


▌Web api


Create an ASP.NET core Web application as Web api. What we will do before implementing the CRUD functions in frontend:

1.  Enable NLog logging
2.  Enable CORS
3.  Set global json options
4.  Support Entity framework core
5.  Create CRUD api in web api

▋Install packages

1.  NLog.Extensions.Logging (1.0.0-rtm-alpha4)
2.  Microsoft.Extensions.Logging.Filter (1.1.0-preview1-final)
4.  Newtonsoft.Json (9.0.1)
5.  System.Data.SqlClient  (4.3.0-preview1-24530-04) 
   


▋NLog logging

Please refer to this article:


▋CORS

Required package : Microsoft.AspNetCore.Cors (1.0.0)

We will enable the global CORS settings in Startup.cs.
For more detail settings, visit here - Enabling Cross-Origin Requests (CORS)

▋Startup.cs

public void ConfigureServices(IServiceCollection services)
{
   //Step1. Enable CORS
    services.AddCors(options =>
    {
                options.AddPolicy("AllowSpecificOrigin",
                    builder =>
                    builder.WithOrigins("http://localhost:4240") //Or AllowAnyOrigin()
                    .WithMethods("GET", "POST", "PUT", "DELETE") //Or AllowAnyMethod()
                    );
     });

     //Step2. Enable CORS for every MVC actions
     services.Configure<MvcOptions>(options =>
     {
                options.Filters.Add(new CorsAuthorizationFilterFactory("AllowSpecificOrigin"));
     });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
            //…
        
            app.UseCors("AllowSpecificOrigin");
}


If you don’t want to enable a policy on every web api action (http method), ignore step 2. And specify the CORS policy on an action with [EnableCors] attribute like this.

[EnableCors("AllowSpecificOrigin")]
public async Task<HttpResponseMessage> Create([FromBody]Customer cust)
{}

Or use [DisableCors] for security issue.



▋Set global json options

Web api will default return the json object with lower-Camel-case property names, even if the properties are in Pascal case.


For example, my class’s properties are Pascal case,

public class Customer
{
        public int Id { get; set; }
        public string Name { get; set; }
        public string Phone { get; set; }
        public int Age { get; set; }
        public string Description { get; set; }
}

But web api returns lower Camel case.






So we add the following configuration to solve this case.

▋Startup.cs

public void ConfigureServices(IServiceCollection services)
{
            services.AddMvc().AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                options.SerializerSettings.Formatting = Formatting.None; //or Formatting.Indented for readability;
            });
           
            //…
}

See the updated result.





▋Support Entity framework core


Have a look at these related articles.



▋DAO

public class Customer
{
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        [StringLength(100)]
        [Required]
        public string Name { get; set; }
        [StringLength(100)]
        public string Phone { get; set; }
        public int Age { get; set; }
        [StringLength(200)]
        public string  Description { get; set; }
}



▋DbContext

public class NgDbContext : DbContext
{
        public NgDbContext(DbContextOptions options) : base(options)
        {}
        public DbSet<Customer> Customers { get; set; }
}


▋DAO CRUD methods in CustomerService

public class CustomerService:IDisposable
    {
        private NgDbContext _dbContext = null;

        public CustomerService(NgDbContext dbContext)
        { 
                this._dbContext = dbContext;
        }

        public IQueryable<Customer> GetAll()
        {
            return this._dbContext.Customers;
        }

        public IQueryable<Customer> Get(Expression<Func<Customer, bool>> filter)
        {
            return this._dbContext.Customers.Where(filter);
        }

        public void Add(Customer entity)
        {
            this._dbContext.Customers.Add(entity);
            this._dbContext.SaveChanges();
        }

        public void Update(Customer entity)
        {
             this._dbContext.SaveChanges();
        }

        public void Remove(Customer entity)
        {
            this._dbContext.Customers.Remove(entity);
            this._dbContext.SaveChanges();
        }
}


Okay, everything is done. We are going to implement the CRUD http methods in web api.


▋Create CRUD api in web api

Okay, the following is a normal web api controller.
Notice in ASP.NET core MVC model binding, we MUST specify the binding source, like [FromBody], [FromHeader] , [FromForm], [FromQuery] , [FromRoute].

PS. I was stuck in this issue for hours L


[Route("api/Basic/[controller]")]
public class CustomerController : BaseController
{
        [HttpGet("GetAll")]
        public IQueryable<Customer> GetAll()
        {
            throw new NotImplementedException();
        }

        [HttpGet("Get/{id}")]
        public Customer Get(int id)
        {
           throw new NotImplementedException();
        }
       
        [HttpPost("Create")]
        public async Task<HttpResponseMessage> Create([FromBody]Customer cust)
        {
            throw new NotImplementedException();
        }

        [HttpPut("Update")]
        [CustomExceptionFilter]
        public async Task<HttpResponseMessage> Update([FromBody]Customer cust)
        {
            throw new NotImplementedException();
        }

        [HttpDelete("Remove/{id}")]
        [CustomExceptionFilter]
        public async Task<HttpResponseMessage> Remove(int id)
        {
            throw new NotImplementedException();
        }
}




▌Angular2 : Http Module


Now we have everything in backend, we will start implementing the CRUD functions in frontend.

▋Create CRUD Service


▋/app/Basic/Customer/customer.app.module.ts

Import HttpModule from @angular/http.


//...
import { HttpModule } from '@angular/http';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        CustomerRoutes,
        HttpModule
    ],
    //...
})
export class CustomerAppModule { }




▋/app/Basic/Customer/customer.service.ts

import {Injectable} from '@angular/core';
import { Http, Headers, RequestOptions  } from '@angular/http';
import {Customer} from '../../class/Customer';
import {ICrudService} from '../../interface/ICrudService';
import {RestUriService} from '../../service/resturi.service';

@Injectable()
export class CustomerService implements ICrudService {

    private customers: Customer[];
    private httpOptions: RequestOptions;

    constructor(
        private http: Http,
        private resturiService: RestUriService
    ) {
       this.customers = [];

       let headers = new Headers({ 'Content-Type': 'application/json' });
       this.httpOptions = new RequestOptions({ headers: headers });
}

//Get all customers
public getAll() {
    return new Promise<Customer[]>(
        resolve => {
        this.http.get(this.resturiService.customerGetAllUri)
            .subscribe(value => {
                Object.assign(this.customers, value.json());
                resolve(value.json());
            });
    });
}

//Get a customers with Id
public get(id: number) {
    return new Promise<Customer>(
        resolve => {
        this.http.get(this.resturiService.customerGetUri.concat(id.toString()))
            .subscribe(value => {
                resolve(value.json());
            });
    });
}

//Create a new customer
public create(item: Customer) {

    var entity = JSON.stringify(item);

    return new Promise(
        resolve => {
            this.http.post(this.resturiService.customerCreateUri, entity, this.httpOptions)
                .subscribe(() => {
                    resolve();
                });
        });
}

//Update a customer
public update(item: Customer) {

    return new Promise(
        resolve => {
            this.http.put(this.resturiService.customerUpdateUri, item, this.httpOptions)
                .subscribe(value => {
                    resolve();
                });
        });
}

//Remove a customer
public remove(item: Customer) {
    return new Promise(
        resolve => {
this.http.delete(this.resturiService.customerRemoveUri.concat(item.Id.toString()))
                .subscribe(value => {
                    resolve();
                });
        });
}


PS. In angular 2, the standard http client functions is written like this:

▋Http GET

public get(id: number) {
    return new Promise<Customer>(
        resolve => {
        this.http.get("http://localhost:7856/api/Basic/Customer/Get/"+id.toString()))
            .subscribe(value => {
                resolve(value.json());
            });
    });
}


▋Http Post

let headers = new Headers({ 'Content-Type': 'application/json' });
let httpOptions = new RequestOptions({ headers: headers });

var entity = JSON.stringify(item);
return new Promise(
    resolve => {
        this.http.post("http://localhost:7856/api/Basic/Customer/Create", entity, httpOptions)
            .subscribe(() => {
                resolve();
            });
    });



▋Read (Inquery)

Remove the const customers’ data and we are going to get the customers’ data with our new CustomerService.

▋/app/Basic/Customer/customer.index.component.ts

import {CustomerService} from './customer.service';
//...

@Component({
    selector: 'customer-index',
    providers: [CustomerService],
    templateUrl: '/app/Basic/Customer/customer-index.component.html'
})

export class CustomerIndexComponent implements OnInit {
   
    //...
    customers: Customer[];

    constructor(
        private router: Router,
        private custService: CustomerService) {
        }

    ngOnInit() {
        this.initCustomers();
    }

    private initCustomers() {
        this.custService.getAll().then(
            data => {
                this.customers = data
            });
    }

}


▋Delete

▋/app/Basic/Customer/customer.index.component.ts

Add the following function for deleting a customer.

//Remove customer
private deleteCustomer(item: Customer) {

        this.custService.remove(item).then(
                () => {
                    //Remove item in Front-end
                    var index = customers.indexOf(item);
                    this.customers.splice(index, 1);
                });
}



▋Create

▋/app/Basic/Customer/customer.create.component.ts

Complete the create function in Customer Create component.

private save() {

    this.custService.create(this.customer).then(
        () => {
                //Return to Index after finished
                this.router.navigate(['Basic/Customer/Index']);
        });
}




▋Update

▋/app/Basic/Customer/customer.edit.component.ts

Complete the Customer Edit component by querying cutomer when ngOnInit and update customer on saving.

//...
import {CustomerService} from './customer.service';

@Component({
    selector: 'customer-edit',
    providers: [CustomerService, RestUriService],
    templateUrl: '/app/Basic/Customer/customer-edit.component.html',
    styleUrls: ['/app/Basic/Customer/customer-edit.component.css']
})

export class CustomerEditComponent implements OnInit {
    title: string;
    customer: Customer;
    selectedCustomer: Customer;
    constructor(
        private router: Router,
        private route: ActivatedRoute,
        private custService: CustomerService) {
            this.title = "Customers - Edit";
            this.customer = new Customer();
        }

    ngOnInit() {
        this.route.params.subscribe(params => {
            let custIdValue = params['id'];
            let custId = +custIdValue; //Equales to parseInt

            this.custService.get(custId).then(
                data => {
                    this.customer = data
                });
        });
    }

    //Save!
    private save() {
        this.custService.update(this.customer);
    }
}





▌Demo










▋What’s next?

In the next tutorial, we will integrate SignalR to create real-time UX.



▌Github








▌Reference



No comments:

Post a Comment