如何在 Asp.Net Core 中限制控制器动作和区域中的参数类型

问题描述

我有 Asp.Net Core MVC 网络应用程序,其中有名为 AdminUI 的区域。

我的控制器如下所示:

[Area("AdminUI")]
public class ConfigurationController : BaseController
{
    public async Task<IActionResult> Client(int? id)
    {
        if (id == default)
        {
            var clientDto = _clientService.BuildClientviewmodel();
            return View(clientDto);
        }

        var client = await _clientService.GetClientAsync(id.Value);
        client = _clientService.BuildClientviewmodel(client);

        return View(client);
    }
}

而且我还有以下方法可以将功能添加到具有特定名称的整个区域的前缀:

public static IEndpointConventionBuilder MapIdentityServer4AdminUI(
    this IEndpointRouteBuilder endpoint,string patternPrefix = "/")
{
    return endpoint.MapAreaControllerRoute("AdminUI","AdminUI",patternPrefix + "{controller=Home}/{action=Index}/{id?}");
}

如果我使用这个:

public void Configure(IApplicationBuilder app,IWebHostEnvironment env,ILoggerFactory loggerFactory)
{
    app.UseRouting();
    
    app.UseEndpoints(endpoint =>
    {
        endpoint.MapIdentityServer4AdminUI("/myPrefix/");
    });
}

我可以通过以下链接访问 AdminUI 区域:

https://localhost:43000/myPrefix/Configuration/Client
https://localhost:43000/myPrefix/Configuration/Client/2

它运行良好,但现在我需要限制上面的 URL,即只能访问 id 为数字或 URL 中没有 id 参数的方法 Client。我不知道如何实现,如果我想在上面的URL中保留前缀。

我已经尝试过这个解决方案,但它不起作用:

[Route("[area]/[controller]/[action]")]
[Route("[area]/[controller]/[action]/{id:int}")]
public async Task<IActionResult> Client(int id)
{
    ...
}

这有效,我只能使用像数字这样的 ID 而没有 ID,但这会破坏 URL,因为它忽略了我上面的前缀。

网址现在是:

https://localhost:43000/AdminUI/Configuration/Client

不是:

https://localhost:43000/myPrefix/Configuration/Client

如何实现以下行为?

解决方法

[Route("[area]/[controller]/[action]")]
[Route("[area]/[controller]/[action]/{id:int}")]
public async Task<IActionResult> Client(int id)
{
    ...
}

该问题与上述 Route 属性有关。

众所周知,Action 要么按常规路由,要么按属性路由。在控制器或动作上放置路由使其属性路由。无法通过常规路由到达定义属性路由的操作,反之亦然。控制器上的任何路由属性都会路由控制器属性中的所有操作。

因此,通过使用上面的代码,它将使用属性路由。尝试更改您的代码如下:

[Route("myPrefix/[controller]/[action]")]
[Route("myPrefix/[controller]/[action]/{id:int}")]
public async Task<IActionResult> Client(int id)
{
    ...
}

参考:Mixed routing: Attribute routing vs conventional routing

更新

此外,您还可以更改路线模板,如下所示:

    public static IEndpointConventionBuilder MapIdentityServer4AdminUI(this IEndpointRouteBuilder endpoint,string patternPrefix = "/")
    {
        return endpoint.MapAreaControllerRoute("AdminUI","AdminUI",patternPrefix + "{controller=Home}/{action=Index}/{id:int?}");
    }

然后,在控制器中,删除 Route 属性。

[Area("AdminUI")]
public class ConfigurationController : BaseController
{
    public async Task<IActionResult> Client(int? id)
    {
        ...             
    }
}

编辑

我想说的是:整个AdminUI区域使用

endpoint.MapAreaControllerRoute("AdminUI",patternPrefix + "{controller=Home}/{action=Index}/{id?}") 

对于 ConfigurationController 和 action Client 使用这个:

endpoint.MapAreaControllerRoute("AdminUI",patternPrefix + "{controller=Home}/{action=Index}/{id:int?}"); 

知道如何结合这两个设置吗?

恐怕我们不能这样做(添加两个路由模板:一个用于整个 AdminUI 区域,一个用于 AdminUI 区域操作方法)。因为,如果我们同时添加它们,如果 url 与 action 方法的路由模板不匹配,它将使用区域路由模板。我能想到的唯一方法是为每个操作方法添加多个路由模板,而不是为整个区域添加路由模板。像这样:

endpoint.MapAreaControllerRoute("AdminUI",patternPrefix + "Home/Index2/{id:int}");

[注意]以上路由仅适用于Home控制器Index2动作方法。

或者使用 Route 属性。