Igo*_*nko 20 c# asp.net-core asp.net-core-2.0 asp.net-core-2.1
ASP.NET Core 2.1.1为appBuilder提供了一些看似相关的扩展方法:
UseStaticFiles 从 Microsoft.AspNetCore.StaticFilesUseSpaStaticFiles 从 Microsoft.AspNetCore.SpaServices.ExtensionsUseSpa 从 Microsoft.AspNetCore.SpaServices.Extensions请帮助我理解他们的目的和彼此之间的关系吗?
另外,如果我以不同的顺序(例如app.UseStaticFiles() -> app.UseSpaStaticFiles() -> app.UseSpa()vs app.UseSpa() -> app.UseSpaStaticFiles() -> app.UseStaticFiles())运行这些方法,那么从服务器执行的角度来看有什么不同吗?
Jez*_*Jez 33
我最近深入研究了它的工作原理,我想我应该在这里详细解释,因为我认为它没有那么详细的记录。我研究了它如何与 Visual Studio 的 React 模板配合使用,但它也适用于其他 SPA 框架。
An important point to understand is that, perhaps unlike a more traditional ASP.NET web application: the way your ASP.NET SPA site runs in development is radically different to how it runs in production.
The above is very important to remember because much of the internal implementation of this stuff only applies in production, when you're not routing requests through to a Webpack development webserver. Most of the relevant ASP.NET SPA code is located at this location in the source repo, which I'll be referencing occasionally below. If you want to really dig into the implementation, look there.
IMPORTANT UPDATE:
With .NET 6, Microsoft have completely flipped how this works and now put the Webpack/Vite/whatever SPA dev server in front of the ASP.NET dev server. Frankly, this irrirtates me and I don't like what they've done. I was perfectly happy with the proxying method before. That code is still there, but presumably they will gradually stop supporting it. This sucks. Whatever. I'll leave the guide below, but it applies to the recommended .NET 5 approach, not the .NET 6 one.
So, to go through the various SPA calls in the order in which they're called by default in the template:
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration => {
configuration.RootPath = "ClientApp/build";
});
Run Code Online (Sandbox Code Playgroud)
The comment is slightly misleading, as I'll explain below. This is called in ConfigureServices, and adds in the DefaultSpaStaticFileProvider as an ISpaStaticFileProvider for DI. This is required if you're going to call UseSpaStaticFiles later on (which you probably are). RootPath is specified here (see later on for what it does). Next (remaining calls are made in Configure):
app.UseStaticFiles();
Run Code Online (Sandbox Code Playgroud)
This is good old UseStaticFiles, tried-and-tested way of serving static files from the wwwroot directory. This is called before the others, meaning that if a requested path exists in wwwroot, it will be served immediately from there, and not looked for in the SPA directory (which is ClientApp/public by default during development, or ClientApp/build by default during production - see 'Where do the static assets get served from?' below). If it doesn't exist there, it will fall through to the next middleware, which is:
app.UseSpaStaticFiles();
Run Code Online (Sandbox Code Playgroud)
This is similar to app.UseStaticFiles, but serves static files from a different directory from wwwroot - your 'SPA' directory, which defaults to ClientApp. However, although it can technically work during development, it is only intended to do anything during production. It looks in the above-mentioned RootPath directory - something like ClientApp/build - and tries to serve files from there if they exist. This directory will exist in a published production site, and will contain the SPA content copied from ClientApp/public, and also what was generated by Webpack. However, even though UseSpaStaticFiles is still registered when the site's running in development, it will probably fall through, because ClientApp/build doesn't exist during development. Why not? If you publish your app, the ClientApp/build directory will indeed be created under your project's root directory. But the SPA templates rely on it being deleted during development, because when you run app.UseSpa later on, it eventually runs npm run start, which (if you look in package.json) will run something like:
"start": "rimraf ./build && react-scripts start",
Run Code Online (Sandbox Code Playgroud)
Notice the destruction of the build directory. UseSpaStaticFiles is relying on a npm script being triggered by a later piece of middleware to delete the build directory and effectively stop it from hijacking the pipeline during development! If you manually restore that build directory after starting the site, this middleware will serve files from it even during development. Rather Heath Robinson. As I mentioned above, the comment about React files being served from this directory 'in Production' is slightly misleading because, well, they'll be served from here during development too. It's just that there is an assumption that this directory won't exist during development. Why they didn't just put something like this in the template I'm not sure:
if (!env.IsDevelopment()) {
app.UseSpaStaticFiles();
}
Run Code Online (Sandbox Code Playgroud)
And indeed I'd recommend you put that if clause in so you're not relying on build being deleted from the filesystem.
UPDATE: I've just noticed that this middleware isn't quite as odd as I'd first thought. The DefaultSpaStaticFileProvider actually checks to see whether ClientApp/build exists upon instantiation, and if it doesn't, it doesn't create a file provider, meaning that this will reliably fall through during development when ClientApp/build doesn't exist, even if you restore the directory later. Except that my above description of the behaviour still applies the first time you run the site after publishing, because it's still true that on the first run, ClientApp/build will exist, so this check is a bit of a questionable way of detecting whether static files should never be served (like in a dev environment proxying through to an internal Webpack dev server) or not. I still think my above wrapping of UseSpaStaticFiles in a clause along the lines of if (!env.IsDevelopment()) { ... } is a more reliable and simpler way to do it, and I'm puzzled that they didn't take that approach.
But anyway, this firmware is intended to fall through 100% of the time during development, because the directory should be deleted when the request is made to ASP.NET, to the final middleware:
app.UseSpa(spa => {
//spa.Options.SourcePath = "ClientApp";
// ^ as this is only used in development, it's misleading to put it here. I've moved
// it inside the following 'if' clause.
if (env.IsDevelopment()) {
spa.Options.SourcePath = "ClientApp";
// This is called for the React SPA template, but beneath the surface it, like all
// similar development server proxies, calls
// 'SpaProxyingExtensions.UseProxyToSpaDevelopmentServer', which causes all
// requests to be routed through to a Webpack development server for React, once
// it's configured and started that server.
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
Run Code Online (Sandbox Code Playgroud)
This 'catch-all' middleware rewrites all requests to the 'default page' for the SPA framework you're using, which is determined by spa.Options.DefaultPage, which defaults to /index.html. So all requests will by default render /index.html during production, allowing the client app to always load its page and deal appropriately with requests to different URLs.
However, this middleware is NOT intended to get hit during development, because of what's inside the above if (env.IsDevelopment()) { ... } clause. That UseReactDevelopmentServer (or whatever eventually calls UseProxyToSpaDevelopmentServer, or maybe a direct call to UseProxyToSpaDevelopmentServer) adds a terminal middleware that proxies all requests to a Webpack development server, and actually prevents requests from going through to the UseSpa middleware. So, during production, this middleware runs and acts as a "catch-all" to render index.html. But during development, it is not indended to run at all, and requests should be intercepted first by a proxying middleware that forwards to a Webpack development server and returns its responses back. The Webpack development server is started in the working directory specified by spa.Options.SourcePath, so it will serve ClientApp/public/index.html as the catch-all webpage during development (/public/??? See below.) The spa.Options.SourcePath option is not used during production.
Take a given request for a file /logo.png. wwwroot will first be checked, and if it exists there, it will be served. During production, ClientApp/build will then be checked for the file, as that was the path configured in services.AddSpaStaticFiles. During development, however, this path is effectively not checked (it is, but it should always have been deleted before development starts; see above), and the path that will get checked for static assets instead is ClientApp/public in your project's root directory. This is because the request will be proxied through to an internal Webpack development server. The Webpack development server serves both dynamically-generated Webpack assets, but also static assets like /logo.png, from its "static directory" option, which defaults to public. Since the server was started in the ClientApp working directory (thanks to the spa.Options.SourcePath option), it will try to serve static assets from ClientApp/public.
Basically, ASP.NET's SPA methods are trying to roughly emulate at production what the Webpack development server does at development - serve static assets first (although ASP.NET also tries wwwroot first, which the Webpack dev server obviously doesn't do), then fall through to a default index.html for the SPA app (the Webpack dev server doesn't do this by default, but things like react-scripts have a default setup which adds a plugin causing this to happen).
However, at development, ASP.NET actually does proxy requests through to that Webpack dev server, so its SPA middleware is basically meant to get bypassed. Here's a summary of the intended flow in both cases:
Production:
Request
-> Check in /wwwroot
-> Check in /ClientApp/build
-> Rewrite request path to /index.html and serve that from /ClientApp/build
Run Code Online (Sandbox Code Playgroud)
Development:
Request
-> Check in /wwwroot
-> Proxy through to internal Webpack dev web server, which:
-> ... serves static assets from /ClientApp/public
-> ... serves dynamically-generated Webpack assets from their locations
-> ... failing that, rewrites request path to /index.html and serves that from /ClientApp/public
Run Code Online (Sandbox Code Playgroud)
Microsoft made a few design decisions I think are a bit questionable with how they did this, but one behaviour makes no sense to me and I have no idea of its use-case, but it's worth mentioning.
app.UseSpa ends up calling app.UseSpaStaticFilesInternal with the allowFallbackOnServingWebRootFiles option set to true. The only time that has an effect is if you didn't previously add an ISpaStaticFileProvider to DI (eg. by calling services.AddSpaStaticFiles), and you call app.UseSpa anyway. In that case, instead of throwing an exception, it will serve files from wwwroot. I honestly have no idea what the point of this is. The template already calls app.UseStaticFiles before anything else, so files from wwwroot already get served as top priority. If you remove that, and remove services.AddSpaStaticFiles, and don't call app.UseSpaStaticFiles (because that'll throw the exception), then app.UseSpa will serve files from wwwroot. If that has a use-case, I don't know what it is.
这个设置工作正常,但是 Webpack 开发服务器的“按需”设置似乎会启动大量节点实例,这对我来说似乎相当低效。正如本博客中“更新的开发设置”下所建议的(该博客还提供了有关 SPA 内容如何工作的一些很好的见解),在开始时手动运行 Webpack 开发服务器(或 Angular/React/其他)可能是一个更好的主意开发会话的一部分,并将按需创建开发服务器 ( spa.UseReactDevelopmentServer(npmScript: "start")) 更改为按需创建现有开发服务器的代理 ( spa.UseProxyToSpaDevelopmentServer("http://localhost:4200")),这应该避免启动 101 个节点实例。这也避免了每次 ASP.NET 源代码发生变化时重建 JS 带来的一些不必要的缓慢。
嗯...MS 付出了很大的努力来允许代理到节点支持的 Webpack 开发 Web 服务器,如果他们只是建议在生产中部署一个基于节点的 Web 服务器来代理所有 SPA,那几乎会更简单请求通过至。这将避免在生产和开发中表现不同的所有额外代码。哦,好吧,我想这样,至少当你进入生产时,你不依赖节点。但你几乎正处于发展阶段。考虑到所有 SPA 生态系统的设计方式都与 Node 配合使用,这可能是不可避免的。当谈到 SPA 应用程序时,节点实际上已成为必要的构建工具,类似于gcc. 您不需要它来运行已编译的应用程序,但您确实需要它来从源代码转换它。我想,在这个类比中,手工编写要在浏览器中运行的 JavaScript 就像手工编写程序集一样。技术上可行,但尚未真正实现。
小智 10
静态文件(例如HTML,CSS,图像和JavaScript)是ASP.NET Core应用直接提供给客户端的资产。需要进行一些配置才能启用这些文件的服务。
UseStaticFiles-在Web根目录(wwwroot文件夹)内提供文件
UseSpaStaticFiles-在angular应用程序的资产文件夹中提供静态文件,例如图片,css,js
UseSpa-让asp.net核心知道您要运行Angle App的目录,在生产模式下运行时的dist文件夹以及在dev模式下运行Angular应用的命令
例
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
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");
}
});
Run Code Online (Sandbox Code Playgroud)
小智 5
throw new InvalidOperationException($"To use {nameof(UseSpaStaticFiles)}, you must " +
$"first register an {nameof(ISpaStaticFileProvider)} in the service provider, typically " +
$"by calling services.{nameof(AddSpaStaticFiles)}.");
Run Code Online (Sandbox Code Playgroud)
因此,您需要调用app.AddSpaStaticFiles()来注册默认的ISpaStaticFileProvider
实际上,UseSpaStaticFiles和UseSpa都在内部调用相同的方法UseSpaStaticFilesInternal,但第3个参数的值不同,即allowFallbackOnServingWebRootFiles。这就是如果未注册ISpaStaticFileProvider的原因,UseSpaStaticFiles引发异常的原因,它根本不允许回退到wwwroot。
顺便说一句,如果UseSpa在内部回退到wwwroot,它将调用旧的好应用程序。UseStaticFiles(staticFileOptions);
链接到github源:1. SpaDefaultMiddleware 2. SpaStaticFilesExtensions
| 归档时间: |
|
| 查看次数: |
3643 次 |
| 最近记录: |