【问题标题】:ASP.Net Core 2.1 Angular SSR/Universal returning Http Status Code 404ASP.Net Core 2.1 Angular SSR/Universal 返回 Http 状态代码 404
【发布时间】:2018-11-28 19:17:23
【问题描述】:

SpaPrerenderingExtensions 在 ASP.Net core 2.0 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 服务器应用程序返回状态代码,以便 ASP.Net Core 2.0 SpaPrerenderingExtensions 正确获取它。

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

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

【问题讨论】:

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


    【解决方案1】:

    我将其发布为答案,因为我可以比在评论中更好地格式化,但是这一切都未经测试,并且基于查看我过时的解决方案、上面的代码 sn-ps 和 Angular6 文档(我不没有最新的 VS 模板,所以我暂时无法测试)。

    为renderModule() 和renderModuleFactory() 提供的options 参数对象有一个extraProviders 数组,我相信它可以让我们向AppServerModule 模块添加额外的提供程序。

    这是我的想法:

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

    这是我未经测试尝试修改您的main.server.ts 文件 sn-p 以使其工作。

    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
        }
    
      });
    });
    

    在我最初的解决方案中,我使用 Angular 组件呈现了我的 404 Not Found 页面,因此我不会覆盖返回的 HTML (html: html, statusCode: statusCode)。

    为了完整起见,这里是上面链接的答案中的HttpStatusCodeService:

    import { Injectable } from '@angular/core';
    
    @Injectable()
    export class HttpStatusCodeService {
    
      private statusCode: number;
    
      constructor(){
        this.statusCode = 200;
      }
    
      public setStatusCode(statusCode: number) {
        this.statusCode = statusCode;
      }
    
      public getStatusCode(): number {
        return this.statusCode;
      }
    }
    

    让我知道这是否有效。如果没有,我可以尝试设置一个测试环境来解决这个问题,但没有承诺。

    【讨论】:

    • 我不确定let statusCodeService = new HttpStatusCodeService(); 行的范围。我的假设是,我们将为每个呈现的页面获取一个新实例,但情况可能并非如此。您将需要测试先转到无效 URL,然后再转到有效 URL,并确保有效 URL 在无效 URL 的 404 之后返回 200。
    • 我似乎得到了一个不同的 statusCodeService 实例 :(
    【解决方案2】:

    好的,我尝试了以下方法,它似乎有效,任何反馈和改进都将受到高度欢迎。我不确定这种方法是否是线程安全的。我的意思是,如果服务器处理并发请求,是否有可能将一个请求的状态代码分配给另一个请求,因为这种方法利用了 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 布局不起作用。

    【讨论】:

    • 您的globals.ts 中的const 是否在多个请求中保持其状态?如果您删除 myGlobals.response.statusCode = 200; 重置,第一个 404 之后的所有请求是否也返回 404?如果是这样,那么您的解决方案肯定不是线程安全的。有没有办法像我在解决方案 (stackoverflow.com/a/46790709/1257979) 中那样从依赖注入系统中获取服务实例?
    • 感谢 Entith 的反馈,是的,如果我删除 'myGlobals.response.statusCode = 200;',你是对的重置,第一个 404 之后的所有请求也返回 404。从我的组件中,当我更新 'myGlobals.response.statusCode = 400;'' 的值时,我停止该组件中的所有处理并立即返回。我还在stackoverflow.com/a/46790709/1257979 中尝试了您的方法,但生成的标记似乎存在以下问题:服务器传输状态似乎不起作用。 fxLayout 似乎损坏了,并且缺少样式表。
    • 我并不是想像那样复制我的boot.server.ts,看起来他们改变了引导过程太多以至于无法正常工作。看看这个:pastebin.com/D71VNV2k 你必须弄清楚如何访问依赖注入器来获取StatusCodeService 实例。
    • 是的,这就是这种方法的要点,我认为新方法没有任何方法可以访问依赖注入器。由于 renderModuleFactory 返回 Promise.
    • 我添加了一个答案,看看你是否能够使用它。
    猜你喜欢
    • 2019-03-10
    • 2019-03-08
    • 2016-09-16
    • 2021-06-03
    • 2017-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    相关资源
    最近更新 更多