ASP.Net Core 2.1 Angular SSR /通用返回Http状态码404

时间:2018-06-19 18:36:27

标签: angular asp.net-core angular6 angular-universal asp.net-core-2.1

借助ASP.Net core 2.0 SpaPrerenderingExtensions中的JavaScriptServices,似乎我们可以从Angular(SSR)应用返回HTTP状态代码,SpaPrerenderingExtensions将使用该状态代码来返回客户端的HTTP状态代码。

如果您在SpaPrerenderingExtensions中查看以下方法

private static async Task ServePrerenderResult(HttpContext context, RenderToStringResult renderResult)

完整的SpaPrerenderingExtensions代码可在这里找到:https://github.com/aspnet/JavaScriptServices/blob/8ded472fe93123079ab97c9332f24460340876d2/src/Microsoft.AspNetCore.SpaServices.Extensions/Prerendering/SpaPrerenderingExtensions.cs

您会注意到,renderResult.Html包含从Angular Server App返回的html标记,而renderResult.StatusCode.Value包含状态代码。

用于启用SSR的官方文档(可在https://docs.microsoft.com/en-us/aspnet/core/spa/angular?view=aspnetcore-2.1&tabs=visual-studio#server-side-rendering获得)建议在main.server.ts文件中添加以下代码。

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModule, renderModuleFactory } from '@angular/platform-server';
import { APP_BASE_HREF } from '@angular/common';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { createServerRenderer } from 'aspnet-prerendering';
export { AppServerModule } from './app/app.server.module';

enableProdMode();

export default createServerRenderer(params => {
  const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports;

  const options = {
    document: params.data.originalHtml,
    url: params.url,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP),
      { provide: APP_BASE_HREF, useValue: params.baseUrl },
      { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }
    ]
  };

  const renderPromise = AppServerModuleNgFactory
    ? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
    : /* dev */ renderModule(AppServerModule, options);

  return renderPromise.then(html => ({ html }));
});

任何人都可以指导我如何从Angular Server应用返回状态代码,以便ASP.Net Core 2.0 SpaPrerenderingExtensions正确地获取它。

任何帮助将不胜感激。例如,如果我有一个具有状态码值的服务,该如何从该服务中选择值并传递到main.server.ts文件中的服务器。

注意:我正在使用基于ASP.Net Core 2.1 Angular CLI 6的模板。

2 个答案:

答案 0 :(得分:0)

好吧,我尝试了以下方法,而且似乎可行,任何反馈和改进都将受到欢迎。我不确定这种方法是否线程安全。我的意思是,如果并发请求由服务器处理,则一个请求的状态代码是否有可能分配给其他请求,因为这种方法利用了constant对象的子属性可变的事实。

我使用以下代码创建了文件globals.ts

export const response = { statusCode: 200 };

然后,我创建了一个服务StatusCodeService

import { Injectable } from '@angular/core';
import * as myGlobals from './globals';

@Injectable()
export class StatusCodeService {

  setStatusCode(statusCode: number) {
    myGlobals.response.statusCode = statusCode;
  }

}

现在,每当需要设置状态代码时,我都会使用此服务来设置状态代码。

我还使用以下代码更新了main.server.ts文件,以将值传递给服务器:

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModule, renderModuleFactory } from '@angular/platform-server';
import { APP_BASE_HREF } from '@angular/common';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { createServerRenderer } from 'aspnet-prerendering';
export { AppServerModule } from './app/app.module.server';

import * as myGlobals from './app/globals';

enableProdMode();

export default createServerRenderer(params => {
  const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports;
  const options = {
    document: params.data.originalHtml,
    url: params.url,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP),
      { provide: APP_BASE_HREF, useValue: params.baseUrl },
      { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }
    ]
  };

  const renderPromise = AppServerModuleNgFactory
    ? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
    : /* dev */ renderModule(AppServerModule, options);

  return renderPromise.then((html) => {

    let statusCode: number = 200;
    if (myGlobals.response.statusCode == 404) {
      statusCode = 404;
      //reset status code to 200 for next request
      myGlobals.response.statusCode = 200;
    }

    return {
      html: statusCode == 404 ? 'Page not found!' : html, StatusCode: statusCode
    }

  });
});

我还尝试了Return 404 status code in aspnet core SPA Angular application

中提到的方法

我为Angular 6更新了main.server.ts文件。

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { APP_BASE_HREF } from '@angular/common';

import { first } from 'rxjs/operators';

import { enableProdMode, ApplicationRef, NgZone } from '@angular/core';
import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { AppServerModule } from './app/app.module.server';

import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

