进行 API 调用时出现 404 错误,即使请求计数递增

Getting 404 error when making an API call, even though the request count increments

这与 Cannot find module @angular/core 完全不同,尽管我仍然有那个解决方案包以防万一有人有想法。

所以我在同一个解决方案中有一个 Angular7 站点和一个 Web API 项目。我已将 IIS Express 设置为仅使用端口 5000; Node 将使用端口 4200。在 Web API 项目中,我制作了一个新的 "AccountController" 控制器、一个 "LoginAttempt" 模型和一个 "LoginResult" 模型。

在 Angular 端,我有 login.component.html、login.component.ts、login.service.ts 和 serviceCall.service.ts 文件。 login.component.ts 由 html 文件更新并将请求传递给 login.service.ts 文件,该文件打包并发送到 serviceCall.service.ts 文件以发送到 API.

尝试调用时发生的情况是我收到 404 错误,但 Visual Studio 增加了与登录调用相关的 "requests" 值。我似乎找不到任何理由可以解释为什么我会收到 404 并且在调用尝试时仍然有请求增量。

源代码: C# 网络 API: Startup.cs

   public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }

LoginAttempt.cs

[Serializable]
public class LoginAttempt
{
    public string username { get; set; }
    public string password { get; set; }
}

LoginResult.cs

[Serializable]
public class LoginResult
{
    public string token { get; set; }
    public string message { get; set; }
    public bool success { get; set; }
}

AccountController.cs

[Route("api/[controller]")]
public class AccountController : Controller
{
    Account accountRepo = new Account();

    [HttpPost]
    public LoginResult Login(LoginAttempt input)
    {
        return accountRepo.verifyCredentials(input.username, input.password);
    }

    public IActionResult Index()
    {
        return View();
    }
}

Angular7 proxy.conf.json

{
  "exclude": [
    "**/bin",
    "**/bower_components",
    "**/jspm_packages",
    "**/node_modules",
    "**/obj",
    "**/platforms"
  ],
  "/api": {
    "target": "http://localhost:5000",
    "secure": false
  }
}

angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "smart-goal": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/smart-goal",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "smart-goal:build",
            "proxyConfig": "proxy.conf.json"
          },
          "configurations": {
            "production": {
              "browserTarget": "smart-goal:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "smart-goal:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    },
    "smart-goal-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "prefix": "",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "smart-goal:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "smart-goal:serve:production"
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "smart-goal"
}

LoginCredentials.ts

export class LoginCredentials {
  username: string | undefined;
  password: string | undefined;
}

LoginResults.ts

export interface ILoginResult {
  token: string,
  message: string,
  success: boolean
}

login.component.html

<p>Login Page</p>
<form>
  <label>Username:</label>
  <input type="text" [(ngModel)]="Username" name="Username"/>
  <label>Password:</label>
  <input type="password" [(ngModel)]="Password" name="Password"/>
  <button type="submit" (click)="LoginAttempt()">Submit</button>
</form>

login.component.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Input } from '@angular/core';
import { LoginCredentials } from '../models/loginCredentials';
import { LoginService } from './login.service';

@Component({
  selector: 'login',
  templateUrl: './login.component.html'
})
export class LoginComponent {
  private router: Router;
  private Username: string;
  private Password: string;
  private Login: LoginCredentials;
  private response: undefined;
  private service: LoginService;

  constructor(router: Router, service: LoginService) {
    this.router = router;
    this.Login = new LoginCredentials();
    this.service = service;
    this.Username = "";
    this.Password = "";
  }

  LoginAttempt() {
    let data = new LoginCredentials();
    data.username = this.Username;
    data.password = this.Password;

    this.service.Login(data)
      .subscribe(
        result => {
          let response = JSON.stringify(result);
          alert("SUCCESS - " + response);
        }
      );
  }
}

login.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { LoginCredentials } from '../models/LoginCredentials';
import { ServiceCall } from '../shared/serviceCall.service';
import { ILoginResult } from '../models/LoginResult';
import { map } from 'rxjs/operators';

@Injectable()
export class LoginService {
  call: ServiceCall;
  constructor(call: ServiceCall) {
    this.call = call;
  }

  public Login(loginAttempt: LoginCredentials): Observable<any> {
    let myResponse = new Map<string, string>()
    let data = new Map<string, string>();
    let data2 = new Map<string, string>();
    let url = "Account/Login";
    data.set('Username', loginAttempt.username);
    data.set('Password', loginAttempt.password);
    return this.call.makeCall(url, 'POST', data).pipe(map(response => data2 = response));
  }
}

serviceCall.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders, HttpRequest, HttpResponse,  } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ServiceCall {
  private _http: HttpClient;
  private _urlAppend: string;
  private _isAuthenticated: boolean;

  constructor(http: HttpClient) {
    this._http = http;
    this._urlAppend = '/api/';
    this._isAuthenticated = false;
  }

  public makeCall(url: string, type: string, data: Map<string, string>): Observable<any> {
    url = this._urlAppend + url;
    let headers = new HttpHeaders();
    headers.append('Content-Type', 'application/json');
    headers.append('charset', 'utf-8');
    let params = new HttpParams();
    let result = new Response();

    data.forEach((value: string, key: string) => {
      params.set(key, value);
    });

    let options = { headers: headers, params: params, withCredentials: this._isAuthenticated };
    let body = JSON.stringify(data);

    if (type == "GET") {
      return this._http
        .get(url, options)
        .pipe(map((result: Response) => result));
    } else if (type == "POST") {
      return this._http
        .post(url, body, options)
        .pipe(map(this.extractData));
    } else {
      Observable.throw("Invalid command.");
    }
  }

  public setAuthentication(input: boolean) {
    this._isAuthenticated = input;
  }

  private extractData(res: Response) {
    let body = res.json();
    return body || {};
  }

  private generateQueryString(input: Map<string, string>) {
    let response = new URLSearchParams();

    input.forEach((value: string, key: string) => {
      response.append(key, value);
    });

    return response;
  }
}

最后,Chrome 控制台中显示的 404 响应:

HttpErrorResponse
{
    error: null
    headers: HttpHeaders
    {
        lazyInit: f()
        lazyUpdate: null
        normalizeNames: Map(0) {}
    }
    message:  "Http failure response for http://localhost:4200/api/Acocunt/Login: 404 Not Found"
    name:  "HttpErrorResponse"
    ok:  false
    status: 404
    statusText:  "Not Found"
    url:  "http://localhost:4200/api/Account/Login"
}

呃。这个问题。首先,在 C# 方面:我习惯于使用 'regular' .NET 框架的旧 Web API,并且在使用 .NET Core 时发生了一些变化。在 AccountController.cs 文件中,需要在 class 声明的正上方指定该控制器的 "branch",以便在 class 声明的正上方有类似 [Route("api/[controller]")] 的内容.然后,在 class 中的每个方法上,我必须定义该特定方法的 URL 部分是什么。在上述情况下,

[HttpPost]
public LoginResult Login(LoginAttempt input)

需要看起来像:

[Route("login")]
[HttpPost]
public IActionResult Login([FromBody]LoginAttempt input)

然后在 Typescript 方面,Angular2 站点忽略了代理信息并且没有尝试转发到我的 API 端口。我的 proxy.conf.json 文件中的代理信息必须如下所示:

"/api": {
  "target": "http://localhost:5000",
  "secure": false,
  "changeorigin": true,
  "logLevel": "debug",
  "pathRewrite": {"^/api": ""}
}

我尝试了我能找到的每个版本,但在我向其中添加 "changeorigin" 位之前它无法工作。这让事情按照对我来说应该的方式工作。