web-dev-qa-db-fra.com

Angular 4 - Comment obtenir des données de l’API Web ASP.Net?

À l'aide du célèbre modèle Visual Studio 2017 Angular 4, j'ai testé les boutons de la barre de navigation latérale et j'ai pu récupérer les données en mémoire.

Ensuite, j'ai ajouté au projet un nouveau contrôleur API ASP.Net Core 2.0 connecté à une base de données à l'aide de Entity Framework et je l'ai exécuté avec le résultat 200 HTTP GET.

Le code du contrôleur:

#region TodoController
namespace TodoAngularUI.Controllers
{
    [Route("api/[controller]")]
    public class TodoController : Controller
    {
        private readonly SchoolContext _context;
        #endregion

        public TodoController(SchoolContext DbContext)
        {
            _context = DbContext;

            if (_context.Todo.Count() == 0)
            {
                _context.Todo.Add(new Todo { TaskName = "Item1" });
                _context.SaveChanges();
            }
        }

        #region snippet_GetAll
        [HttpGet]
        public IEnumerable<Todo> GetAll()
        {
            return _context.Todo.ToList();
        }

        [HttpGet("{id}", Name = "GetTodo")]
        public IActionResult GetById(long id)
        {
            var item = _context.Todo.FirstOrDefault(t => t.Id == id);
            if (item == null)
            {
                return NotFound();
            }
            return new ObjectResult(item);
        }
        #endregion

Maintenant, je voulais afficher les données du contrôleur ASP.Net Core obtenues à l'aide de Angular. J'ai donc créé un composant TypeScript nommé "todo" comme ci-dessous:

import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';

@Component({
    selector: 'todo',
    templateUrl: './todo.component.html'
})
export class TodoComponent {
    public Todo: task[];

    constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
        http.get(baseUrl + '/api/todo').subscribe(result => {
            this.Todo = result.json() as task[];
        }, error => console.error(error));
    }
}

interface task {
    Id: number;
    TaskName: string;
    IsComplete: boolean;
}

Et créé son composant HTML comme ci-dessous:

<h1>Todo tasks</h1>

<p>This component demonstrates fetching Todo tasks from the server.</p>

<p *ngIf="!todo"><em>Loading...</em></p>

<table class='table' *ngIf="Todo">
    <thead>
        <tr>
            <th>Id</th>
            <th>Task Name</th>
            <th>Is complete</th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let Task of todo">
            <td>{{ Task.Id }}</td>
            <td>{{ Task.TaskName }}</td>
            <td>{{ Task.Iscomplete }}</td>
        </tr>
    </tbody>
</table>

Puis nous sommes allés ajouter son routage dans le menu de la barre latérale Nav, voici le code TypeScript:

import { Component } from '@angular/core';

@Component({
    selector: 'nav-menu',
    templateUrl: './navmenu.component.html',
    styleUrls: ['./navmenu.component.css']
})
export class NavMenuComponent {
}

Et voici le code HTML Navbar:

<div class='main-nav'>
<div class='navbar navbar-inverse'>
    <div class='navbar-header'>
        <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
            <span class='sr-only'>Toggle navigation</span>
            <span class='icon-bar'></span>
            <span class='icon-bar'></span>
            <span class='icon-bar'></span>
            <span class='icon-bar'></span>
        </button>
        <a class='navbar-brand' [routerLink]="['/home']">TodoAngularUI</a>
    </div>
    <div class='clearfix'></div>
    <div class='navbar-collapse collapse'>
        <ul class='nav navbar-nav'>
            <li [routerLinkActive]="['link-active']">
                <a [routerLink]="['/home']">
                    <span class='glyphicon glyphicon-home'></span> Home
                </a>
            </li>
            <li [routerLinkActive]="['link-active']">
                <a [routerLink]="['/counter']">
                    <span class='glyphicon glyphicon-education'></span> Counter
                </a>
            </li>
            <li [routerLinkActive]="['link-active']">
                <a [routerLink]="['/fetch-data']">
                    <span class='glyphicon glyphicon-th-list'></span> Fetch data
                </a>
            </li>
            <li [routerLinkActive]="['link-active']">
                <a [routerLink]="['/api/todo']">
                    <span class='glyphicon glyphicon-Apple'></span> Todo api
                </a>
            </li>
        </ul>
    </div>
</div>

Et mon app.component.ts:

import { Component } from '@angular/core';

@Component({
    selector: 'app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
}

Le problème ici est que lorsque je clique sur le bouton de menu pour obtenir des données, rien ne se passe, tous les autres boutons fonctionnent, mais pas celui-ci, et 200 résultat est toujours affiché lors de l'utilisation directe de l'URL du navigateur.

Aucun message d'erreur et je n'ai pas trouvé de solution à la recherche sur le net de problèmes liés aux boutons non cliquables dans Angular et liés au transfert de données d'ASP.Net à Angular.

Qu'est-ce que je fais mal?

4
Sami-L

(Réponse dérivée de mes commentaires ci-dessus)

J'ai eu un problème similaire à celui-ci avant d'utiliser le modèle Angular 4 de Microsoft. 


Le problème

Microsoft fournit la chaîne BASE_URL dans son modèle; elle est obtenue en extrayant l'attribut href à partir de la balise base dans index.cshtml (la chaîne BASE_URL ne fait pas partie du cadre Angular).

La balise base dans index.cshtml devrait ressembler à <base href="~/" />

Ce qui signifie que partout où vous utilisez BASE_URL dans votre projet Angular 4, le suffixe BASE_URL est précédé d'un caractère /.

Donc, en regardant ce composant appelant http.get en utilisant cette URL:

@Component({
    selector: 'todo',
    templateUrl: './todo.component.html'
})
export class TodoComponent {
    public Todo: task[];

    constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
        http.get(baseUrl + '/api/todo').subscribe(result => {
            this.Todo = result.json() as task[];
        }, error => console.error(error));
    }
}

Notez que votre appel à http.get(baseUrl + '/api/todo') a un / devant /api/todo - le paramètre transmis dans http.get ressemblera à http://example.com//api/todo en raison du / supplémentaire de BASE_URL.


La solution

Essayez plutôt http.get(baseUrl + 'api/todo') (notez l'absence de / devant api/todo) - la chaîne BASE_URL devrait déjà inclure cela, si rien d'autre dans le modèle n'a été modifié.


Mise à jour 22-03-2018: Utilisation de HTTP POST

Conformément au commentaire ci-dessous, voici un exemple rapide de fonction pour POST, en supposant que baseUrl et http ont tous deux été injectés dans le constructeur:

import { Observable } from 'rxjs/rx';
import { Http, Headers, RequestOptions } from '@angular/http';

@Component({
    selector: 'todo',
    templateUrl: './todo.component.html'
})
export class TodoComponent {
    constructor(private http: Http, 
        @Inject('BASE_URL') private baseUrl: string) {
    }

    post(todo: Todo) {    
        let fullUrl = this.baseUrl + 'api/todo';
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        this.http.post(fullUrl, JSON.stringify(todo), options)
            .subscribe(result => {
                console.log(result);
        }, error => console.error(error));
    }
}

Et côté ASP.NET WebAPI (qui sait implicitement comment gérer Content-Type sur application/json dans une requête HTTP POST):

public class TodoController : Controller
{
    [HttpPost]
    public IActionResult Post([FromBody] Todo todo)
    {
        return Ok();
    }
}
1
Ben Cottrell