65.9K
CodeProject 正在变化。 阅读更多。
Home

SPA^2 使用 ASP.Net Core 1.1 + Angular 4.0 - 第 7 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (11投票s)

2017 年 4 月 4 日

CPOL

26分钟阅读

viewsIcon

36169

downloadIcon

771

涵盖从 Angular 2.40 转换为 Angular 4.0 + 使用 VS2015 将 ASP.Net Core / Angular 4 SPA 发布到 IIS。

如果上述链接失效,请转到 Github 此处,或者仅获取第 7 部分分支此处

引言

这是系列文章的第 7 部分,介绍了一种通过集成 ASP.Net Core 和 Angular 2.4(现为 Angular 4.0)来创建单页应用程序 (SPA) 的方法,其中 ASP.Net Core 通过使用标签助手生成 Angular 4 HTML 模板代码,从而在应用程序管道中保留了积极的作用,这些模板代码又与您的 C# 数据模型相关联。

本部分(第 7 部分)将涵盖将 SPA 从 Angular 2.4 转换为最新的 Angular 4.0,设置 IIS Express 的 SSL,然后介绍如何使用 Visual Studio 2015 发布到 IIS(在本部分,即第 7 部分)以及如何在下一部分(第 8 部分)中继续使用 Visual Studio 2017。

最后,我们将通过尽可能自动化所有操作来保持一切尽可能简单。

最后,按照本系列设定的传统,我们将避免设置 webpack、grunt、gulp 或服务器端预渲染的复杂性。

在此过程中,我将犯很多错误,并展示如何在途中修复它们。

背景

到目前为止,本系列文章一直在描述一种将 ASP.Net Core 与 Angular 2 集成的方法。

第 1 部分 - 如何集成 ASP.Net Core 和 Angular 2
第 2 部分 - 如何使用标签助手显示数据
第 3 部分 - 如何将标签助手用于数据输入,添加使用 EF Core 的 SQL 后端。
第 4 部分 - 使用 OpenIdDict 添加 JWT 令牌身份验证。 
第 5 部分 - 添加异步服务、服务器端验证、一个简单的 Angular 2 数据网格和更多标签助手。
第 6 部分 - 使用 NSwag 创建 Swagger 文档并生成 Angular 2 / Typescript 数据模型 + 数据服务。

这是第 7 部分 - 为 VS2015 用户发布到 IIS

从 Angular 2.40 转换为 Angular 4.0

Angular 4.0 在许多方面与 Angular 2.4 只有一小步之遥。它更小,加载更快,一些速度改进是通过优化实现的,而其他改进则是通过省略一些模块(例如动画)并将其视为可选模块而实现的。

Angular 4.0(或任何未来版本)的基本要求很容易通过查看 Angular Quickstart Github 仓库(此处链接)上的代码来了解,这是 Angular 教程中提到的最终 Quickstart 代码。您可以下载代码(作为 zip 文件)或克隆整个仓库(使用 Git)或仅使用 Web 浏览器中的文件视图查看它。不要因为网站仍然在某些地方提到 Angular 2 而感到困惑,因为它目前(截至 3 月 31 日,撰写本文时)网站已更新到 Angular 4.0,您可以从其 package.json 文件(此处链接)中看到。

要从 Angular 2.40 转换为 Angular 4.0,我们将从 package.json 文件开始更改,该文件负责我们的 Node Package Modules 或 NPM 文件;以前它是

{
  "dependencies": {
    "@angular/common": "~2.4.0",
    "@angular/compiler": "~2.4.0",
    "@angular/core": "~2.4.0",
    "@angular/forms": "~2.4.0",
    "@angular/http": "~2.4.0",
    "@angular/platform-browser": "~2.4.0",
    "@angular/platform-browser-dynamic": "~2.4.0",
    "@angular/router": "~3.4.0",

    "angular-in-memory-web-api": "~0.2.4",
    "systemjs": "0.19.40",
    "core-js": "^2.4.1",
    "rxjs": "5.0.1",
    "zone.js": "^0.7.6",
    "ngx-toastr": "^4.3.0"
  },
  "devDependencies": {
    "typescript": "~2.0.10",
    "tslint": "^3.15.1"
  },
  "repository": {}
}

这应该更改为以下内容

{
  "dependencies": {
    "@angular/animations": "^4.0.0",
    "@angular/common": "~4.0.0",
    "@angular/compiler": "~4.0.0",
    "@angular/core": "~4.0.0",
    "@angular/forms": "~4.0.0",
    "@angular/http": "~4.0.0",
    "@angular/platform-browser": "~4.0.0",
    "@angular/platform-browser-dynamic": "~4.0.0",
    "@angular/router": "~4.0.0",
    "angular-in-memory-web-api": "~0.3.0",
    "core-js": "^2.4.1",
    "ngx-toastr": "^5.0.5",
    "rxjs": "5.0.1",
    "systemjs": "0.19.40",
    "typescript": "^2.1.6",
    "zone.js": "^0.8.4"
  },
  "devDependencies": {
    "typescript": "~2.1.0",
    "tslint": "^3.15.1"
  },
  "repository": {}
}

