スキップしてメイン コンテンツに移動

[Angular] Error handling of HttpClient

Intro

Sometimes I got errors when I had accessed to server with HttpClient.
I had wanted to know what should I do, so I tried in some situations.

Returning errors sample (ASP.NET Core)

Because I had wanted to get some kinds of problems, I used Identity.

Startup.cs


using Files;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Models;
using UpgradeSample.Models;
using UpgradeSample.Products;
using UpgradeSample.Users;

public class Startup
{
    private IConfigurationRoot Configuration { get; }
    public Startup(IHostEnvironment env)
    {
...
    }
    public void ConfigureServices(IServiceCollection services)
    {
        // DB Connect
        services.AddDbContext<UpgradeSampleContext>(options =>
            options.UseNpgsql(Configuration["DbConnect"]));
        // Identity
        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddUserStore<ApplicationUserStore>()
            .AddEntityFrameworkStores<UpgradeSampleContext>() 
            .AddDefaultTokenProviders();
        // SPA
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "clients/dist";
        });
        // DI
        services.AddSingleton<ILocalFileAccessor, LocalFileAccessor>();
        services.AddScoped<IProductDao, ProductDao>();
        services.AddScoped<IProductService, ProductService>();
        services.AddScoped<IUserService, UserService>();

        services.AddRazorPages();
        services.AddControllers();
    }
    public void Configure(IApplicationBuilder app, IHostEnvironment env)
    {
...
        if (env.IsDevelopment() == false)
        {
            app.UseSpaStaticFiles();
        }
        app.UseStaticFiles();
        app.UseCors(AllowedOrigins);
        app.UseRouting();
        // when I tried to redirect what was caused by unauthorized, I would uncomment.
        // app.UseStatusCodePagesWithRedirects("/login");

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "clients";
            
            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "s");
            }
        });
    }
}

ThrowSampleController.cs


using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Files;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using UpgradeSample.Models;

namespace Controllers
{
    public class ThrowSampleController: Controller
    {
        private readonly SignInManager<ApplicationUser> _signInManager;
        public ThrowSampleController(SignInManager<ApplicationUser> signInManager)
        {
            _signInManager = signInManager;
        }
        
        // Throw some results

        /* For authentication */
        [Route("/sample-signin")]
        public async Task Signin()
        {
            await _signInManager.PasswordSignInAsync("hello", "world", false, false);
        }
        [Route("/sample-signin")]
        public async Task SignOut()
        {
            await _signInManager.SignOutAsync();
        }
        // For redirection of unauthorized
        [Route("/Account/Login")]
        public IActionResult GetLoginPage()
        {
            return View("~/Views/LoginPage.cshtml");
        }
        // Generate dummy data
        private List<Product> GenerateSampleData()
        {
            return new List<Product>
            {
                new Product { ProductId = 0, ProductName = "Hello", LastUpdateDate = DateTime.Now },
                new Product { ProductId = 1, ProductName = "World", LastUpdateDate = DateTime.Now },
            };
        }
    }
}

Get results with HttpClient

Default

If no any probrems were happened, I could get data like below.

ThrowSampleController.cs


...
namespace Controllers
{
    public class ThrowSampleController: Controller
    {
...
        [Route("/throw/ok")]
        public List<Product> GetOk()
        {
            return GenerateSampleData();
        }
    }
}

throw-sample.service.ts


import { Injectable } from '@angular/core';
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
import {Observable, throwError} from "rxjs";
import {catchError, map} from 'rxjs/operators';
import {Product} from "./models/product";
import {UploadResult} from "./file-upload/upload-result";

@Injectable({
  providedIn: 'root'
})
export class ThrowSampleService {
  constructor(private httpClient: HttpClient) { }
  public getOk(): Observable<Array<Product>> {
    return this.httpClient.get<Array<Product>>(
      `http://localhost:5000/throw/ok`);
  }
...
}

If I had subscribed this method, I could get the array of "Product".
It was great simple:)
But unfortunately, some errors might happen.

404

ThrowSampleController.cs


...
namespace Controllers
{
    public class ThrowSampleController: Controller
    {
...
        [Route("/throw/notfound")]
        public IActionResult GetNotFound()
        {
            return NotFound();
        }
...
    }
}

throw-sample.service.ts


...
@Injectable({
  providedIn: 'root'
})
export class ThrowSampleService {