//ADD THIS LINE
import { HttpStatusCodeService } from './path/to/services/http-status-code.service';

enableProdMode();

export default createServerRenderer(params => {

  const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports;

  const providers = [
    provideModuleMap(LAZY_MODULE_MAP),
    { provide: INITIAL_CONFIG, useValue: { document: '<app-root></app-root>', url: params.url } },
    { provide: APP_BASE_HREF, useValue: params.baseUrl },
    { provide: 'BASE_URL', useValue: params.origin + params.baseUrl },
  ];

  return platformDynamicServer(providers).bootstrapModule(AppServerModule).then(moduleRef => {
    const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
    const state = moduleRef.injector.get(PlatformState);
    const zone = moduleRef.injector.get(NgZone);

    //ADD THIS LINE: this will get the instance of the HttpStatusCodeService created for this request.
    //const statusCodeService = moduleRef.injector.get(HttpStatusCodeService);

    return new Promise<RenderResult>((resolve, reject) => {
      zone.onError.subscribe((errorInfo: any) => reject(errorInfo));
      appRef.isStable.pipe(first(isStable => isStable)).subscribe(() => {
        // Because 'onStable' fires before 'onError', we have to delay slightly before
        // completing the request in case there's an error to report
        setImmediate(() => {
          resolve({
            html: state.renderToString()//,

            //ADD THIS LINE: this will get the currently set status code and return it along with the prerendered html string
            statusCode: statusCodeService.getStatusCode()
          });
          moduleRef.destroy();
        });
      });
    });
  });
});

但是当我运行该应用程序时,它几乎坏掉了,并出现以下问题:

  • 生成的标记中没有任何样式表。服务器转移 状态不见了。 fxFlex布局不起作用。

答案 1 :(得分:0)

我将其发布为答案,因为我可以设置格式,使其比注释中的格式更好,但这都是未经测试的,并且基于我过时的解决方案,上面的代码段以及Angular6文档(我没有最新的VS模板,因此我目前无法进行测试。

同时提供给renderModule()renderModuleFactory()options参数对象有一个extraProviders数组,我相信这将使我们向{{ 1}}模块。

这是我的主意:

  • 在这里的答案中使用我的AppServerModuleReturn 404 status code in aspnet core SPA Angular application
  • 将其添加到HttpStatusCodeService模块(仅浏览器模块,而不是服务器模块)的providers数组中。
    • 这将允许在应用程序在浏览器中运行时正常注入服务。
  • 实例化AppBrowserModule文件中的HttpStatusCodeService并将该实例添加为main.server.ts数组中的提供者。
    • 这将允许在应用程序在服务器上运行时注入服务,同时还为我们提供了对注入服务的引用。
  • 使用对服务的引用来确定响应是否需要为404。

这是我的未测试尝试修改您的extraProviders文件片段以使其正常工作。

main.server.ts

在原始解决方案中,我使用Angular组件渲染了404 Not Found页面,所以我不会覆盖返回的HTML(import 'zone.js/dist/zone-node'; import 'reflect-metadata'; import { renderModule, renderModuleFactory } from '@angular/platform-server'; import { APP_BASE_HREF } from '@angular/common'; import { enableProdMode } from '@angular/core'; import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader'; import { createServerRenderer } from 'aspnet-prerendering'; export { AppServerModule } from './app/app.module.server'; //ADD THIS LINE import { HttpStatusCodeService } from './path/to/services/http-status-code.service'; enableProdMode(); export default createServerRenderer(params => { const { AppServerModule, AppServerModuleNgFactory, LAZY_MODULE_MAP } = (module as any).exports; //ADD THIS LINE - We manually instantiate the service so we have a reference to it. let statusCodeService = new HttpStatusCodeService(); const options = { document: params.data.originalHtml, url: params.url, extraProviders: [ provideModuleMap(LAZY_MODULE_MAP), { provide: APP_BASE_HREF, useValue: params.baseUrl }, { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }, //ADD THIS LINE - We add a provider for the service that uses our instance. { provide: HttpStatusCodeService, useValue: statusCodeService }, ] }; const renderPromise = AppServerModuleNgFactory ? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options) : /* dev */ renderModule(AppServerModule, options); return renderPromise.then((html) => { //ADD THIS LINE - get the status code from the service. let statusCode: number = statusCodeService.getStatusCode(); return { html: statusCode == 404 ? 'Page not found!' : html, StatusCode: statusCode } }); }); )。

为了完整起见,以下是答案上方的html: html, statusCode: statusCode

HttpStatusCodeService

让我知道是否可行。如果没有,我可以尝试建立一个测试环境以进一步解决这个问题,但是没有希望。