zoukankan      html  css  js  c++  java
  • AngularJS 2调用.net core WebAPI的几个坑

    前几天,按照AngularJS2的英雄指南教程走了一遍,教程网址是http://origin.angular.live/docs/ts/latest/tutorial/

    在步骤完成后,又更进一步,在英雄增删改的时候,直接调用.net core的WebApi来实现后台数据的操作,替换教程中的模拟WebApi方式。在替换.net core WebApi时,还是遇到了一些坑的,这里记录一下。

    先来看一下WebApi和AngularJS的源代码:

    WebApi

     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

     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

    一、跨域访问(Cors)问题

    在建好WebApi站点,启动运行,一切正常。我们仅显示前两个Action,结果如下:

    然而,在AngularJS客户端,出现了问题,界面没有任何英雄出现。

    再查Chrome的控制台,发现了问题所在,WebApi的Access-Control-Allow-Origin没有开,也就是跨域访问不通过。

    跨域访问,就是JS访问其他网址的信息。例如这里的AngularJS的

    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。这次彻底搞定,运行正常。

    二、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);
            }

    三、路由改变

    我们在做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

    这次是真的解决了。

  • 相关阅读:
    python的metaclass
    鱼和水的故事
    iOS的QuickTime Plugin
    二进制/十六进制转浮点数的编程(互转类似)
    Android开发常见错误及技巧
    Mac 热键大全
    Java动态程序设计:反射介绍
    注册asp.net 4.0 到iis
    javascript常用判断写法
    将存储过程执行的结果保存到临时表
  • 原文地址:https://www.cnblogs.com/BenDan2002/p/6256161.html
Copyright © 2011-2022 走看看