AngularJS 2調用.net core WebAPI的幾個坑

前幾天,按照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 }
View Code

 

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     }
View Code

 

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

 

此次是真的解決了。

相關文章
相關標籤/搜索