正在寻找 C# URL 路由器,但不适用于 HTTP

Looking for a C# URL router, but not for HTTP

我正在寻找 C# URL 路由器组件。非常经典的东西,接受输入字符串,例如 /resources/:id/stuff 并调用适当的方法。类似于默认 ASP.net 路由或 RestfullRouting。 但是,我没有使用 HTTP,也不想要完整的 HTTP stack/framework。我正在寻找一些简单的东西来路由我的 MQTT 消息。 不知道有没有这样的组件?

以下未优化的、非真正防御性编码的代码根据路由解析 URI:

public class UriRouteParser
{
    private readonly string[] _routes;

    public UriRouteParser(IEnumerable<string> routes)
    {
        _routes = routes.ToArray();
    }

    public Dictionary<string, string> GetRouteValues(string uri)
    {
        foreach (var route in _routes)
        {
            // Build RegEx from route (:foo to named group (?<foo>[a-z0-9]+)).
            var routeFormat = new Regex("(:([a-z]+))\b").Replace(route, "(?<>[a-z0-9]+)");

            // Match uri parameter to that regex.
            var routeRegEx = new Regex(routeFormat);

            var match = routeRegEx.Match(uri);

            if (!match.Success)
            {
                continue;
            }

            // Obtain named groups.
            var result = routeRegEx.GetGroupNames().Skip(1) // Skip the "0" group
                                   .Where(g => match.Groups[g].Success && match.Groups[g].Captures.Count > 0)
                                   .ToDictionary(groupName => groupName, groupName => match.Groups[groupName].Value);
            return result;
        }

        // No match found
        return null;
    }
}

它对输入(路由和 URI)做了一些假设,但基本上它从路由中选择 :foo 参数名称并从中创建命名的捕获组,这些组与输入 URI 匹配.

这样使用:

var parser = new UriRouteParser(new []{ "/resources/:id/stuff" });
var routeValues = parser.GetRouteValues("/resources/42/stuff");

这将生成一个 { "id" = "42" } 的字典,您可以随意使用它。

我很快更改了@CodeCaster 的解决方案以附加和调用委托。

public class UriRouter
{
    // Delegate with a context object and the route parameters as parameters
    public delegate void MethodDelegate(object context, Dictionary<string, string> parameters);

    // Internal class storage for route definitions
    protected class RouteDefinition
    {
        public MethodDelegate Method;
        public string RoutePath;
        public Regex RouteRegEx;

        public RouteDefinition(string route, MethodDelegate method)
        {
            RoutePath = route;
            Method = method;

            // Build RegEx from route (:foo to named group (?<foo>[a-z0-9]+)).
            var routeFormat = new Regex("(:([a-z]+))\b").Replace(route, "(?<>[a-z0-9]+)");

            // Build the match uri parameter to that regex.
            RouteRegEx = new Regex(routeFormat);
        }
    }

    private readonly List<RouteDefinition> _routes;

    public UriRouter()
    {
        _routes = new List<RouteDefinition>();
    }

    public void DefineRoute(string route, MethodDelegate method)
    {
        _routes.Add(new RouteDefinition(route, method));
    }

    public void Route(string uri, object context)
    {
        foreach (var route in _routes)
        {
            // Execute the regex to check whether the uri correspond to the route
            var match = route.RouteRegEx.Match(uri);

            if (!match.Success)
            {
                continue;
            }

            // Obtain named groups.
            var result = route.RouteRegEx.GetGroupNames().Skip(1) // Skip the "0" group
                                   .Where(g => match.Groups[g].Success && match.Groups[g].Captures.Count > 0)
                                   .ToDictionary(groupName => groupName, groupName => match.Groups[groupName].Value);

            // Invoke the method
            route.Method.Invoke(context, result);

            // Only the first match is executed
            return;
        }

        // No match found
        throw new Exception("No match found");
    }
}

可以这样使用:

var router = new UriRouter();

router.DefineRoute("/resources/:id/stuff",
    (context, parameters) => Console.WriteLine(parameters["id"] + " - " + context)); 

router.Route("/resources/42/stuff", "abcd");

这将在标准输出上打印 42 - abcd