【发布时间】:2020-09-06 12:36:10
【问题描述】:
我目前正在使用 Visual Studio 2019 提供的 Angular 模板开发 ASP.NET Core Web 应用程序 - 一切都运行良好,直到我尝试访问 ASP.NET Core api 路由,即使用默认路由的简单 HTTPGET通过构建数据库模型提供,我通过将 Azure SQL 数据库拉入 SQL 对象资源管理器来构建它。我还根据模型搭建了我的控制器,因此我当前使用的所有 api 路由都是默认的 CRUD 操作。
当我在 localhost:port/api/enrolees 访问我的应用程序时,我收到了这个角度错误:
未捕获(承诺中):错误:无法匹配任何路由。 URL 段:'api/enrolees'
我可以清楚地看到 Angular 正在抓取 api 路由并将其视为 Angular 路由。所以,我尝试在我的 web.config 中添加 url 重写规则,如下所示:
<rule name="AngularJS" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
但是,这使得应用程序无法找到 css 文件,并且当我尝试访问路由时仍然会抛出错误。我应该提一下,应用程序中内置的默认控制器 WeatherForecast 可以正常工作。
如果我将我的 httpget 请求更改为只返回一个字符串,那么 url http://localhost:port/api/enrolees 会按预期返回该字符串。
当我调试代码并直接点击 api/controller 时,我收到一个 System.NotImplementedException,它指出:
函数或方法没有实现。
除此之外,我还设置了调用服务的函数,该服务调用 api 路由,所有这些都按预期命中但返回未定义。很明显,有些东西没有正确设置。我已经苦苦挣扎了几个小时,无法在这里或 Google 上找到合适的解决方案。
我的启动文件
using Hackathon.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Hackathon
{
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.AddControllersWithViews();
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddDbContext<SQContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddCors(o =>
{
o.AddPolicy("AllowAllHeaders", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
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();
app.UseRouting();
app.UseCors("AllowAllHeaders");
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";
});
}
}
}
我的控制器:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Hackathon.Models;
namespace Hackathon.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EnroleesController : ControllerBase
{
private readonly SQContext _context;
public EnroleesController(SQContext context)
{
_context = context;
}
// GET: api/Enrolees
[HttpGet]
public async Task<ActionResult<IEnumerable<Enrolee>>> GetEnrolee()
{
var list = await _context.Enrolee.ToListAsync();
return list;
}
// GET: api/Enrolees/5
[HttpGet("{id}")]
public async Task<ActionResult<Enrolee>> GetEnrolee(int id)
{
var enrolee = await _context.Enrolee.FindAsync(id);
if (enrolee == null)
{
return NotFound();
}
return enrolee;
}
// PUT: api/Enrolees/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPut("{id}")]
public async Task<IActionResult> PutEnrolee(int id, Enrolee enrolee)
{
if (id != enrolee.EnroleeId)
{
return BadRequest();
}
_context.Entry(enrolee).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!EnroleeExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/Enrolees
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
[HttpPost]
public async Task<ActionResult<Enrolee>> PostEnrolee(Enrolee enrolee)
{
_context.Enrolee.Add(enrolee);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (EnroleeExists(enrolee.EnroleeId))
{
return Conflict();
}
else
{
throw;
}
}
return CreatedAtAction("GetEnrolee", new { id = enrolee.EnroleeId }, enrolee);
}
// DELETE: api/Enrolees/5
[HttpDelete("{id}")]
public async Task<ActionResult<Enrolee>> DeleteEnrolee(int id)
{
var enrolee = await _context.Enrolee.FindAsync(id);
if (enrolee == null)
{
return NotFound();
}
_context.Enrolee.Remove(enrolee);
await _context.SaveChangesAsync();
return enrolee;
}
private bool EnroleeExists(int id)
{
return _context.Enrolee.Any(e => e.EnroleeId == id);
}
}
}
我的 app.module:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
import { TemplateComponent } from './template/template.component';
import { LoginComponent } from './login/login.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RegistrationComponent } from './registration/registration.component';
import { MaterialModule } from './material/material.module';
import { EnroleesService } from './services/enrolees.service';
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent,
TemplateComponent,
LoginComponent,
RegistrationComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'counter', component: CounterComponent },
{ path: 'fetch-data', component: FetchDataComponent },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegistrationComponent }
]),
BrowserAnimationsModule,
MaterialModule
],
providers: [ EnroleesService ],
bootstrap: [AppComponent]
})
export class AppModule { }
我认为不需要任何其他信息。我的 angular.json 是默认文件,除了我安装的 nuget 包之外,我的 package.json 也是如此。我通过 Visual Studio 将其发布到 IIS。我一定是错过了什么!
任何帮助将不胜感激!蒂亚!
【问题讨论】:
-
你是如何运行应用程序的?
-
@ESG 我正在通过 Visual Studio 部署 - 发布到我的 C:\inetpub\wwwroot 目录中的文件夹,其中包含“无托管代码”的应用程序池。我在 IIS 中为它在 8011 端口上创建了一个网站。
-
发布时输出到该文件夹的内容是什么?通常,aspnet 核心堆栈应该为 angular 文件提供服务,您不需要在 angular 中做任何事情来忽略 api 路由
-
是的,这就是我以前以这种方式构建的应用程序所经历的。根文件夹包含 ASP.NET Core 文件以及 ClientApp Angular 目录,该目录包含 dist 文件夹和内部的 Angular 文件。我还将角度文件复制并粘贴到应用程序文件夹的 wwwroot 目录中(c:\inetpub\wwwroot\application\wwwroot)。
-
那就是问题所在。在发布时,您的应用正在构建 clientapp/dist 文件夹,这就是您所需要的。如果你把 angular 文件放在 wwwroot 中,你就让 aspnet 使用静态文件中间件而不是 SPA 中间件
标签: angular asp.net-core