前幾天,按照AngularJS2的英雄指南教程走了一遍,教程網址是http://origin.angular.live/docs/ts/latest/tutorial/。 json
在步驟完成後,又更進一步,在英雄增刪改的時候,直接調用.net core的WebApi來實現後臺數據的操做,替換教程中的模擬WebApi方式。在替換.net core WebApi時,仍是遇到了一些坑的,這裏記錄一下。 api
先來看一下WebApi和AngularJS的源代碼: 跨域
WebApi app
1 [Route("api/[controller]")] 2 public class ValuesController : Controller 3 { 4 private List<Hero> heroes; 5 6 public ValuesController() 7 { 8 heroes = GetHeroes() 9 10 } 11 12 [HttpGet] 13 public IEnumerable<Hero> Get() 14 { 15 return heroes; 16 } 17 18 [HttpGet("{id}")] 19 public Hero Get(int id) 20 { 21 return heroes.Single(h => h.Id == id); 22 } 23 24 [HttpGet] 25 [Route("GetList")] 26 public IEnumerable<Hero> GetList(string key) 27 { 28 return heroes.Where(h => h.Name.Contains(key)); 29 } 30 31 [HttpPost] 32 public void Post([FromBody]Hero info) 33 { 34 Hero hero = new Hero(); 35 36 hero.Id = heroes.Max(h => h.Id) + 1; 37 hero.Name = info.Name; 38 39 AddHeroes(hero); 40 } 41 42 [HttpPut("{id}")] 43 public void Put(int id, [FromBody]Hero hero) 44 { 45 Hero x = heroes.Single(h => h.Id == id); 46 47 x.Name = hero.Name; 48 49 UpdateHeroes(x); 50 } 51 52 [HttpDelete("{id}")] 53 public void Delete(int id) 54 { 55 Hero hero = heroes.Single(h => h.Id == id); 56 RemoveHeroes (hero); 57 } 58 }
AngularJS ide
1 getHeroes(): Promise<Hero[]> { 2 return this.http.get(this.heroUrl).toPromise().then(response => response.json() as Hero[]).catch(this.handleError); 3 } 4 5 getHero(id: number): Promise<Hero> { 6 return this.getHeroes().then(heroes => heroes.find(hero => hero.id === id)); 7 } 8 9 getHeroesSlowly(): Promise<Hero[]> { 10 return new Promise<Hero[]>(resolve => 11 setTimeout(resolve, 2000)) // delay 2 seconds 12 .then(() => this.getHeroes()); 13 } 14 15 updateHero(hero: Hero): Promise<Hero> { 16 const url = `${this.heroUrl}/${hero.id}`; 17 return this.http 18 .put(url, JSON.stringify(hero), { headers: this.headers }) 19 .toPromise() 20 .then(() => hero) 21 .catch(this.handleError); 22 } 23 24 addHero(heroName: string): Promise<Hero> { 25 return this.http 26 .post(this.heroUrl, JSON.stringify({ name: name }), { headers: this.headers }) 27 .toPromise() 28 .then(response => response.json()) 29 .catch(this.handleError); 30 } 31 32 deleteHero(heroId: number): Promise<void> { 33 const url = `${this.heroUrl}/${heroId}`; 34 return this.http 35 .delete(url, { headers: this.headers }) 36 .toPromise() 37 .then(() => null) 38 .catch(this.handleError); 39 } 40 41 search(term: string): Observable<Hero[]> { 42 return this.http.get(`${this.heroUrl}/GetList?key=${term}`).map((r: Response) => r.json() as Hero[]); 43 } 44 45 private handleError(error: any): Promise<any> { 46 console.error('An error occurred', error); // for demo purposes only 47 return Promise.reject(error.message | error); 48 }
1、跨域訪問(Cors)問題 post
在建好WebApi站點,啓動運行,一切正常。咱們僅顯示前兩個Action,結果以下: 測試
然而,在AngularJS客戶端,出現了問題,界面沒有任何英雄出現。 網站
再查Chrome的控制檯,發現了問題所在,WebApi的Access-Control-Allow-Origin沒有開,也就是跨域訪問不經過。 ui
跨域訪問,就是JS訪問其餘網址的信息。例如這裏的AngularJS的 this
1 getHeroes(): Promise<Hero[]> { 2 return this.http.get(this.heroUrl).toPromise().then(response => response.json() as Hero[]).catch(this.handleError); 3 }
方法中,調用heroUrl(WebApi網址)。AngularJS的網址是http://localhost:3000/,而WebApi的網址是http://localhost:5000/api/values。只要協議、域名、端口有任何一個不一樣,都被看成是不一樣的域。默認狀況下是不支持直接跨域訪問的。爲了在WebiApi中增長跨域訪問,咱們要在WebApi的Startup.cs中打開Access-Control-Allow-Origin:
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 app.UseCors(builder => 4 { 5 builder.AllowAnyOrigin(); 6 }); 7 8 app.UseMvc(); 9 }
如今程序訪問正常。
咱們查看AngularJS網站的network狀況,在values的headers—Response Headers中有Access-Control-Allow-Origin: *的字樣,這樣就解決了前面出現的"No Access-Control-Allow-Origin header is present…"錯誤。
再繼續測試,又出現問題了,此次是在更新時
錯誤信息以下:
咱們都已經打開Access-Control-Allow-Origin,怎麼還報錯:"No Access-Control-Allow-Origin header is present…"?咱們回過來看AngularJS,調用的是Update:
1 updateHero(hero: Hero): Promise<Hero> { 2 const url = `${this.heroUrl}/${hero.id}`; 3 return this.http 4 .put(url, JSON.stringify(hero), { headers: this.headers }) 5 .toPromise() 6 .then(() => hero) 7 .catch(this.handleError); 8 }
對應WebApi,此次調用的是Put方法
1 [HttpPut("{id}")] 2 public void Put(int id, [FromBody]Hero hero) 3 { 4 Hero x = heroes.Single(h => h.Id == id); 5 6 x.Name = hero.Name; 7 8 UpdateHeroes(hero.Name); 9 }
是否是因爲Method不對啊,之前都是Post、Get,此次是Put。趁早,一不作,二不休,在WebApi的Startup.cs中,寫成以下:
1 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 2 { 3 app.UseCors(builder => 4 { 5 builder.AllowAnyOrigin(); 6 builder.AllowAnyHeader(); 7 builder.AllowAnyMethod(); 8 }); 9 10 app.UseMvc(); 11 }
容許全部Origin、Header和Method。此次完全搞定,運行正常。
2、Update方法,必須使用類
其實上面Update時,仍是遇到了部分問題的。就是按照最開始的程序,咱們在WebApi的Put方法中,傳入的是英雄名稱name,而不是Hero對象。
1 [HttpPut("{id}")] 2 public void Put(int id, [FromBody]string name) 3 { 4 Hero x = heroes.Single(h => h.Id == id); 5 6 x.Name = name; 7 8 UpdateHero(x); 9 }
同時AngularJS中以下:
1 updateHero(hero: Hero): Promise<Hero> { 2 const url = `${this.heroUrl}/${hero.id}`; 3 return this.http 4 .put(url, JSON.stringify({name: name}), { headers: this.headers }) 5 .toPromise() 6 .then(() => hero) 7 .catch(this.handleError); 8 }
在程序運行時,咱們修改第13號英雄:
查看network,提交也是name: "Bombasto1111"。
然而,在WebApi中,調試狀況下:
Id沒有問題,可是name是null。[FromBody]要求前臺傳入的是Json串,而後提交到Action中。這時,Angular傳入的是{name: "Bombasto1111"},經過FromBody轉換後,是一個object,而不是一個string,所以name的值是null。
若是想用string name方式,AngularJS就要寫成:
1 updateHero(hero: Hero): Promise<Hero> { 2 const url = `${this.heroUrl}/${hero.id}`; 3 return this.http 4 .put(url, JSON.stringify(hero.name), { headers: this.headers }) 5 .toPromise() 6 .then(() => hero) 7 .catch(this.handleError); 8 }
再看network
WebApi調試狀態以下圖,name有值,一切搞定。
也可使用Hero做爲參數的方式,如同咱們文檔最開始提供的代碼同樣。
1 updateHero(hero: Hero): Promise<Hero> { 2 const url = `${this.heroUrl}/${hero.id}`; 3 return this.http 4 .put(url, JSON.stringify(hero), { headers: this.headers }) 5 .toPromise() 6 .then(() => hero) 7 .catch(this.handleError); 8 }
[HttpPut("{id}")] public void Put(int id, [FromBody]Hero hero) { Hero x = heroes.Single(h => h.Id == id); x.Name = hero.Name; UpdateHeroes(x); }
3、路由改變
咱們在作Hero Search的時候,又要在後臺WebApi中編寫按照關鍵字查詢的方法
1 [HttpGet] 2 public IEnumerable<Hero> GetList(string key) 3 { 4 return heroes.Where(h => h.Name.Contains(key)); 5 }
若是僅僅這樣寫,整個WebApi都很差用了。在調用Get時,出現了喜聞樂見的500錯誤。
而調用Get(int id)方法,就是localhost:5000/api/values/11時,一切正常。這是爲何呢?
由於按照微軟的方式,若是有多個相同Method(例如HttpGet)的方法,WebApi路由就無法區分了。這裏,Get()和GetList(string key)都是[HttpGet],路由都是localhost:5000/api/values。而Get(int id)方法因爲寫的是[HttpGet("{id}")],網址是localhost:5000/api/values/11,與Get()路由不一樣。所以,爲了解決Get()和GetList(string key),咱們在getlist方法上增長指定路由的方式。例如這裏就是
1 [HttpGet] 2 [Route("GetList")] 3 public IEnumerable<Hero> GetList(string key) 4 { 5 return heroes.Where(h => h.Name.Contains(key)); 6 }
在AngularJS中,就可使用localhost:5000/api/values/GetList?key=XXXX的方式調用,程序以下:
1 search(term: string): Observable<Hero[]> { 2 return this.http.get(`${this.heroUrl}/GetList?key=${term}`).map((r: Response) => r.json() as Hero[]); 3 }
固然了,若是想更進一步,須要再修改整個路由方式,增長Action。這就須要在Controller上增長[Route("api/[controller]/[action]/")]的寫法,而且將Action的[Route]刪除。
[Route("api/[controller]/[action]/")]
public class ValuesController : Controller
此次是真的解決了。