  constructor(private httpClient: HttpClient) { }
...
  public getNotFound(): Observable<Array<Product>> {
    return this.httpClient.get<Array<Product>>(
      `http://localhost:5000/throw/notfound`);
  }
  ...
}

This was the result.
Object { headers: {…}, status: 404, statusText: "Not Found", url: "http://localhost:5000/throw/notfound", ok: false, name: "HttpErrorResponse", message: "Http failure response for http://localhost:5000/throw/notfound: 404 Not Found", error: null }
How to handling the errors?
I could write like this.

throw-sample.component.ts


import { Component, OnInit } from '@angular/core';
import {ThrowSampleService} from "../throw-sample.service";

@Component({
  selector: 'app-throw-sample',
  templateUrl: './throw-sample.component.html',
  styleUrls: ['./throw-sample.component.css']
})
export class ThrowSampleComponent implements OnInit {

  constructor(private throwSampleService: ThrowSampleService) { }
...
  public getNotFound() {
    this.throwSampleService.getNotFound()
      .subscribe(result => console.log(result),
        error => {
            console.error(error);
            // Do something.
        },
        () => console.log('getNotFound completed')*/);
  }
...
}

But should I do like this?
According to the documents, I had felt I shall handle the errors in the service.
Angular - HttpClient

throw-sample.service.ts


...
@Injectable({
  providedIn: 'root'
})
export class ThrowSampleService {
    constructor(private httpClient: HttpClient) { }
...
    public getNotFound(): Observable<Array<Product>> {
        return this.httpClient.get<Array<Product>>(
                `http://localhost:5000/throw/notfound`)
            .pipe(
                catchError(ThrowSampleService.handleError)
            );
    }
...
    private static handleError(error: HttpErrorResponse){
        console.error(error);

        // Do something

        return throwError(error.message);
    }
}

I could write common operations.
For example, if I had got 401 error, I would move to login page.

And I hadn't had to return error in handleError.
So I also could delegate error handling to the class, and return default values.

throw-sample.service.ts


...
@Injectable({
    providedIn: 'root'
})
export class ThrowSampleService {
    constructor(private httpClient: HttpClient) { }
...
  public getNotFound(): Observable<Array<Product>> {
    return this.httpClient.get<Array<Product>>(
            `http://localhost:5000/throw/notfound`)
        .pipe(
            catchError(error => ThrowSampleService.handleError(error, []))
        );
  }
...  
  private static handleError<T> (error: HttpErrorResponse, args: T) {
    console.error(error);
    
    // Do something

    return args;
  }
}

401

How about the other errors?
I tried to handle the 401 error.

ThrowSampleController.cs


...
namespace Controllers
{
    public class ThrowSampleController: Controller
    {
...
        [Route("/throw/unauthorized")]
        public IActionResult GetUnauthorized()
        {
            return Unauthorized();
        }
...
    }
}

throw-sample.service.ts


...
@Injectable({
  providedIn: 'root'
})
export class ThrowSampleService {
  constructor(private httpClient: HttpClient) { }
...
  public getUnauthorized(): Observable<Array<Product>> {
    return this.httpClient.get<Array<Product>>(
      `http://localhost:5000/throw/unauthorized`)
      .pipe(
        catchError(ThrowSampleService.handleError)
      );
  }
...
}

Off cource I could get 401 error.
Object { headers: {…}, status: 401, statusText: "Unauthorized", url: "http://localhost:5000/throw/unauthorized", ok: false, name: "HttpErrorResponse", message: "Http failure response for http://localhost:5000/throw/unauthorized: 401 Unauthorized", error: null }

404 with redirection

How about I had used redirection to the login page?

Startup.cs


...
namespace UpgradeSample
{
    public class Startup
    {
...
        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
...
            app.UseStatusCodePagesWithRedirects("/login");
...
        }

ThrowSampleController.cs


...
namespace Controllers
{
    public class ThrowSampleController: Controller
    {
...
        [Route("/throw/unauthorized")]
        public IActionResult GetUnauthorized()
        {
            return Unauthorized();
        }
...
        [Route("/login")]
        [Route("/Account/Login")]
        public IActionResult GetLoginPage()
        {
            return View("~/Views/LoginPage.cshtml");
        }
    }
}

The result was here.
Object { headers: {…}, status: 200, statusText: "OK", url: "http://localhost:5000/login", ok: false, name: "HttpErrorResponse", message: "Http failure during parsing for http://localhost:5000/login", error: {…} }
If the request had been redirected, the status code what I could get was 200.
So I couldn't think only about the status code of 401 in this case.

403

How about 403 error?
I had removed the redirection again.

Startup.cs


...
namespace UpgradeSample
{
    public class Startup
    {
...
        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
...
            // app.UseStatusCodePagesWithRedirects("/login");
...
        }

ThrowSampleController.cs


...
namespace Controllers
{
    public class ThrowSampleController: Controller
    {
...
        [Route("/throw/forbidden")]
        public IActionResult GetForbidden()
        {
            return Forbid();        
        }
...
    }
}

throw-sample.service.ts


...
@Injectable({
  providedIn: 'root'
})
export class ThrowSampleService {

