如何在 ASP .NET Core Web API 中映射回退,以便 Blazor WASM 应用程序仅拦截未发送到 API 的请求
Posted
技术标签:
【中文标题】如何在 ASP .NET Core Web API 中映射回退,以便 Blazor WASM 应用程序仅拦截未发送到 API 的请求【英文标题】:How to map fallback in ASP .NET Core Web API so that Blazor WASM app only intercepts requests that are not to the API 【发布时间】:2020-04-17 09:22:32 【问题描述】:我有一个 Blazor WebAssembly 解决方案,其中包含一个客户端项目、服务器项目和共享项目,基于 Microsoft 的默认解决方案模板。我正在使用 Google Chrome 在 Visual Studio 2019 预览版中进行编辑和调试。
开箱即用,该解决方案有一个启动项目,即服务器应用程序。该服务器应用程序具有对客户端应用程序的项目引用。您可以通过在服务器项目属性中选中“启用 SSL”来将其设置为使用 HTTPS,我已经这样做了。
当您点击调试时,它会完美运行。
现在我想更改它,以便 Blazor WASM 应用程序仅响应来自 https://localhost:44331 的请求,而不响应来自 https://localhost:44331/api 的请求。这些请求应该由服务器应用程序的 API 控制器端点处理。因此,如果有人访问 https://localhost:44331/api/something,但不存在这样的 API 端点,他们应该从 API 收到 404 错误代码,并且不会被路由到通常的 Blazor 页面,上面写着“抱歉,这个地址没有任何东西。”
我想使用 URL 的这个额外的“/api”部分来保持对 API 的请求与对页面的请求分开。我认为这将更接近生产中的正常设置。我希望很清楚我想要做什么。
这是一个带有路由属性的示例控制器声明:
namespace BlazorApp2.Server.Controllers
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
// Etc.
[HttpGet]
public IEnumerable<WeatherForecast> Get()
//etc.
///etc.
这是我在 Startup.cs 中尝试过的,但它不起作用。任何人都可以提出一些可以取悦的建议吗?
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
// Etc.
app.UseStatusCodePages();
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
endpoints.MapControllers();
// The line commented out below is the out-of-the-box behaviour for a Blazor WASM app with ASP NET Core API. This is the line I want to replace.
// endpoints.MapFallbackToFile("index.html");
// The line below is my (failed) attempt to get the behaviour I want.
endpoints.MapFallback(HandleFallback);
);
private async Task HandleFallback(HttpContext context)
var apiPathSegment = new PathString("/api"); // Find out from the request URL if this is a request to the API or just a web page on the Blazor WASM app.
bool isApiRequest = context.Request.Path.StartsWithSegments(apiPathSegment);
if (!isApiRequest)
context.Response.Redirect("index.html"); // This is a request for a web page so just do the normal out-of-the-box behaviour.
else
context.Response.StatusCode = StatusCodes.Status404NotFound; // This request had nothing to do with the Blazor app. This is just an API call that went wrong.
请问有人知道如何按照我的意愿进行操作吗?
【问题讨论】:
你能发布一个你的 API 控制器路由示例吗? 我当然会。我马上就会把它放在我的问题中 【参考方案1】:概括一下问题,当有人提出以下请求时:
https://yourapp.com/api/someendpoint
和 /api/someendpoint
找不到,它们被带到 Blazor 页面。这种默认行为很奇怪。对于以/api
开头的请求,他们期待一个 HTTP 状态代码,也可能是一个 JSON 对象,但相反,他们得到了 HTML。也许他们甚至不使用您的应用程序。也许他们甚至不是人类(更有可能他们是一个软件)。
这就是您向他们发送 HTTP 状态代码的方式。 在您的控制器上:
[Route("api/[controller]")]
public class SampleController : ControllerBase
// ...
在 Startup.cs 中:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
// ...
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.Map("api/**slug", HandleApiFallback);
endpoints.MapFallbackToFile("**slug", "index.html");
);
private Task HandleApiFallback(HttpContext context)
context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
【讨论】:
我来晚了(无论如何我正在调查另一个问题),但我认为对这种情况更灵活的解决方案是使用类似的东西:endpoints.MapFallbackToController("return Task.FromResult(0);
更改为return Task.CompletedTask;
,因为它更清晰和惯用。
这是一个公平的观点。谢谢你。我会更新的。
我似乎面临的问题是,使用这种方法,如果您尝试 POST api/path
而不是 GET api/path
,您将得到 404 而不是 MethodNotAllowed
。有什么办法可以纠正这个吗?如果方法错误,404 对调试非常没有帮助。
我在这里解决了我的问题:***.com/a/69844850/526704【参考方案2】:
很确定这应该可行:
endpoints.MapFallbackToFile("*path:regex(^(?!api).*$)", "index.html"); // don't match paths beginning with api
我认为这意味着只匹配路径不以 api 开头的 URL。
【讨论】:
使用 .NET 6 blazor 我使用此方法收到以下错误:ArgumentException: 'index.html' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/')
这可能是最好的解决方案,只是您需要使用 MapFallbackToFile,因为它用于静态文件,而不是用于映射到 Razor 页面的 MapFallbackToPage。【参考方案3】:
如果您从 Blazor WASM 托管解决方案开始,您将获得一个示例,只需启动
dotnet new blazorwasm --hosted
它创建了一个包含 3 个项目的解决方案:
|-- 客户端 |-- 服务器 |-- 共享
在 Server Startup
类中,中间件管道设置如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
else
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
);
控制器使用Route
属性定义其路由。如果您想在 /api/controller 上托管控制器,请使用 Route
属性值 api/[controller]
:
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
【讨论】:
也许我没有正确解释这一点。从您在上面发布的此默认模板开始,但现在您希望从以“api”开头的 URL(如 localhost:44331/api/weatherforecasts)提供 API 端点,并且希望从不以“api”开头的 URL 提供 Blazor 页面,如localhost:44331/fetchdata. 因此,如果有人进入 API 端点,您根本不希望 Blazor 应用程序参与其中。因此,访问“localhost/4331/api/somethingnotthere”不会导致 Blazor 页面显示“抱歉,此地址没有任何内容”。它只会导致一个简单的 404 Http 状态代码响应。我希望这很清楚现在的目标是什么。 好吧,我不明白你为什么想要这种行为,但顺便说一句,你可以编写一个中间件来检查未知路由 你能解释一下为什么你想要默认行为吗?默认行为是有人发出了一个 API 请求,期待一个 Http 状态代码,并且可能是一个 JSON 对象,但最终却出现在 Blazor 应用程序的 HTML 页面上。也许他们以前从未使用过您的 Blazor 应用程序,并且只对使用您的 API 感兴趣。也许他们甚至不是人;API 请求也可以来自其他软件。所以,在我看来,默认行为很奇怪,而我试图创建的行为是正常的......我错了吗? 是的,在这种情况下,如果 URL 不在预定义的表中,则编写一个中间件来检查返回 404 的已知 URL。【参考方案4】:使用来自@Darragh 的代码出现以下错误:
endpoints.MapFallbackToPage("path:regex(^(?!api).$)", "index.html");
System.ArgumentException: ''index.html' 不是有效的页面名称。一种 页面名称是相对于 Razor Pages 根目录的路径 以前导正斜杠 ('/') 开头并且不包含 文件扩展名,例如“/Users/Edit”。 (参数'pageName')'
如果我像原始代码一样使用MapFallbackToFile
而不是MapFallbackToPage
,代码将运行。
但是,当我测试正则表达式时,它匹配了包括 API URL 在内的所有内容:
https://regex101.com/r/nq7FCi/1
我的正则表达式看起来像这样:^(?!.*?(?:\/api\/)).*$
基于这个答案:
https://***.com/a/23207219/3850405
https://regex101.com/r/qmftyc/1
在测试时它无论如何都不起作用,并且包含/api/
的网址被重定向到index.html
。
我的最终代码基于@benjamin 的答案,但最后使用的是原始MapFallbackToFile
。
app.UseEndpoints(endpoints =>
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.Map("api/**slug", HandleApiFallback);
endpoints.MapFallbackToFile("index.html");
);
private Task HandleApiFallback(HttpContext context)
context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
【讨论】:
正则表达式只能针对“路径”段进行测试...而不是绝对 URL 你能解释一下这里的 **slug 代表什么吗? @NSS 你可以找到一些有用的信息表单docs.microsoft.com/en-us/aspnet/core/fundamentals/… 我似乎面临的问题是,使用这种方法,如果您尝试使用POST api/path
而不是GET api/path
,您将得到 404 而不是 1MethodNotAllowed`。有什么办法可以纠正这个吗?如果您的 http 方法错误,或者任何其他可能发生的错误被 404 覆盖,404 对调试非常没有帮助
我在这里解决了我的问题:***.com/a/69844850/526704【参考方案5】:
我已经用 Blazor WebAssembly .NET 5 尝试过这个。发布到 IIS 后,之前建议的解决方案不起作用。
here 提供了此问题的答案。 经过测试并且可以正常工作。
很快:
编辑 wwwroot\service-worker.published.js 文件并添加要排除的路径,在本例中为 /api/
。
const shouldServeIndexHtml = event.request.mode === 'navigate' &&
!event.request.url.includes('/api/')
【讨论】:
【参考方案6】:您可以通过为不以 /api
开头的路径显式映射 Blazor 回退来解决此问题,然后只为那些以 /api
开头的路径映射 api 路径,就像我在 this answer to my owner question 中提到的那样。这带来的好处是,如果您尝试将POST
传递给GET
api 方法,那么您将获得405
的正确api 响应,或者该api 通常会返回的任何其他错误,而不是仅仅返回404
请求。
//explicitly only use blazor when the path doesn't start with api
app.MapWhen(ctx => !ctx.Request.Path.StartsWithSegments("/api"), blazor =>
blazor.UseBlazorFrameworkFiles();
blazor.UseStaticFiles();
blazor.UseRouting();
blazor.UseEndpoints(endpoints =>
endpoints.MapFallbackToFile("index.html");
);
);
//explicitly map api endpoints only when path starts with api
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), api =>
//if you are not using a blazor app, you can move these files out of this closure
api.UseStaticFiles();
api.UseRouting();
api.UseEndpoints(endpoints =>
endpoints.MapControllers();
);
);
【讨论】:
以上是关于如何在 ASP .NET Core Web API 中映射回退,以便 Blazor WASM 应用程序仅拦截未发送到 API 的请求的主要内容,如果未能解决你的问题,请参考以下文章