上述新副本中的更改还适应了添加 Angular 动画的需要,以及将 ngx-toastr 更新到最新版本并获取更高版本的 Typescript。

在 VS2015 中,您可以通过单击“工具”菜单,然后单击“扩展和更新”,并在已安装部分中搜索 typescript 来检查已安装的 Typescript 版本。如果您需要更新 VS2015,请转到“联机”部分,搜索 Typescript 并选择最新版本(目前为 2.2),下载并按照提示进行安装。

接下来我们需要更新位于 \wwwroot 文件夹中的 systemjs.config.js 文件。找到 Angular 捆绑包部分,将该部分从此处更新为

...
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
...

这里需要添加 3 个动画包。将上述代码段更改为

...
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.min.js',
      '@angular/animations/browser':'npm:@angular/animations/bundles/animations-browser.umd.js',
      '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
...

接下来,我们需要确保我们的 ngModule 明确加载动画,因为这现在是一个附加组件。 

编辑 \wwwroot\app 中的 app.module.ts 文件以包含此额外导入

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

然后像这样在 ngmodule 导入中添加一个引用

imports: [ BrowserAnimationsModule, BrowserModule, FormsModule, HttpModule, ToastrModule.forRoot(), routing],

VS2017 用户可以进行相同的修改,VS2015 和 VS2017 用户现在都应该能够重建应用程序,然后按 Ctrl-F5 查看结果。

一切都应该像以前一样正常工作。在开发中,您可能会发现缓存问题,在这种情况下,要么清除浏览器缓存,要么在运行 F12 调试模式时,打开“禁用缓存”选项。

您的页面将以更智能的方式刷新,并且在编写代码时可以更轻松地看到更改。

可能出现什么问题?

假设您已更新 ngx-toast 模块,但忘记添加动画,您可能会看到贫血、相当“扁平”的 toast。

在某些情况下,您可能会看到下面的错误,展开后表示缺少动画。

在这两种情况下,您所需要做的就是添加前面概述的动画。

如果您不同时更新 Angular 4 和 ngx-toast,而是先更新 Angular 4,您很可能会收到错误消息,指出您的版本与较旧的 Angular2.x 版本不兼容。同时进行这些操作可以节省大量时间。

在 IIS Express 中设置 SSL

在我们直接发布到 IIS 之前,我们将先在 IIS Express 中做一些工作,在那里首先设置 SSL。

在 Visual Studio 中构建和运行 IIS Express 中的 ASP.Net Core 网站非常简单。您只需按 F5 或 Ctrl-F5,它就会出现。同样,对于 Visual Studio 中的 ASP.Net Core 项目,在 IIS Express 上运行 SSL 也非常容易。现在就让我们来做,为 IIS 中的“真实情况”做准备。

在 VS2015 和 VS2017 中,(1) 右键单击您的项目,单击“属性”,(2) 选择“调试”部分,(3) 勾选“启用 SSL”复选框,然后为了节省时间,(4) 单击“复制”链接将您的 SSL 网站的 URL 复制到剪贴板中。

上面显示的是 VS 2015,下面是 VS2017。

将 SSL URL 复制到剪贴板后。按 Ctrl-S 保存,然后重建并按 Ctrl-F5。

浏览器打开后,首先使用普通的 HTTP:// URL 验证网站是否在没有 SSL 的情况下正常运行。

验证后,在浏览器中按 Ctrl-T 打开新选项卡,然后按 Alt-D 选择地址,并粘贴地址 (Ctrl-V 或 Shift-Insert)。

您现在应该看到您的网站已启用 SSL,或者可能没有。

大多数浏览器在看到自制 SSL 证书时都会反对。Visual Studio 在此处生成的证书将起作用并加密流量,但未由已知的“CA”或“证书颁发机构”进行“签名”或身份验证。例如,Firefox 会出现此结果,以警告您潜在的危险网站。

点击“高级”,然后您会看到这个

单击“添加例外”,按照提示操作,您将有机会让浏览器接受测试 SSL 证书

接下来,您的页面(现在使用带有 https:// URL 的 SSL)应该会出现,尽管浏览器仍然会告诉您它不太“合规”,因为它会来自真实的、已付费的 SSL 证书

当处于开发模式时,我们当前的设置并未强制执行 SSL/HTTPS。在 Startup.cs 中使用 .DisableHttpsRequirement() 可以阻止 HTTPS 成为一个选项

    // Register the OpenIddict services.
    services.AddOpenIddict()
        // Register the Entity Framework stores.
        .AddEntityFrameworkCoreStores<A2spaContext>()
 
        // Register the ASP.NET Core MVC binder used by OpenIddict.
        // Note: if you don't call this method, you won't be able to
        // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
        .AddMvcBinders()
 
        // Enable the token endpoint.
        .EnableTokenEndpoint("/connect/token")
 
        // Enable the password flow.
        .AllowPasswordFlow()
 
        // During development, you can disable the HTTPS requirement.
        .DisableHttpsRequirement();
    }

