【问题标题】:Match route relative to path匹配路径相对于路径
【发布时间】:2018-03-08 14:02:40
【问题描述】:

我希望任何以 /templates/{filename} 结尾的 URL 使用路由属性映射到特定控制器,例如:

public class TemplateController : Controller
{
    [Route("templates/{templateFilename}")]
    public ActionResult Index(string templateFilename)
    {
        ....
    }

}

这可行,但引用此路线的链接是相对的,所以

  • http://localhost/templates/t1 -- 有效
  • http://localhost/foo/bar/templates/t2 -- 中断 (404)

我需要类似的东西:

[Route("*/templates/{templateFilename}")]

【问题讨论】:

  • 我没有看到任何以foo/bar/templates/ 开头的路线的代码。您的代码似乎按照它所说的做了。
  • 您的 URL 中不能包含文件路径 - 您的应用程序应该如何知道路由斜杠和目录树斜杠之间的区别?
  • @Jakotheshadows 我的 web.config 中有这个...
  • 据我所知,这意味着您可以提供 html 文件。这并不意味着您的路由参数可以在其中包含斜杠,而无需映射到带有这些斜杠的实际路由
  • 任何以 'templates/{thefiletemplateName}' 结尾的 url 我想映射到 TemplateController。那我该怎么做呢?

标签: c# asp.net-mvc routes url-routing asp.net-mvc-routing


【解决方案1】:

你不能用属性路由来完成这样的事情。只能通过实现IRouteConstraint 或子类化RouteBase 来进行高级路由匹配。

在这种情况下,继承RouteBase 更简单。这是一个例子:

public class EndsWithRoute : RouteBase
{
    private readonly Regex urlPattern;
    private readonly string controllerName;
    private readonly string actionName;
    private readonly string prefixName;
    private readonly string parameterName;

    public EndsWithRoute(string controllerName, string actionName, string prefixName, string parameterName)
    {
        if (string.IsNullOrWhiteSpace(controllerName))
            throw new ArgumentException($"'{nameof(controllerName)}' is required.");
        if (string.IsNullOrWhiteSpace(actionName))
            throw new ArgumentException($"'{nameof(actionName)}' is required.");
        if (string.IsNullOrWhiteSpace(prefixName))
            throw new ArgumentException($"'{nameof(prefixName)}' is required.");
        if (string.IsNullOrWhiteSpace(parameterName))
            throw new ArgumentException($"'{nameof(parameterName)}' is required.");

        this.controllerName = controllerName;
        this.actionName = actionName;
        this.prefixName = prefixName;
        this.parameterName = parameterName;
        this.urlPattern = new Regex($"{prefixName}/[^/]+/?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var path = httpContext.Request.Path;

        // Check if the URL pattern matches
        if (!urlPattern.IsMatch(path, 1))
            return null;

        // Get the value of the last segment
        var param = path.Split('/').Last();

        var routeData = new RouteData(this, new MvcRouteHandler());

        //Invoke MVC controller/action
        routeData.Values["controller"] = controllerName;
        routeData.Values["action"] = actionName;
        // Putting the myParam value into route values makes it
        // available to the model binder and to action method parameters.
        routeData.Values[parameterName] = param;

        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        object controllerObj;
        object actionObj;
        object parameterObj;

        values.TryGetValue("controller", out controllerObj);
        values.TryGetValue("action", out actionObj);
        values.TryGetValue(parameterName, out parameterObj);

        if (controllerName.Equals(controllerObj.ToString(), StringComparison.OrdinalIgnoreCase) 
            && actionName.Equals(actionObj.ToString(), StringComparison.OrdinalIgnoreCase)
            && !string.IsNullOrEmpty(parameterObj.ToString()))
        {
            return new VirtualPathData(this, $"{prefixName}/{parameterObj.ToString()}".ToLowerInvariant());
        }
        return null;
    }
}

用法

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.Add(new EndsWithRoute(
            controllerName: "Template",
            actionName: "Index",
            prefixName: "templates",
            parameterName: "templateFilename"));

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

这将匹配这些 URL:

http://localhost/templates/t1
http://localhost/foo/bar/templates/t2

并将它们都发送到TemplateController.Index() 方法,最后一段作为templateFilename 参数。

注意:出于 SEO 的目的,将相同的内容放在多个 URL 上通常不是一种好的做法。如果您这样做,建议使用canonical tag 通知搜索引擎哪个网址是权威的

See this to accomplish the same in ASP.NET Core.

【讨论】:

  • 谢谢!这就是我认为的情况。
  • 这是一项艰巨的工作,因为可以使用 RouteConstraint 完成一些事情。
  • @ErikPhilips - 试图让它与路由约束一起工作,但似乎没有办法实现它,因为传入和传出的 URL 是不同的。但是,如果您有更简洁的方法来完成此操作,那将会很有趣。
  • 传出 URL... 你的意思是服务器端重定向还是 302?
  • @ErikPhilips - 如果你走这条路线,你最终会得到一个只能匹配传入 URL 的路线,但需要你定义一个 second 路线来生成传出 URL .这是一个比简单地创建一个RouteBase 子类更复杂的配置,它可以控制双向路由(匹配传入的 URL 并为 UI 生成 URL)。如果您不需要生成 URL 来放置在 UI 上,那么使用路由约束的代码会更少,但感觉有点破,因为每次使用它时,您必须进行捕获-所有参数,否则路由约束将不起作用。
猜你喜欢
  • 2012-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-11
  • 2010-09-15
  • 2013-04-17
  • 1970-01-01
  • 2019-07-08
相关资源
最近更新 更多