能否在非WebAPI项目中复刻ASP.NET RouteAttribute的功能?
能否在非WebAPI项目中复刻ASP.NET RouteAttribute的功能?
当然可以!我之前在控制台项目里捣鼓过完全类似的功能,核心就是照着ASP.NET路由的思路,拆成几个小模块来实现:自定义标记属性、路由模板解析、命令匹配执行这几步,说起来不难,咱们一步步来。
首先,先定义一个和ASP.NET的[Route]几乎一样的自定义属性,用来标记你的方法:
// 自定义Route属性,标记在方法上,支持绑定多个路由 [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] public class RouteAttribute : Attribute { public string Template { get; } public RouteAttribute(string template) { Template = template ?? throw new ArgumentNullException(nameof(template)); } }
接下来是核心的路由模板解析,要能识别{param}这种占位符,把模板拆成固定段和参数段:
// 简单的路由模板解析器,处理{param}格式的占位符 public class RouteTemplateParser { public static (string[] Segments, Dictionary<string, int> ParameterPositions) Parse(string template) { var segments = template.Split('/') .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToArray(); var paramPositions = new Dictionary<string, int>(); for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; if (segment.StartsWith("{") && segment.EndsWith("}")) { var paramName = segment.Substring(1, segment.Length - 2); paramPositions[paramName] = i; // 把占位符替换成统一标记,方便后续匹配对比 segments[i] = "{param}"; } } return (segments, paramPositions); } }
然后是路由注册和匹配的核心类,负责扫描程序集里的标记方法,然后在输入命令的时候匹配并执行:
// 路由核心类,负责注册路由、匹配并执行对应的方法 public class Router { private readonly List<RouteRegistration> _registeredRoutes = new List<RouteRegistration>(); // 扫描指定程序集,注册所有标记了RouteAttribute的方法 public void RegisterRoutesFromAssembly(Assembly assembly) { var targetMethods = assembly.GetTypes() .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)) .Where(m => m.GetCustomAttributes<RouteAttribute>().Any()); foreach (var method in targetMethods) { foreach (var routeAttr in method.GetCustomAttributes<RouteAttribute>()) { var (segments, paramPositions) = RouteTemplateParser.Parse(routeAttr.Template); _registeredRoutes.Add(new RouteRegistration { TargetMethod = method, RouteSegments = segments, ParameterPositions = paramPositions }); } } } // 匹配输入的路由命令,执行对应的方法 public object? MatchAndExecute(string inputCommand, object? instance = null) { var inputSegments = inputCommand.Split('/') .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToArray(); foreach (var route in _registeredRoutes) { // 先判断段数是否一致,不一致直接跳过当前路由 if (route.RouteSegments.Length != inputSegments.Length) continue; var paramValues = new Dictionary<string, string>(); bool isMatch = true; for (int i = 0; i < route.RouteSegments.Length; i++) { var routeSeg = route.RouteSegments[i]; var inputSeg = inputSegments[i]; if (routeSeg == "{param}") { // 提取参数名和对应用户输入的值 var paramName = route.ParameterPositions.First(p => p.Value == i).Key; paramValues[paramName] = inputSeg; } else if (routeSeg != inputSeg) { // 固定段不匹配,直接结束当前路由的判断 isMatch = false; break; } } if (isMatch) { // 准备方法的参数值,做简单的类型转换 var methodParams = route.TargetMethod.GetParameters(); var invokeParams = new object[methodParams.Length]; for (int i = 0; i < methodParams.Length; i++) { var param = methodParams[i]; if (paramValues.TryGetValue(param.Name, out var value)) { invokeParams[i] = Convert.ChangeType(value, param.ParameterType); } else { // 处理可选参数或默认值,这里做简化处理 invokeParams[i] = param.DefaultValue ?? Activator.CreateInstance(param.ParameterType); } } // 执行目标方法并返回结果 return route.TargetMethod.Invoke(instance, invokeParams); } } return null; // 没有匹配到任何对应的路由 } // 内部类,存储路由与方法的映射关系 private class RouteRegistration { public MethodInfo TargetMethod { get; set; } = null!; public string[] RouteSegments { get; set; } = null!; public Dictionary<string, int> ParameterPositions { get; set; } = null!; } }
最后是在控制台项目里的实际用法,非常直观:
// 定义你的命令类,用Route属性标记需要绑定路由的方法 public class MyConsoleCommands { [Route("testFunc/{param}")] public void TestFunction(string param) { Console.WriteLine($"执行TestFunction,传入的参数是:{param}"); } [Route("greet/{username}/{age}")] public string GreetUser(string username, int age) { return $"嗨,{username}!你今年{age}岁啦~"; } } class Program { static void Main(string[] args) { var router = new Router(); // 注册当前程序集里的所有路由 router.RegisterRoutesFromAssembly(Assembly.GetExecutingAssembly()); Console.WriteLine("请输入路由命令(比如 testFunc/hello 或者 greet/张三/25):"); string userInput = Console.ReadLine()!; var executeResult = router.MatchAndExecute(userInput, new MyConsoleCommands()); if (executeResult != null) { Console.WriteLine($"执行结果:{executeResult}"); } else { Console.WriteLine("抱歉,没有匹配到对应的路由哦~"); } } }
当然啦,上面的代码是基础版,你还可以根据自己的需求扩展:
- 支持路由约束:比如
{param:int},在解析模板的时候提取约束类型,匹配时验证参数是否符合要求 - 支持可选参数:比如
[Route("test/{param?}")],解析的时候标记可选段,匹配时允许该段为空 - 支持通配符:比如
[Route("files/*")],处理任意后续的路径段 - 添加路由优先级:如果多个路由匹配同一个命令,可以给路由加优先级属性,按优先级执行
我当时就是从这个基础版本开始,慢慢迭代成更贴合自己需求的路由系统,完全能达到和ASP.NET [Route]差不多的体验~
内容来源于stack exchange
相关产品推荐
相关产品推荐