要测试没有此开发选项会发生什么,请注释掉该行,并在上一行添加一个分号,即:

...

.EnableTokenEndpoint("/connect/token")
 
// Enable the password flow.
.AllowPasswordFlow();
 
// During development, you can disable the HTTPS requirement.
//.DisableHttpsRequirement();

重新构建并按 Ctrl-F5。您应该仍然能够通过默认的 HTTP URL 浏览网站,但是当您尝试登录时,您将收到一个来自后端 OpenIdDict 的错误消息。

使用您的 HTTPS / SSL URL(从您项目的属性页复制)重复此操作,现在我唯一的问题是忘记注册,但至少使用 SSL 它现在正在进一步进行

当然,一旦注册,您就可以登录了,除了黄色警告(同样,因为它是自签名测试证书),那么我们就可以开始了

如果您希望在开发时将其设为可选,但在生产时强制执行,那么我们需要将设置封装在一个条件中以更改行为。但是,在 startup.cs 的 ConfigureServices 方法中,尝试使用此方法存在问题,它不受支持。

IHostingEnvironment

在 startup.cs 的 ConfigureServices 方法中,不支持此操作。

为了让我们访问环境,我们将创建一个属性,在执行 startup.cs 时填充它,然后能够在任何地方访问它。

在 startup.cs 的类定义下方添加此代码

private IHostingEnvironment CurrentEnvironment { get; set; }

并将 startup.cs 从这里更改为

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
 
    Configuration = builder.Build();
}

改为这样。

public Startup(IHostingEnvironment env)
{
    CurrentEnvironment = env;
 
    var builder = new ConfigurationBuilder()
        .SetBasePath(CurrentEnvironment.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{CurrentEnvironment.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
 
    Configuration = builder.Build();
}

现在将配置服务方法更新为

// Register the OpenIddict services.
services.AddOpenIddict(options =>
{
    // Register the Entity Framework stores.
    options.AddEntityFrameworkCoreStores<A2spaContext>();
 
    // Register the ASP.NET Core MVC binder used by OpenIddict.
    // Note: if you don't call this method, you won't be able to bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
    options.AddMvcBinders();
 
    // Enable the authorization, logout, token or userinfo endpoints here:
    options.EnableTokenEndpoint("/connect/token");
 
    options.AllowPasswordFlow();
 
    // When request caching is enabled, authorization and logout requests are stored in the distributed cache by OpenIddict and the user agent
    // is redirected to the same page with a single parameter (request_id).
    // This allows flowing large OpenID Connect requests even when using an external authentication provider like Google, Facebook or Twitter.
    // options.EnableRequestCaching();
 
    // During development, you can disable the HTTPS requirement.
    if (CurrentEnvironment.IsDevelopment())
    {
        options.DisableHttpsRequirement();
    }
});

您可以将这一行从

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, A2spaContext context)

to

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, A2spaContext context)

在 startup.cs 的 Configure 方法中,也将对 env. 的引用更改为我们的新属性 CurrentEnvironment.。

现在我们准备测试部署到 IIS。

设置 IIS 以便支持 ASP.Net Core

如果您习惯于使用基于传统 .net 框架的 Web 应用程序进行开发,例如 ASP.Net 或 ASP.Net MVC,那么您可能已经针对 IIS 进行开发和调试。我在“日常工作”中经常这样做,因为 IIS Express 与 IIS 存在许多差异,这使得我无法将 IIS Express 用于这些较旧的应用程序。 

ASP.Net Core 在某种程度上改变了这一点,它不再是您妈妈的网站,ASP.Net Core Web 应用程序通常会与所有依赖项一起部署或发布到子目录中,然后该子目录将独立于任何其他 ASP.Net Core 应用程序实例使用。用于为 ASP.Net Core Web 应用程序提供服务的 Kestrel Web 服务器不适合直接使用,而应置于反向代理之后。事实证明,IIS 是一个合适的反向代理。配置后,请求将从 IIS 传递到特定的 ASP.Net Core Web 应用程序,正因为如此以及许多依赖项,它真的不容易调试或就地使用。

下图来自 Microsoft ASP.Net Core 文档网站(此处),清楚地显示了 ASP.Net Core Web 应用程序在 IIS 后面托管时发生的情况

要在 IIS 下托管 ASP.Net Core Web 应用,您需要 Windows 7 或 Server 2008 R2 或更高版本。 

您需要安装 ANCM 或“ASP.Net Core 模块”才能使 IIS 能够托管您的 ASP.Net Core Web 应用程序,没有它将无法工作,并且在您尝试在 IIS 管理器中设置它时会给出奇怪的错误,例如缺少 web.config 或用于日志记录等项目的空白/无法使用的对话框。通过从此处安装 ASP.Net Core 服务器托管捆绑包来下载 ANCM。

有关 ANCM 及其设置的更多信息,请使用以下两个链接

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/servers/aspnet-core-module

