Angular + Web API 仅在生产中出错
Angular + Web API Error Only In Production
关于应用程序
我有一个使用 .Net REST API 的 Angular 8 应用程序,它是我从以前的员工那里继承来的(我对这两个框架都是新手)。经过几个月的开发,在整个开发过程中多次成功发布到生产服务器进行测试。
问题
最后一次发布到生产服务器后,我在控制台中收到两个错误,指出 Unexpected token < in JSON at position 0 for the API 调用 api/MtUsers/GetLoggedInUser ,这是在 home 组件的后端调用的。自上次更改发布到生产环境以来,我没有更新主组件或 MTUsersController 中的任何代码。
观察结果
- 错误只出现在生产中
- 如果我签出较旧的(以前工作的)提交和发布,错误仍然存在
- Visual Studio 开始抱怨实验性装饰器和发布时缺少模块(通过重启 VS 修复)
- 使用邮递员调用 API 在生产中似乎 return index.html 但 return 是本地主机中的 MtUser 对象
我试过的
- 清理解决方案并重新发布
- 检查最后已知的工作提交并发布
- 在 IIS 中回收应用程序池并重启网站
- 尝试各种与网站配置相关的代码更改
相关代码
我不太确定什么与此问题最“相关”,因此我提供了错误中指定的代码和 startup.cs 文件。让我知道是否还有其他更有用的东西。
home.component.ts
import { Component } from '@angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model'
import { MtUserService } from 'src/app/core/services/mtUser.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
loadingLoggedInUserInfo = true;
loggedInUser: MtUser = <MtUser>{};
/** home ctor */
constructor(
private mtUserService: MtUserService){
document.getElementsByClassName('main-content')[0].scroll(0, 0);
this.mtUserService.GetLoggedInUser()
.subscribe(response => {
this.loadingLoggedInUserInfo = false;
this.loggedInUser = response;
});
}
}
MtUserService
import { Injectable } from '@angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model';
import { environment } from 'src/environments/environment';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root', })
export class MtUserService {
constructor(private http: HttpClient) { }
GetLoggedInUser() {
return this.http.get<MtUser>(environment.apiUrl + '/MtUsers/GetLoggedInUser');
}
}
MtUsersController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using MT8.Models;
using MT8.Utilities;
namespace MT8.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class MtUsersController : Mt8ControllerBase
{
private readonly Mt8Context _context;
public MtUsersController(Mt8Context context)
{
_context = context;
}
// GET: api/MtUsers/GetLoggedInUser
[HttpGet("GetLoggedInUser")]
public async Task<ActionResult<MtUser>> GetLoggedInUser()
{
var loggedInUserName = ApplicationEnvironment.GetLoggedInUserName(this.HttpContext);
var loggedInUser = await this._context.MtUsers
.SingleOrDefaultAsync(u => u.UserName == loggedInUserName);
if (loggedInUser == null)
return NotFound();
return loggedInUser;
}
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using Microsoft.AspNetCore.Server.IISIntegration;
using System.Threading.Tasks;
using MT8.Utilities;
namespace MT8
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
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.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
services.AddDbContext<Mt8Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Mt8Context")));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToIntJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableIntConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDecimalJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDoubleJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDateTimeJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableDateTimeConverter()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Mt8Context dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
if (!env.IsProduction())
dbContext.InitializeData();
}
}
}
经过反复试验,我确定问题出在 appsettings.Production.json 文件中的连接字符串。最初设置为 Integrated Security=True
当应用程序首次构建时,某些原因导致它突然停止工作。我更新了数据库以使用 SQL 登录名,并在连接字符串中提供了 ID 和密码以解决问题。
关于应用程序
我有一个使用 .Net REST API 的 Angular 8 应用程序,它是我从以前的员工那里继承来的(我对这两个框架都是新手)。经过几个月的开发,在整个开发过程中多次成功发布到生产服务器进行测试。
问题
最后一次发布到生产服务器后,我在控制台中收到两个错误,指出 Unexpected token < in JSON at position 0 for the API 调用 api/MtUsers/GetLoggedInUser ,这是在 home 组件的后端调用的。自上次更改发布到生产环境以来,我没有更新主组件或 MTUsersController 中的任何代码。
观察结果
- 错误只出现在生产中
- 如果我签出较旧的(以前工作的)提交和发布,错误仍然存在
- Visual Studio 开始抱怨实验性装饰器和发布时缺少模块(通过重启 VS 修复)
- 使用邮递员调用 API 在生产中似乎 return index.html 但 return 是本地主机中的 MtUser 对象
我试过的
- 清理解决方案并重新发布
- 检查最后已知的工作提交并发布
- 在 IIS 中回收应用程序池并重启网站
- 尝试各种与网站配置相关的代码更改
相关代码
我不太确定什么与此问题最“相关”,因此我提供了错误中指定的代码和 startup.cs 文件。让我知道是否还有其他更有用的东西。
home.component.ts
import { Component } from '@angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model'
import { MtUserService } from 'src/app/core/services/mtUser.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent {
loadingLoggedInUserInfo = true;
loggedInUser: MtUser = <MtUser>{};
/** home ctor */
constructor(
private mtUserService: MtUserService){
document.getElementsByClassName('main-content')[0].scroll(0, 0);
this.mtUserService.GetLoggedInUser()
.subscribe(response => {
this.loadingLoggedInUserInfo = false;
this.loggedInUser = response;
});
}
}
MtUserService
import { Injectable } from '@angular/core';
import { MtUser } from 'src/app/core/models/mtUser.model';
import { environment } from 'src/environments/environment';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root', })
export class MtUserService {
constructor(private http: HttpClient) { }
GetLoggedInUser() {
return this.http.get<MtUser>(environment.apiUrl + '/MtUsers/GetLoggedInUser');
}
}
MtUsersController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using MT8.Models;
using MT8.Utilities;
namespace MT8.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class MtUsersController : Mt8ControllerBase
{
private readonly Mt8Context _context;
public MtUsersController(Mt8Context context)
{
_context = context;
}
// GET: api/MtUsers/GetLoggedInUser
[HttpGet("GetLoggedInUser")]
public async Task<ActionResult<MtUser>> GetLoggedInUser()
{
var loggedInUserName = ApplicationEnvironment.GetLoggedInUserName(this.HttpContext);
var loggedInUser = await this._context.MtUsers
.SingleOrDefaultAsync(u => u.UserName == loggedInUserName);
if (loggedInUser == null)
return NotFound();
return loggedInUser;
}
}
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using MT8.Data;
using Microsoft.AspNetCore.Server.IISIntegration;
using System.Threading.Tasks;
using MT8.Utilities;
namespace MT8
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
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.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
services.AddDbContext<Mt8Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Mt8Context")));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToIntJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableIntConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDecimalJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDoubleJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToDateTimeJsonConverter()))
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new StringToNullableDateTimeConverter()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, Mt8Context dbContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
if (!env.IsProduction())
dbContext.InitializeData();
}
}
}
经过反复试验,我确定问题出在 appsettings.Production.json 文件中的连接字符串。最初设置为 Integrated Security=True
当应用程序首次构建时,某些原因导致它突然停止工作。我更新了数据库以使用 SQL 登录名,并在连接字符串中提供了 ID 和密码以解决问题。