You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core MVC中实现ActionLink参数以斜杠(/)替代查询字符串问号(?),并支持任意控制器、方法的任意数量参数路由映射

ASP.NET Core MVC中实现ActionLink参数以斜杠(/)替代查询字符串问号(?),并支持任意控制器、方法的任意数量参数路由映射

嘿,这个需求我之前做项目的时候刚好碰到过,要把原来带问号的查询参数改成斜杠分隔的路径格式,还得支持任意控制器、方法和参数数量,其实通过自定义路由配合参数绑定就能搞定,给你两个实用的方案:

方案一:路由参数转查询参数(兼容原有Action参数)

这个方案的思路是先定义一个能捕获所有后续路径段的路由,然后用中间件把这些路径段转换成查询参数,让ASP.NET Core默认的模型绑定器直接处理,这样你不用修改原有Action的参数定义就能直接用。

步骤1:注册自定义路由

在Program.cs里,把这个自定义路由注册在标准路由的前面(路由匹配是按顺序来的,优先匹配自定义路由):

// 支持任意数量参数的自定义Area路由
app.MapControllerRoute(
    name: "MyAreaWithArbitraryParams",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{*routeParams}"
);

// 原来的标准Area路由
app.MapControllerRoute(
    name: "MyArea",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);

这里的{*routeParams}是通配符,会把/paraA/1/paraB/2这类后续路径段全部捕获成一个字符串。

步骤2:编写中间件转换参数

创建一个中间件,把捕获到的routeParams拆分成键值对,添加到请求的查询参数里:

public class RouteParamsToQueryMiddleware
{
    private readonly RequestDelegate _next;

    public RouteParamsToQueryMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.RouteValues.TryGetValue("routeParams", out var routeParamsObj) 
            && routeParamsObj is string routeParams 
            && !string.IsNullOrEmpty(routeParams))
        {
            var segments = routeParams.Split('/');
            // 按"参数名/值"的成对格式拆分
            for (int i = 0; i < segments.Length; i += 2)
            {
                if (i + 1 >= segments.Length) break; // 处理奇数个路径段的异常情况
                context.Request.QueryString = context.Request.QueryString.Add(segments[i], segments[i + 1]);
            }
        }

        await _next(context);
    }
}

步骤3:注册中间件

在Program.cs里,把这个中间件注册在路由中间件之前:

app.UseMiddleware<RouteParamsToQueryMiddleware>();

// 后面是路由相关的中间件
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(...);

使用方式

现在你的Action可以直接接收对应的参数,不用做任何修改:

public IActionResult MyAction(string paraA, string paraB)
{
    // paraA会自动拿到1,paraB拿到2
    return View();
}

方案二:自定义模型绑定器(直接接收参数字典)

如果你的参数数量不固定,或者想统一用字典接收参数,可以用自定义模型绑定器的方式,直接把路径段转换成字典。

步骤1:注册自定义路由

和方案一的路由注册一样,先把自定义路由放在前面。

步骤2:编写模型绑定器和绑定器提供者

// 模型绑定器提供者,告诉框架什么时候用这个绑定器
public class RouteParamsModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        // 当Action参数是Dictionary<string, string>时使用这个绑定器
        if (context.Metadata.ModelType == typeof(Dictionary<string, string>))
        {
            return new RouteParamsModelBinder();
        }
        return null;
    }
}

// 具体的绑定逻辑实现
public class RouteParamsModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var routeParams = bindingContext.HttpContext.Request.RouteValues["routeParams"] as string;
        var paramDict = new Dictionary<string, string>();

        if (!string.IsNullOrEmpty(routeParams))
        {
            var segments = routeParams.Split('/');
            for (int i = 0; i < segments.Length; i += 2)
            {
                if (i + 1 >= segments.Length) break;
                paramDict[segments[i]] = segments[i + 1];
            }
        }

        bindingContext.Result = ModelBindingResult.Success(paramDict);
        return Task.CompletedTask;
    }
}

步骤3:注册模型绑定器

在Program.cs里把绑定器提供者添加到服务中:

builder.Services.AddControllersWithViews(options =>
{
    // 把自定义绑定器放在最前面,优先使用
    options.ModelBinderProviders.Insert(0, new RouteParamsModelBinderProvider());
});

使用方式

在Action里直接声明Dictionary<string, string>类型的参数:

public IActionResult MyAction(Dictionary<string, string> routeParams)
{
    var paraA = routeParams["paraA"]; // 拿到1
    var paraB = routeParams["paraB"]; // 拿到2
    return View();
}

额外:生成这种格式的URL(ActionLink扩展)

如果需要在视图或代码里生成/Area/Controller/Action/paraA/1/paraB/2这种格式的URL,可以写一个UrlHelper的扩展方法:

public static class UrlHelperExtensions
{
    public static string ActionWithRouteParams(this IUrlHelper urlHelper, string actionName, string controllerName, object routeValues, string areaName)
    {
        // 先拿到基础的Area/Controller/Action URL
        var baseUrl = urlHelper.Action(actionName, controllerName, new { area = areaName });
        if (string.IsNullOrEmpty(baseUrl)) return baseUrl;

        // 把路由参数转换成键值对列表
        var routeDict = new RouteValueDictionary(routeValues);
        var paramSegments = new List<string>();
        foreach (var kvp in routeDict)
        {
            paramSegments.Add(kvp.Key);
            paramSegments.Add(kvp.Value?.ToString() ?? string.Empty);
        }

        // 如果有参数,拼接到基础URL后面
        if (paramSegments.Any())
        {
            baseUrl += "/" + string.Join("/", paramSegments);
        }

        return baseUrl;
    }
}

在视图里使用:

@* 生成/MyArea/MyController/MyAction/paraA/1/paraB/2 *@
<a href="@Url.ActionWithRouteParams("MyAction", "MyController", new { paraA = 1, paraB = 2 }, "MyArea")">点击跳转</a>

备注:内容来源于stack exchange,提问作者WorkInProgress

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 13:52:44