https://docs.microsoft.com/zh-cn/aspnet/core/hosting/aspnet-core-module

接下来,我们将创建一个文件夹,用于发布我们的 Web 应用程序。

在此初始示例中,我将使用简单的基于文件的发布,并定位到本地文件夹。IIS 位于我用于开发的同一台 PC 上。

这里我创建了一个名为 c:\publish 的文件夹,然后在该文件夹中又创建了一个名为 A2SPA 的文件夹。

现在转到 IIS 管理器,右键单击您的默认网站,单击添加应用程序

 

创建一个新的应用程序,如下所示。我使用了别名 A2SPA,然后找到了发布路径,c:\publish\A2SPA

暂时点击确定

默认情况下,您的新站点可能会使用应用程序池“DefaultAppPool”。我们将创建一个新的应用程序池,因为使用 ASP.Net Core 的关键是拥有一个运行非托管代码的应用程序池。

单击左侧的“应用程序池”,然后单击最右侧的“添加应用程序池”。

为您的新应用程序池输入一个唯一的名称,但这里的关键是选择“无托管代码”。 

接下来选择您的新 A2SPA Web 应用程序,(1) 在左侧,然后在最右侧,(2) 单击“高级设置”。
出现高级设置对话框后,单击应用程序池旁边的 [..] 按钮 (3) 以选择新的应用程序池,然后从应用程序池选择对话框 (4) 中选择刚刚创建的新应用程序池,然后单击“确定”并再次单击“确定”。 

 

接下来我们将添加一个“绑定”以允许我们使用 HTTPS。由于这是默认网站的一部分,我们需要右键单击左侧的“默认网站”,然后单击“编辑绑定”

或者,单击左侧的“默认网站”图标,然后单击最右侧的“绑定”。

现在 (1) 单击“添加”,然后 (2) 选择 HTTPS,(3) 选择您的 IIS Express 开发证书(或创建自签名证书,或添加您的“真实”证书),然后单击“确定”,然后单击“关闭”。

开发证书会起作用,但和以前一样,会产生一些有效性错误。显然,如果您要发布到“真实”的面向外部的网站,那么您应该获取一个真实的证书。有关更多信息,请参阅下面的链接。

发布到 IIS

VS2015 和 VS2017 的发布结果会略有不同,但一些相同的问题和错误会在这两者中都很常见。首先,我们将介绍使用 VS2015 发布,然后看看 VS2017 如何改变事情。

在 VS2015 中,右键单击 A2SPA 项目本身,然后单击“发布”。出现以下对话框

我们将从自定义选项开始,单击自定义,然后您会看到一个弹出窗口,要求您为配置文件创建名称

想好名称并输入后,单击“确定”,接下来需要更新“目标位置”,即我们将发布构成我们应用程序的文件的地方。这应该与我们刚刚在 IIS 中创建的网站相同,到目前为止的示例中,它是 c:\publish\a2spa,如下所示

单击“下一步”,然后您会看到另一个对话框。默认值在这里没问题,暂时先这样,所以只需单击“下一步”

现在点击“下一步”或“发布”,在网站构建完成后,您会发现您已发布了应用程序。

尝试浏览您在 IIS 中配置的 HTTPS URL,在我的例子中是 https:///a2spa 

具体消息会因您使用的浏览器而异,但是您会看到类似以下内容,您的浏览器抱怨您的证书无效。 

证书是安全的(我们这样做是为了使用它),尽管在公共网站上看到这种情况,您可能有理由保持谨慎。大多数这些浏览器也会为您提供创建例外情况的机会,就像我们之前使用 IIS Express 所做的那样。在下面的 Firefox 中,您会看到一个这样的屏幕

您需要创建另一个例外,因为这次站点的 URL 不同,单击“高级”,然后单击“添加例外”,如下所示

接下来,您应该能够确认异常,因为您会看到更多详细信息。在 Firefox 的情况下,单击“确认安全例外”按钮

这是我们的第一个问题。这是一个由 IIS 生成的“HTTP 错误 502.5:进程失败”页面。

要查找问题的原因,请转到 IIS,单击左侧以选择我们的 A2SPA Web 应用程序。然后双击“日志记录”或右键单击日志记录并单击“打开功能”。

请注意,下面显示的日志目标选择和其他日志设置是灰色的。

要访问这些,请转到左侧的父网站“Default Web Site”。再次双击日志记录,然后您将能够选择“日志文件和 ETW 事件”。我们可能最终不会使用日志文件(稍后会详细介绍),但至少我们会通过使用 ETW 在标准 Windows 事件查看器中看到日志条目。

选择 ETW 和日志文件后,单击“应用”。不过,如果您忘记并离开,系统会询问您是否要保存。

现在刷新您的网页浏览器中的页面,然后点击键盘上的 Windows 键,输入 event,然后选择事件查看器。

错误信息是

