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

如何在 Asp.net Core Web Api 中默认使用 Newtonsoft.Json?

如何在 ASP.NET Core Web API 中使用 MassTransit 事件总线?

如何在 asp.net core web api 中绑定 Json Query 字符串

ASP.NET Core Web API - 如何在中间件管道中隐藏 DbContext 事务?

Asp.Net Core Web API 应用程序:如何更改监听地址?

如何在 ASP.NET Core Web API 中添加两个不同的令牌