2016年11月1日 星期二

[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



沒有留言:

張貼留言