应用程序“MACHINE/WEBROOT/APPHOST/DEFAULT WEB SITE/A2SPA”的物理根路径为“c:\publish\A2SPA\”,启动进程的命令行是“"%LAUNCHER_PATH%" %LAUNCHER_ARGS%'”,ErrorCode = '0x80070002 : 0。

为了节省您的悬念,在 VS2015 中,查看您的 web.config 文件,您会发现默认值是这样的

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <!--
    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
  -->
 
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
  </system.webServer>
</configuration>

web.config 设置的详细内容在以下两个地方得到了很好的涵盖:一个是 ASP.Net Core 文档,此处;另一个是 Rick Strahl 的博客,此处

TLDR;要让 dotnet web 应用程序正常启动,您需要将 %LAUNCHER_PATH% 更改为 dotnet,并将 %LAUNCHER_ARGS% 更改为 .\a2spa.dll,仅此而已。

换句话说,将您的 web.config 文件更改为以下内容

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <!--
    Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
  -->
 
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\a2spa.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
  </system.webServer>
</configuration>

再次发布(右键单击 A2SPA 项目,选择“发布”,然后单击“发布”按钮),然后刷新浏览器。

在事件查看器中查看,当事件查看器本身刷新时,它也向我们显示我们正在从 Kestrel 向 IIS 提供文件。 

Kestrel,这里使用的 ASP.Net Core Web 服务器,正在端口 7870 上运行,如前所述,IIS 会自动将其转换: 

应用程序“MACHINE/WEBROOT/APPHOST/DEFAULT WEB SITE/A2SPA”成功启动进程“7004”,正在监听端口“7870”。

让我们检查一下浏览器,看看那里发生了什么。至少我们看到了 Angular 加载消息,但之后并没有发生太多其他事情,查看网络选项卡,您只能看到偶尔的成功以及许多 404 错误。

这是 404 错误的特写。一些 JavaScript 文件已加载,但请注意,这些只是 CDN 或内容分发网络上的远程文件;本地 JavaScript 文件没有得到正确提供。一些 JavaScript 片段之所以可以,是因为它们嵌入在 HTML 视图中。 

那么发生了什么?秘密是,当您部署文件时,它们需要位于 wwwroot 文件夹中才能向外部世界提供服务。回想一下我们的 NPM 模块,其中包括我们所有的 Angular 4 源代码,它们位于 node_modules 文件夹中,而不是 wwwroot 中,而是在其旁边。

如果您不确定发生了什么,另一个诊断技巧是打开管理员命令提示符,导航到您刚刚发布文件的文件夹,然后尝试从命令行执行应用程序。在此示例中,您键入

cd \publish\a2sa
dotnet .\a2spa.dll

然后你会看到一个可能更清晰的错误

为了解决这个问题,在 VS2015 中,我们需要进入 project.json 文件,并将此关键部分从这里更改为

"publishOptions": {
  "include": [
    "wwwroot",
    "**/*.cshtml",
    "appsettings.json",
    "web.config"
  ]
},

添加“node_modules”文件夹和一个逗号,变成这样

"publishOptions": {
  "include": [
    "wwwroot",
    "node_modules",
    "**/*.cshtml",
    "appsettings.json",
    "web.config"
  ]
},

重新发布,然后再次刷新浏览器(您可能需要按 Shift-F5 或打开网络选项卡并关闭缓存设置)。

还会出什么问题?

发布失败!

有时重新发布会失败,当 dll 正在使用时,它可能不允许您覆盖它。

要解决此问题,只需停止 IIS,发布,然后再次启动 IIS。

应用程序崩溃 (APPCRASH)

如果您在 C# 代码中存在内部问题,可能会发生此错误,在浏览器中通常是这样的

在事件查看器中可能像这样

文本的一小部分是

错误存储桶 120721861317,类型 4
事件名称:APPCRASH
响应:不可用
Cab ID:0

问题签名
P1:dotnet.exe
P2:1.1.0.1179
P3:5820b092
P4:KERNELBASE.dll
P5:10.0.15063.0

在这种情况下,我的 startup.cs 文件中有一些考虑不周的代码。 

HTTP 错误 403.14 - 禁止访问

这可能就像忘记发布文件一样简单;如果发布目标文件夹为空,您可能会收到 403.14 错误,就像您试图浏览一个禁止访问的文件夹一样。目录浏览是一个选项,可以在 ASP.Net Core 中启用,但如果没有文件,IIS 将不知道您在做什么,并会抛出此错误。

让我们试着把这个做好。快速检查 web.config,确保它包含 dotnet 和 .\a2spa.dll,然后发布,并使用 F12 网络选项卡,关闭缓存,我们可以一步一步地查看它。

那么刚刚发生了什么?仔细查看其中一个好的文件(此处显示 HTTP 状态 = 200 表示正常)

仔细查看请求路径,例如 https:///A2SPA/css/site.min.css?v=M .....

接下来看一个出现 404 文件未找到错误的文件

现在看看这里的请求 URL,https:///node_modules/ngx-toastr/toastr.css

