假定一个场景,我们需要一个前后端分离的应用,服务端API使用ASP.NET Core开发,前端有两套,一个是用于所有用户的客户端,另一个是给管理员使用的管理后台,使用asp.net core的hosting作为后端+前端的web服务器。应为默认情况下没有开启hosting的spa功能。针对这个,我们可能首先想到的是自定义一个中间件进行处理,当请求特定path,以及静态资源是,加载对应文件夹下的spa应用文件。其实微软早就为我们准备好了这个中间件。这个中间件在:Microsoft.AspNetCore.SpaServices.Extensions 包中。
dotnet new 命令创建项目时,模板就已经内置了 angular和react的spa 模板。新建完成后,可以看到startup启动类中,多出来了两个东西:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.UseEndpoints ...
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";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
直接运行,就可以启动应用程序,就可以看到spa应用程序已经正常工作了。
默认的例子,只hosting 了一个spa应用,回到最初的问题,如果有两个或者多个需要处理呢。这就需要多次配置。
我们来做下多spa的配置。
为了整齐可读性高,我们使用IApplicationBuilder扩展的方式来配置多个SPA应用
新建SpaAPP01的扩展
/// <summary>
/// SpaAPP01
/// </summary>
/// <returns></returns>
public static IApplicationBuilder UseSpaAPP01(this IApplicationBuilder app, IConfiguration configuration)
{
// sourcePathPath 是spa应用程序build后的文件目录
var sourcePathPath = configuration["Spa:SpaAPP01:RootPath"];
// routeString 是路由模板,这里需要在spa应用打包时,所有引用的静态资源的地址前缀
// 比如 './spa001/statics/index.js' 可以参见html <base href=''> 的作用
var routeString = configuration["Spa:SpaAPP01:RouteTemplate"];
var fileOtp = new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(sourcePathPath)
};
app.Map(routeString, appBuilder =>
{
appBuilder.UseSpaStaticFiles(fileOtp);
appBuilder.UseSpa(options =>
{
options.Options.DefaultPageStaticFileOptions=fileOtp;
});
});
return app;
}
/// <summary>
/// SpaAPP02
/// </summary>
/// <returns></returns>
public static IApplicationBuilder UseSpaAPP02(this IApplicationBuilder app, IConfiguration configuration)
{
// sourcePathPath 是spa应用程序build后的文件目录
var sourcePathPath = configuration["Spa:SpaAPP02:RootPath"];
// routeString 是路由模板,这里需要在spa应用打包时,所有引用的静态资源的地址前缀
// 比如 './spa001/statics/index.js' 可以参见html <base href=''> 的作用
var routeString = configuration["Spa:SpaAPP02:RouteTemplate"];
var fileOtp = new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(sourcePathPath)
};
app.Map(routeString, appBuilder =>
{
appBuilder.UseSpaStaticFiles(fileOtp);
appBuilder.UseSpa(options =>
{
options.Options.DefaultPageStaticFileOptions=fileOtp;
});
});
return app;
}
appsettign.json
{
"Spa": {
"SpaAPP01": {
"RootPath":"E:\\spa001",
"RouteTemplate":"/spa-demo1"
},
"SpaAPP02":{
"RootPath":"E:\\spa002",
"RouteTemplate":"/spa-demo2"
}
},
}
startup
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSpaStaticFiles();
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// app.UseEndpoints ...
app.UseSpaAPP01(configuration);
app.UseSpaAPP02(configuration);
}
最后在E:\\spa001和E:\\spa002中放置build后的spa应用。启动asp.net core 应用程序,访问appsetting中设置好的对应RouteTemplate,就会打开对应的spa应用程序。spa中请求接口的路径也只需要使用接口的相对路径即可。spa引用的静态资源也会被加载。
注意:当静态资源不存在时,会默认指向index.html。 如果发现某些非html 静态文件,返回成了html格式,那就是引入路径缺少了appsetting中配置的RouteTemplate前缀。