匹配相对于路径的路由
Match route relative to path
我希望任何以 /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}")]
你不能用属性路由完成这样的事情。只能通过实现 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 }
);
}
}
这将匹配这些网址:
http://localhost/templates/t1
http://localhost/foo/bar/templates/t2
并将它们都发送到 TemplateController.Index()
方法,最后一段作为 templateFilename
参数。
NOTE: For SEO purposes, it is generally not considered a good practice to put the same content on multiple URLs. If you do this, it is recommended to use a canonical tag to inform the search engines which of the URLs is the authoritative one.
.
我希望任何以 /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}")]
你不能用属性路由完成这样的事情。只能通过实现 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 }
);
}
}
这将匹配这些网址:
http://localhost/templates/t1
http://localhost/foo/bar/templates/t2
并将它们都发送到 TemplateController.Index()
方法,最后一段作为 templateFilename
参数。
NOTE: For SEO purposes, it is generally not considered a good practice to put the same content on multiple URLs. If you do this, it is recommended to use a canonical tag to inform the search engines which of the URLs is the authoritative one.