因此,尽管我们包含了 node_modules 文件夹,但我们的网站正在形成请求,就好像它是应用程序的根目录,而不是相对于应用程序的 URL a2spa。像我们的样式表立即上方这样的静态文件有一个正确构建的 URL。

对于上面的示例文件,我们需要 node 模块中任何内容的 URL 请求为 
https:///a2spa/node_modules/... 而不是 https:///node_modules/...

这不需要太多修改,在我们的 views\shared 文件夹中,转到 _Layout.cshtml 文件,并注意我们为 /node_modules 中的任何内容设置的路径,例如

<link rel="stylesheet" href="/node_modules/ngx-toastr/toastr.css" />

这些应该更改为在初始斜杠前面添加一个波浪号或 ~,即:

<link rel="stylesheet" href="~/node_modules/ngx-toastr/toastr.css" />

更新 _Layout.cshtml 中的所有引用以修复此问题,然后重新发布。

我们的节点模块和静态文件现在都正常了,但现在遇到的障碍是我们的部分控制器提供的模板;这些模板也以绝对路径构建,例如 

https:///partial/registerComponent

应该是 

https:///a2spa/partial/registerComponent

这里的问题是我们的请求是绝对的,而不是相对的。如果它是相对的,它就会起作用。例如,查看 \wwwroot\app 文件夹中的 about.component.ts,我们加载部分组件的地方

@Component({
    selector: 'my-about',
    templateUrl: '/partial/aboutComponent'
})

现在让我们在 Typescript 组件中修复这个问题。像这样更改每个模板的 URL

@Component({
    selector: 'my-about',
    templateUrl: 'partial/aboutComponent'
})

更新 about.component.ts、app.component.ts、contact.component.ts、index.component.ts、login.component.ts 和 register.component.ts 以修复每个模板,删除前导斜杠。重新发布并

快到了,现在我们的 URL 似乎都构建正确了,但是菜单和默认页面不太对劲。当您导航,点击“主页”之类的链接时,您将被带到根目录 https://

更新 AppComponent.cshtml 以更改此处的链接,删除前导斜杠,从此处更改为

<ul class="nav navbar-nav">
    <li>
        <a class="nav-link" (click)="setTitle('Home - A2SPA')" routerLink="/home" routerLinkActive="active">Home</a>
    </li>
    <li [hidden]="!isLoggedIn()">
        <a class="nav-link" (click)="setTitle('About - A2SPA')" routerLink="/about">About</a>
    </li>
    <li [hidden]="!isLoggedIn()">
        <a class="nav-link" href="/swagger">NSwag</a>
    </li>
    <li>
        <a class="nav-link" (click)="setTitle('Contact - A2SPA')" routerLink="/contact">Contact</a>
    </li>
</ul>

改为这样。

<ul class="nav navbar-nav">
    <li>
        <a class="nav-link" (click)="setTitle('Home - A2SPA')" routerLink="home" routerLinkActive="active">Home</a>
    </li>
    <li [hidden]="!isLoggedIn()">
        <a class="nav-link" (click)="setTitle('About - A2SPA')" routerLink="about">About</a>
    </li>
    <li [hidden]="!isLoggedIn()">
        <a class="nav-link" href="swagger">NSwag</a>
    </li>
    <li>
        <a class="nav-link" (click)="setTitle('Contact - A2SPA')" routerLink="contact">Contact</a>
    </li>
</ul>

在 \views\shared\_loginpartial.cshtml 中,从此处更改为

<ul class="nav navbar-nav navbar-right">
    <li><a class="nav-link" (click)="setTitle('Register - A2SPA')" routerLink="/register">Register</a></li>
    <li><a class="nav-link" (click)="setTitle('Login - A2SPA')" routerLink="/login">Login</a></li>
</ul>

改为这样。

<ul class="nav navbar-nav navbar-right">
    <li><a class="nav-link" (click)="setTitle('Register - A2SPA')" routerLink="register">Register</a></li>
    <li><a class="nav-link" (click)="setTitle('Login - A2SPA')" routerLink="login">Login</a></li>
</ul>

发布并再次检查,我们又进了一步。这次我们遇到了登录 URL 的问题。它指向 /connect/login 而不是 /a2spa/connect/login,所以当我们将登录请求发送到服务器时,自然会收到 404 未找到错误。但是,嘿,至少页面正在提供服务!这是进步。

现在不要执行下一步,我尝试更改 startup.cs 并更改令牌端点的设置,从这里更改为

// Enable the authorization, logout, token or userinfo endpoints here:
options.EnableTokenEndpoint("/connect/token");

改为这样。

// Enable the authorization, logout, token or userinfo endpoints here:
options.EnableTokenEndpoint("connect/token");

这会产生一个有点难以诊断的错误。在浏览器中看起来是这样的,F12 网络调试器显示是 500 错误(404 是上一个会话)。消息中也没有太多其他帮助。

在事件查看器中,我们收到一条模糊的消息,称耗时过长。

然后刷新后,重启 IIS,然后重试实际上在事件查看器看来是成功的,但在浏览器中却给出了相同的结果。