  constructor(private httpClient: HttpClient) { }
...
  public getForbidden(): Observable<Array<Product>> {
    return this.httpClient.get<Array<Product>>(
      `http://localhost:5000/throw/forbidden`)
      .pipe(
        catchError(ThrowSampleService.handleError)
      );
  }
...

This was the result.
Object { headers: {…}, status: 404, statusText: "Not Found", url: "http://localhost:5000/Account/AccessDenied?ReturnUrl=%2Fthrow%2Fforbidden", ok: false, name: "HttpErrorResponse", message: "Http failure response for http://localhost:5000/Account/AccessDenied?ReturnUrl=%2Fthrow%2Fforbidden: 404 Not Found", error: "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot GET /Account/AccessDenied</pre>\n</body>\n</html>\n" }
Why redirected automatically?
Because I hadn't written the page what was routed "/Account/AccessDenied".
So I hadn't been surprised. But I didn't know the redirection.
I would read the document.

Resources

RxJS in Action
Angular-HttpClient

コメント

このブログの人気の投稿

[Angular][ASP.NET Core] Upload chunked files

Intro I wanted to send files to Web application (made by ASP.NET Core). If the file size had been small, I didn't need do any special things. But when I tried to send a large file, the error was occurred by ASP.NET Core's limitation. Though I could change the settings, but I didn't want to do that, because I hadn't known the file sizes what would been actually using. So I splitted the data into chunks first, and sent them. After receiving all chunks, I merged them into one file. There might be some libraries or APIs (ex. Stream API) what did them automatically, but I couldn't find them. What I did [ASP.NET Core] Make CORS enabled [Angular] Split a large file into chunks [Angular][ASP.NET Core] Send and receive data as form data [ASP.NET Core] Merge chunks into one file [ASP.NET Core] Make CORS enabled Because the client side application(Angular) and the server side application(ASP.NET Core) had been separated, I had to make CORS(Cross-Origin Requests)

[Nest.js] Use WebSocket with ws

Intro Until last time , I had used node-web-rtc to try WebRTC. But because the example was a little complicated for I understood the core functions of using WebRTC. So I look for other frameworks or libraries. PeerJS is a famous library for WebRTC. peers/peerjs: Peer-to-peer data in the browser. - GitHub peers/peerjs-server: Server for PeerJS - GitHub PeerJS - Simple peer-to-peer with WebRTC A problem is I don't know how to integrate to the Nest.js project. I couldn't find examples. So I don't choose at least this time. What shall I choose? According MDN, WebRTC doesn't specify strictly what technology is used on server application for connecting two devices. Signaling and video calling - Web APIs | MDN But in many examples include MDN's one use WebSocket. samples-server/s/webrtc-from-chat at master · mdn/samples-server · GitHub So I try WebSocket in the Nest.js project. Use WebSocket in a Nest.js project Nest.js has a function for using We

[Nest.js] Show static files

Intro I wanted to use Nest.js and WebRTC(node-webrtc). NestJS - A progressive Node.js framework Documentation | NestJS - A progressive Node.js framework And because I wanted to try with simple page(not use JavaScript frameworks), I added static HTML, CSS, JavaScript into a Nest.js project. Prepare Install First, I installed @nestjs/cli. First steps | NestJS - A progressive Node.js framework As same as last time , I couldn't do global install because I had used Volta. But I could installed by volta. volta install @nestjs/cli Create project nest new nest-web-rtc-sample volta pin node@12 Run npm start After doing "npm start", I could getting "Hello World!" from http://localhost:3000. Add static files I could add static files by two ways. @nestjs/serve-static First one of them was using "serve-static". Serve Static | NestJS - A progressive Node.js framework npm install --save @nestjs/serve-static And I needed adding a module into app.modu