Asp.Net Core+Angular4调用接口遇NodeInvocationException错误求助
问题背景
你正在使用ASP.NET Core作为后端、Angular 4作为前端开发项目,通过ProjectRoleService调用后端MenuController的接口时,遇到了Unexpected end of JSON input错误,错误栈指向前端尝试解析JSON时失败。
前端服务代码(ProjectRoleService)
import { Injectable, Inject } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Http } from '@angular/http'; import 'rxjs/Rx'; @Injectable() export class ProjectRoleService { _serverURL: string; constructor(private _http: Http) { this._serverURL = "http://localhost:8080/api"; } getMenuDetails(roleName: string) { let _url = this._serverURL + "/Menu/GetMenuDetails?roleName=" + roleName; return new Promise((resolve, reject) => { this._http.get(_url) .map(res => res.json()) .catch((error: any) => { console.error(error); reject(error); return Observable.throw(error.json().error || 'Server error'); }) .subscribe((data) => { resolve(data); }); }); } }
后端控制器代码(MenuController)
public class MenuController : ApiController { /// <summary> /// Get Menu Details by logged in user role /// </summary> /// <param name="email ID"></param> /// <returns></returns> [Microsoft.AspNetCore.Mvc.HttpGet] public HttpResponseMessage GetMenuDetails(string roleName) { HttpResponseMessage httpResponseMessage = null; try { httpResponseMessage = Request.CreateResponse(new Menu().GetMenuDetails(roleName)); } catch (Exception ex) { throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent(ex.Message), ReasonPhrase = "Warning" }); } return httpResponseMessage; } }
错误信息
NodeInvocationException: Unexpected end of JSON input SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) at Response.module.exports.Body.json (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:71887:25) at CatchSubscriber.selector (E:\AngularProject\NodeUI\ClientApp\dist\main-server.js:19325:96) at CatchSubscriber.module.exports.CatchSubscriber.error (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:91048:31) at MapSubscriber.module.exports.Subscriber._error (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:10724:26) at MapSubscriber.module.exports.Subscriber.error (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:10698:18) at ZoneTask.onComplete [as callback] (E:\AngularProject\NodeUI\ClientApp\dist\main-server.js:4454:30) at ZoneDelegate.module.exports.ZoneDelegate.invokeTask (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:85819:31) at Object.onInvokeTask (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:14821:37) at ZoneDelegate.module.exports.ZoneDelegate.invokeTask (E:\AngularProject\NodeUI\ClientApp\dist\vendor.js:85818:36)
错误原因分析
这个错误的核心是前端尝试解析一个空或无效的JSON响应,主要由以下几个问题导致:
- 后端返回类型不匹配ASP.NET Core规范:你在ASP.NET Core中使用了旧的
HttpResponseMessage(这是Web API 2的用法),而ASP.NET Core MVC推荐使用IActionResult作为返回类型,直接使用HttpResponseMessage可能导致响应没有正确序列化JSON,甚至返回空内容。 - 前端Promise与Observable混用:Angular的
Http模块本身返回Observable,你手动包装成Promise的方式存在冗余,且错误处理逻辑可能在响应为空时触发解析失败。 - 潜在的后端返回空内容:如果
new Menu().GetMenuDetails(roleName)返回null或未正确初始化的对象,后端可能返回空响应,导致前端res.json()解析失败。
具体修复步骤
1. 修复后端Controller的返回类型
将HttpResponseMessage替换为ASP.NET Core标准的IActionResult,确保响应正确序列化:
using Microsoft.AspNetCore.Mvc; // 确保引入这个命名空间 [Route("api/[controller]")] // 添加路由特性,确保接口路径正确 public class MenuController : ControllerBase { // 继承ControllerBase而不是ApiController(ASP.NET Core中推荐) /// <summary> /// Get Menu Details by logged in user role /// </summary> /// <param name="roleName"></param> /// <returns></returns> [HttpGet("GetMenuDetails")] public IActionResult GetMenuDetails(string roleName) { try { var menuDetails = new Menu().GetMenuDetails(roleName); // 如果返回结果为空,返回空数组或默认值,避免空响应 if (menuDetails == null) { return Ok(new List<Menu>()); // 假设Menu是集合类型,根据实际情况调整 } return Ok(menuDetails); // Ok()会自动序列化对象为JSON } catch (Exception ex) { return StatusCode(StatusCodes.Status500InternalServerError, new { Message = ex.Message, ReasonPhrase = "Warning" }); } } }
2. 优化前端Service代码
去掉冗余的Promise包装,直接使用Observable,同时添加响应内容检查:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Http, Response } from '@angular/http'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; @Injectable() export class ProjectRoleService { private _serverURL = "http://localhost:8080/api"; constructor(private _http: Http) { } getMenuDetails(roleName: string): Observable<any> { const _url = `${this._serverURL}/Menu/GetMenuDetails?roleName=${encodeURIComponent(roleName)}`; // 使用encodeURIComponent处理特殊字符,避免URL拼接错误 return this._http.get(_url) .map((res: Response) => { // 检查响应是否有内容 if (res.status === 204 || res.text().trim() === '') { return []; // 返回空数组作为默认值 } return res.json(); }) .catch((error: any) => { console.error('API调用错误:', error); // 处理错误信息,避免解析错误的JSON const errorMsg = error.json ? error.json().Message || 'Server error' : error.message; return Observable.throw(errorMsg); }); } }
如果你仍然需要使用Promise,可以在调用处用
.toPromise()转换,比如:this.projectRoleService.getMenuDetails('admin').toPromise().then(...)
3. 验证后端接口有效性
先直接在浏览器或Postman中访问接口:http://localhost:8080/api/Menu/GetMenuDetails?roleName=你的角色名,检查返回的内容:
- 如果返回有效的JSON数组/对象,说明后端修复成功;
- 如果返回错误或空内容,检查
Menu.GetMenuDetails(roleName)的逻辑,确保它返回正确的对象集合。
4. 检查CORS配置(可选)
如果前端运行在不同端口(比如Angular默认4200),需要在ASP.NET Core中配置CORS,允许前端域名访问:
在Startup.cs的ConfigureServices中添加:
services.AddCors(options => { options.AddPolicy("AllowLocalAngular", policy => policy.WithOrigins("http://localhost:4200") // 你的Angular地址 .AllowAnyHeader() .AllowAnyMethod()); });
然后在Configure中启用:
app.UseCors("AllowLocalAngular");
总结
最核心的问题是后端使用了不兼容ASP.NET Core的返回类型,导致响应没有正确序列化JSON。通过切换到IActionResult、优化前端响应处理,应该能解决这个JSON解析错误。
内容的提问来源于stack exchange,提问作者CPK_2011