于是切换到命令提示符(或 PowerShell),你就会得到一个提示,提示缺少强制斜杠。

我之前所做的更改,即从令牌端点删除前导斜杠,并不是很有帮助。恢复斜杠,我们又回到了原点,仍然是相同的 404 错误,但至少不是 500 错误。

要解决此问题,您需要更新几个地方的代码。请求是从我们的 Angular 4 代码发送出去的,我们需要进行这些关键更改。在 /wwwroot/app/app.module.ts 中,将此更改为

@NgModule({
    imports: [ BrowserAnimationsModule, BrowserModule, FormsModule, HttpModule, ToastrModule.forRoot(), routing],
    declarations: [AppComponent, routedComponents],
    providers: [SampleDataService,
        AuthService,
        AuthGuard, Title, { provide: APP_BASE_HREF, useValue: '/' }],
    bootstrap: [AppComponent]
})

通过将 APP_BASE_HREF 更改为以下内容来更正基本网站路径

@NgModule({
    imports: [ BrowserAnimationsModule, BrowserModule, FormsModule, HttpModule, ToastrModule.forRoot(), routing],
    declarations: [AppComponent, routedComponents],
    providers: [SampleDataService,
        AuthService,
        AuthGuard, Title, { provide: APP_BASE_HREF, useValue: '/a2spa' }],
    bootstrap: [AppComponent]
})

然后,在 /wwwroot/app/security 中,将 auth-guard.service.ts 中的路由器链接从这里更改为: 

this.router.navigate(['/login']);

改为这样。

this.router.navigate(['login']);

同样,更改 /wwwroot/app/login.component.ts 中的链接(此处仅显示一小部分),从此处更改为

