You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在带预渲染的Angular模板中使用Razor语法?

解决ASP.NET Core 2 Angular预渲染中使用Razor语法的问题

首先得明确:直接把Razor页面路径设为Angular组件的templateUrl在预渲染场景下确实不可行,你遇到的Cannot find module错误本质是Webpack打包逻辑和ASP.NET Razor渲染逻辑的冲突——我来拆解原因和给出可行方案:

为什么预渲染时会报错?

服务器端预渲染(SSR)是靠Node.js环境运行Angular的服务器端bundle,而Webpack在打包这个bundle时,会把templateUrl的路径当作Node.js模块去查找解析。但Razor页面是ASP.NET Core在.NET环境下处理的动态资源,不在Webpack的打包范围内,自然会触发模块找不到的错误。

可行的解决思路

方案1:通过API获取Razor渲染后的HTML,动态渲染组件

这种方式最直观,把Razor页面的内容通过API暴露出来,Angular组件不管是客户端还是预渲染阶段,都通过HTTP请求获取HTML内容并渲染:

  1. 创建Razor内容API控制器
    在ASP.NET Core项目中添加一个控制器,负责渲染Razor页面并返回HTML:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Razor;
    using System.IO;
    using System.Threading.Tasks;
    
    [Route("api/razor-content")]
    public class RazorContentController : Controller
    {
        private readonly IRazorViewEngine _viewEngine;
        private readonly ITempDataProvider _tempDataProvider;
    
        public RazorContentController(IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider)
        {
            _viewEngine = viewEngine;
            _tempDataProvider = tempDataProvider;
        }
    
        [HttpGet("test")]
        public async Task<IActionResult> GetTestPage()
        {
            var actionContext = new ActionContext(HttpContext, RouteData, new ActionDescriptor());
            var viewResult = _viewEngine.FindView(actionContext, "/Pages/Test", isMainPage: true);
    
            if (!viewResult.Success)
            {
                return NotFound("Razor页面未找到");
            }
    
            using var sw = new StringWriter();
            var viewContext = new ViewContext(
                actionContext,
                viewResult.View,
                ViewData,
                new TempDataDictionary(HttpContext, _tempDataProvider),
                sw,
                new HtmlHelperOptions()
            );
    
            await viewResult.View.RenderAsync(viewContext);
            return Content(sw.ToString(), "text/html");
        }
    }
    
  2. 修改Angular组件动态获取并渲染HTML
    调整fetchdata.component.ts,去掉templateUrl,改用动态获取HTML并渲染:

    import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
    import { isPlatformServer } from '@angular/common';
    import { TransferState, makeStateKey } from '@angular/platform-browser';
    
    const TEST_PAGE_KEY = makeStateKey<string>('testPageHtml');
    
    @Component({
      selector: 'app-fetch-data',
      template: '<div [innerHTML]="safeRazorHtml"></div>'
    })
    export class FetchDataComponent implements OnInit {
      safeRazorHtml: SafeHtml;
    
      constructor(
        private http: HttpClient,
        private sanitizer: DomSanitizer,
        private transferState: TransferState,
        @Inject(PLATFORM_ID) private platformId: Object
      ) { }
    
      ngOnInit() {
        // 优先从TransferState取预渲染时的数据,避免客户端重复请求
        const cachedHtml = this.transferState.get(TEST_PAGE_KEY, '');
        if (cachedHtml) {
          this.safeRazorHtml = this.sanitizer.bypassSecurityTrustHtml(cachedHtml);
          return;
        }
    
        // 预渲染时需要用完整API地址,Node环境无法识别相对路径
        const apiUrl = isPlatformServer(this.platformId) 
          ? 'http://localhost:64066/api/razor-content/test' 
          : '/api/razor-content/test';
    
        this.http.get(apiUrl, { responseType: 'text' }).subscribe(html => {
          this.safeRazorHtml = this.sanitizer.bypassSecurityTrustHtml(html);
          // 把数据存入TransferState,客户端复用
          if (isPlatformServer(this.platformId)) {
            this.transferState.set(TEST_PAGE_KEY, html);
          }
        });
      }
    }
    

方案2:预渲染阶段直接把Razor内容传递给Angular

这种方式更高效,在ASP.NET的预渲染中间件中提前渲染Razor页面,把内容通过SupplyData传递给Angular的服务器端代码:

  1. 修改Startup.cs配置预渲染数据

    using Microsoft.AspNetCore.SpaServices.Prerendering;
    // 其他必要using...
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...其他中间件配置
    
        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "ClientApp";
    
            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
            else
            {
                spa.UseSpaPrerendering(options =>
                {
                    options.BootModulePath = $"{spa.Options.SourcePath}/dist/main-server.js";
                    options.BootModuleBuilder = env.IsDevelopment()
                        ? new AngularCliBuilder(npmScript: "build:ssr")
                        : null;
                    options.ExcludeUrls = new[] { "/sockjs-node" };
                    // 预渲染时传递Razor页面内容
                    options.SupplyData = async (context, data) =>
                    {
                        var viewEngine = context.RequestServices.GetRequiredService<IRazorViewEngine>();
                        var tempDataProvider = context.RequestServices.GetRequiredService<ITempDataProvider>();
                        var actionContext = new ActionContext(context, context.GetRouteData(), new ActionDescriptor());
                        var viewResult = viewEngine.FindView(actionContext, "/Pages/Test", isMainPage: true);
    
                        if (viewResult.Success)
                        {
                            using var sw = new StringWriter();
                            var viewContext = new ViewContext(
                                actionContext,
                                viewResult.View,
                                new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
                                new TempDataDictionary(context, tempDataProvider),
                                sw,
                                new HtmlHelperOptions()
                            );
                            await viewResult.View.RenderAsync(viewContext);
                            data["testRazorHtml"] = sw.ToString();
                        }
                    };
                });
            }
        });
    }
    
  2. 在Angular服务器端模块接收数据并传递给组件
    找到main-server.ts,修改createServerRenderer的逻辑,把传递过来的Razor内容注入到组件中(或通过服务传递),示例简化版:

    import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
    import { AppServerModule } from './app/app.server.module';
    import { NgModuleRef, NgZone } from '@angular/core';
    import { TransferState } from '@angular/platform-browser';
    
    export default createServerRenderer(params => {
      const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
    
      const transferState = params.data.root.injector.get(TransferState);
      // 把预渲染时的Razor内容存入TransferState
      transferState.set(makeStateKey<string>('testRazorHtml'), params.data.testRazorHtml);
    
      return new Promise<RenderResult>((resolve, reject) => {
        const zone = NgZone.current;
        zone.run(() => {
          // ...原有渲染逻辑
        });
      });
    });
    

    之后组件就可以像方案1那样,从TransferState中直接获取Razor内容渲染了。

关键注意事项

  • 预渲染阶段是Node.js环境,HTTP请求需要使用完整的服务器地址,不能用相对路径;
  • 使用TransferState避免客户端重复请求相同内容,提升性能;
  • 如果Razor页面需要用户上下文(比如登录状态),要确保预渲染时的HttpContext包含正确的请求信息。

内容的提问来源于stack exchange,提问作者pbristow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:18:21