将 URL 变成控制器/动作对

Turning URL into controller / action pair

在 .Net MVC 中,您将路由定义到 RouteCollection 中。 URL 辅助方法可以轻松地将 controller + action + optional params 转换为 URL。

当.Net MVC处理来自客户端浏览器的请求时,它清楚地将这个URL映射到右边的controller + action,以执行适当的命令。

但是,我看不到以编程方式即时访问此路由的方法,这样我就可以将完全限定的 URL(或 10k+ URLs 的列表)转换为它是路由组件。

有谁知道你会怎么转,比如下面的字符串input:

""

进入如下输出:

{
   controller: "questions",
   action: "view",
   id: 2342325,
   seoText: "c-sharp-net-mvc-turning-url-into-controller-action-pair"
}

鉴于此映射显然是由 .Net 完成的,它是否暴露在任何地方?

为什么会有人想要这样做?

假设您有一个您知道已被访问的 URL 的列表,大多数本质上是动态的,例如 whosebug.com/questions/2342325/c-sharp-net-mvc-turning-url-into-controller-action-pair,并且您想要计算出哪些实际端点/操作/控制器是以编程方式被命中(不太关心传递的实际数据)。

可以 手写代码映射,这样你就知道 /questions/{id}/{text} -> controller: questions, action: question,但这不是面向未来的,也不是很有趣,并依赖于文本操作/处理。

给定一个路由字典和一个 URL 的列表,具有上述功能,您可以查看哪些控制器被击中最多,或者哪些动作等。

您应该看看创建自己的 MvcRouteHandler。这是 MVC 堆栈中的一点,Route Engine 已经解析了 URL 以找到要调用的 Controller 和 Action,然后它通过此方法获取实际的 C# class 和方法来调用。尚未应用授权甚至 HTTP Verb,因此您将看到对您的应用程序进行的每个调用。

public class CustomRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext context)
    {
        var controller = context.RouteData.Values["controller"];
        var action = context.RouteData.Values["action"];

        // Do whatever logging you want with this data, maybe grab the other params too.

        return base.GetHttpHandler(context);
    }
}

这可以很容易地在您设置路由的地方注册。

routes.MapRoute("Home", "{controller}/{action}", new
    {
        controller = "Home",
        action = "Index"
    })
    .RouteHandler = new CustomRouteHandler();

看起来唯一的方法是创建一个虚拟的 HTTP 上下文,类似于单元测试路由的方式。遗憾的是 MVC 没有提供对此的更好访问,因为它在每个请求上都是 运行,而不是将它包装在上下文对象中。

无论如何,这里有一个可行的解决方案,可以根据您的需要进行修改:

public class UrlToRouteMapper
{
    public static RouteValueDictionary GetRouteDataFromURL(string absoluteURL)
    {
        var testUrl = "~" + new Uri(absoluteURL).AbsolutePath;
        var context = new StubHttpContextForRouting(requestUrl: testUrl);
        var routes = new System.Web.Routing.RouteCollection();
        MvcApplication.RegisterRoutes(routes);

        System.Web.Routing.RouteData routeData = routes.GetRouteData(context);

        return routeData.Values;
    }

    public static string GetEndpointStringFromURL(string absoluteURL)
    {
        var routeData = GetRouteDataFromURL(absoluteURL);
        return routeData["controller"] + "/" + routeData["action"];
    }

}

public class StubHttpContextForRouting : HttpContextBase {
    StubHttpRequestForRouting _request;
    StubHttpResponseForRouting _response;

    public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/") {
        _request = new StubHttpRequestForRouting(appPath, requestUrl);
        _response = new StubHttpResponseForRouting();
    }

    public override HttpRequestBase Request {
        get { return _request; }
    }

    public override HttpResponseBase Response {
        get { return _response; }
    }
}

public class StubHttpRequestForRouting : HttpRequestBase {
    string _appPath;
    string _requestUrl;

    public StubHttpRequestForRouting(string appPath, string requestUrl) {
        _appPath = appPath;
        _requestUrl = requestUrl;
    }

    public override string ApplicationPath {
        get { return _appPath; }
    }

    public override string AppRelativeCurrentExecutionFilePath {
        get { return _requestUrl; }
    }

    public override string PathInfo {
        get { return ""; }
    }
}

public class StubHttpResponseForRouting : HttpResponseBase {
    public override string ApplyAppPathModifier(string virtualPath) {
        return virtualPath;
    }
}