this.http.post('/connect/token', body, { headers: this.authService.contentHeaders() })
    .subscribe(response => {
        // success, save the token to session storage
        this.authService.login(response.json());
        this.router.navigate(['/about']);

删除 http.post 和路由链接中的前导斜杠,改为以下内容

this.http.post('connect/token', body, { headers: this.authService.contentHeaders() })
    .subscribe(response => {
        // success, save the token to session storage
        this.authService.login(response.json());
        this.router.navigate(['about']);

再次,在 register.component.ts 中,同样的操作,删除前导斜杠,变成这样

this.http.post('Account/Register', JSON.stringify(body), { headers: this.authService.jsonHeaders() })
    .subscribe(response => {
        if (response.status == 200) {
            this.router.navigate(['login']);

现在再次发布,我们终于可以……不,又一个 500 错误。但不要放弃!

这次查看事件查看器,有时错误可能难以察觉,因此请多次刷新页面。

再次查看事件查看器,在事件查看器中按 F5 刷新事件视图。您最终会看到一个 SQL Server 问题。 

SQL Manager 有一些帮助,打开它,右键单击您的 A2SPA 数据库,单击属性,然后单击“文件”,它会显示所有者和文件名,尽管不直接显示每个文件的路径。

这里我们遇到了 LocalDB 的限制。通常 LocalDB 在您的本地用户帐户的上下文中运行,数据库容器(.MDF 文件)和日志文件(.LDF 文件)位于您的用户目录中。正如您在下面看到的

除了文件和用户问题,LocalDB 背后的应用程序仅在需要时才会启动和运行,它不会作为服务运行,虽然它很适合开发,但对于面向公众的网站来说并不是最佳选择。

尽管存在这些问题,仍有一些人设法让 LocalDB 运行,但就我个人而言,我认为不值得付出努力,使用 SQL Express(也是免费的)要容易得多,但考虑到时间,使用起来更简单更快(因此更便宜!)。

如果您有兴趣,以下两篇博客文章描述了您可以使用的各种技巧,并且是对这些问题的深入研究

https://blogs.msdn.microsoft.com/sqlexpress/2011/12/08/using-localdb-with-full-iis-part-1-user-profile/
https://blogs.msdn.microsoft.com/sqlexpress/2011/12/08/using-localdb-with-full-iis-part-2-instance-ownership/

安装 SQL Express

如果您已经安装了 SQL Express,请跳过此步骤,但如果没有,请帮自己一个忙,下载并安装它。它足够小,可以在您不需要时(或在火车上用电池供电的笔记本电脑上,并且确实不需要它时)安装并禁用服务。

从 Microsoft 下载 SQL Express(目前是 SQL Express 2016 SP1),网址为: 
https://www.microsoft.com/zh-cn/sql-server/sql-server-editions-express

稍后,安装完成后,请在此处下载最新的(也是免费的)SQL Server Management Studio (SSMS)
https://docs.microsoft.com/zh-cn/sql/ssms/download-sql-server-management-studio-ssms

最后,Visual Studio 的 SQL Server Data Tools (SSDT) 在此
https://docs.microsoft.com/zh-cn/sql/ssdt/download-sql-server-data-tools-ssdt

安装 SQL Express 相当简单,安装程序有无数链接。从“新建 SQL Server 独立...”开始 

当然,您接受,还有什么选择 :)

下一步,下一步,...

命名实例很重要。如果您正在玩例如来自 GitHub 的某人的源代码,很有可能它在连接字符串中假定名称“SQLEXPRESS”,因此默认值很好,按照提示,点击下一步。

随你,我勾选了复选框,然后点击了“下一步”。

我发现这个很有用,默认通常只有 Windows 身份验证。这没问题,但有时您可能需要使用用户名和密码。如果您现在没有启用它,以后可能需要重新安装。所以为什么不呢,即使您不使用它。如果您想玩 Filestream,也可以启用它,点击下一步。

然后安装开始...

完成后,并且您已经安装了 SQL Server Management Studio,就万事俱备了。幕间休息后,回到我们的 Web 应用程序。

从 LocalDB 转换为 SQL Express

在 Visual Studio 中,转到“生成”菜单,然后单击“发布”,或者右键单击 A2SPA 项目并单击“发布”。返回左侧的“设置”选项,打开“数据库”选项,如下图所示。

我已突出显示您需要更改的部分。默认设置将从我们的 appsettings.json 文件中获取连接字符串,服务器显示为 (localdb)\mssqllocaldb,如下所示

将其更改为 .\SQLEXPRESS,以便为我们的新 SQL Express 数据库做好准备,即一个点,然后是一个反斜杠,然后是 SQLEXPRESS,所有这些都没有空格。如下所示

点击下一步。

现在我们在这里要走一个捷径。如果您尝试过 SQL 脚本导出选项(本应在 EF Core 中),并且与我得到了相同的结果,您会发现它不会导出到脚本。

那么我们如何设置 SQLExpress 上的数据库呢?鉴于工具尚不完善,我们可以做两件事。我们可以从 LocalDB 编写数据库脚本,使用 SSMS 我们可以相当容易地创建一个数据库创建脚本,然后使用该脚本在 SQL Express 上创建数据库和表。相当容易,除了我们需要稍微编辑脚本以解决 LocalDB 和常规 SQL 之间的差异。 

更简单的方法是,我们在 startup.cs 中有一个数据库种子和自动迁移子句,但它只设置为开发模式。因此,暂时注释掉 startup.cs 底部的 if 语句,改为:

// if (CurrentEnvironment.IsDevelopment())
{
    options.DisableHttpsRequirement();
}

然后在 SQL Server Management Studio 中,右键单击“数据库”,然后单击“新建数据库”。

 

命名为 A2SPA,请注意存储位置不在用户帐户下,这与 LocalDB 大不相同。单击“确定”。

然后打开“数据库”,按 F5 查看新创建的数据库 A2SPA,在左侧的“对象资源管理器”窗格中单击 A2SPA。然后按 Ctrl-N 打开“新建查询”,并输入以下内容

create login [IIS APPPOOL\a2spa] from windows;
exec sp_addsrvrolemember N'IIS APPPOOL\a2spa', sysadmin

然后按 Ctrl-E 执行,然后点击红色的 ! 按钮。这将创建一个新的登录名,恰好与应用程序池的名称一致,并为其分配我们数据库的权限。

这部分内容与设置 SQL Express(或其他 SQL)与 ASP.Net MVC 或 ASP.Net 网站以与 IIS 和 IIS 应用程序池用户协同工作并没有什么不同。您可以在此处找到更多详细信息。

现在发布网站,然后启动浏览器,您应该最终能够注册新用户并登录。

好吧,在我的情况下,不完全是,结果是我拼错了。

快速查看 SQL 管理器,我发现我拼错了我的用户帐户!

最后……鼓声响起,我们发布的 VS2015 Web 应用程序

 

使用 VS2017 发布以及我们最终的清理,以删除一些使网站正常工作的硬编码“黑客”将在第 8 部分中介绍。 

如果您正在使用 VS2017,请坚持下去,发布方面有一些差异,特别是包括 node_modules 和其他静态内容,我们将解决这些问题。 

历史

本系列文章一直在描述一种将 ASP.Net Core 与 Angular 2 集成的方法。

第 1 部分 - 如何集成 ASP.Net Core 和 Angular 2
第 2 部分 - 如何使用标签助手显示数据
第 3 部分 - 如何将标签助手用于数据输入,添加使用 EF Core 的 SQL 后端。
第 4 部分 - 使用 OpenIdDict 添加 JWT 令牌身份验证。 
第 5 部分 - 添加异步服务、服务器端验证、一个简单的 Angular 2 数据网格和更多标签助手。
第 6 部分 - 使用 NSwag 创建 Swagger 文档并生成 Angular 2 / Typescript 数据模型 + 数据服务。

这是第 7 部分 - 为 VS2015 用户发布到 IIS

接下来是第 8 部分,介绍如何使用 VS2017 发布到 IIS,以及 VS2015 和 VS2017 项目的最终代码优化和清理。

最后,非常感谢 Kévin Chalet 出色的 OpenIdDict 安全框架。它功能强大,但又非常简单易用。

© . All rights reserved.