如何在 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 的请求的主要内容,如果未能解决你的问题,请参考以下文章