For a scenario, I have a ASP.NET MVC application with URLs that look like the following:
对于一个场景,我有一个ASP。NET MVC应用程序的url如下:
http://example.com/Customer/List
http://example.com/Customer/List/Page/2
http://example.com/Customer/List
http://example.com/Customer/View/8372
http://example.com/Customer/Search/foo/Page/5
These URLs are achieved with following routes in Global.asax.cs
这些url是通过以下路线在Global.asax.cs中实现的
routes.MapRoute(
"CustomerSearch"
, "Customer/Search/{query}/Page/{page}"
, new { controller = "Customer", action = "Search" }
);
routes.MapRoute(
"CustomerGeneric"
, "Customer/{action}/{id}/Page/{page}"
, new { controller = "Customer" }
);
//-- Default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Customer", action = "Index", id = "" }
);
These all have gone well until a new requirement arrived and wants to drop keyword 'Customer' off the URL, to make the URLs look like:
这些都进行得很顺利,直到一个新的需求到来,并想删除关键字“客户”的URL,使URL看起来像:
http://example.com/List
http://example.com/List/Page/2
http://example.com/List
http://example.com/View/8372
http://example.com/Search/foo/Page/5
Edit: corrected example links, thanks to @haacked.
编辑:更正示例链接,感谢@haacked。
I tried to add new MapRoutes
to take {action}
only and have default controller set to Customer. eg/
我尝试添加新的maproute以只执行{action},并将默认控制器设置为Customer。例如/
routes.MapRoute(
"CustomerFoo"
, "{action}"
, new { controller = "Customer", action = "Index" }
);
This seems to work, however now all links generated by Html.ActionLink() are weird and no longer URL friendly.
这似乎是可行的,但是现在Html.ActionLink()生成的所有链接都很奇怪,不再对URL友好。
So, is this achievable? Am I approaching in the right direction?
所以,这是可以实现的吗?我的方向对吗?
3 个解决方案
#1
16
don't mix a rule like: "{action}/{id}"
with one that's "{controller}/{action}/{id}"
... specially when id in the later has a default value i.e. is optional.
不要将诸如:“action}/{id}”这样的规则与“{controller}/{action}/{id}”混合在一起。特别是当后面的id有一个默认值时,即是可选的。
In that case you have nothing that allows routing to know which one is the right one to use.
在这种情况下,没有任何东西允许路由知道使用哪个是正确的。
A workaround, if that's what you need, would be to add a constrain (see this) to the action in the earlier to a set of values i.e. List, View. Of course that with these types of rules, you can't have a controller with the same name of an action.
一个解决方案,如果您需要的话,是在前面的操作中添加一个约束(请参见这个)到一组值,即List, View。当然,对于这些类型的规则,您不能有一个具有相同名称的操作的控制器。
Also remember that if you specify a default action & id in the "{action}/{id}"
rule, that will be used when you hit the route of your site.
还要记住,如果您在“{action}/{id}”规则中指定了一个默认的操作和id,那么当您点击站点的路由时将使用该操作和id。
#2
10
Why does the first URL in the new list still have "Customer". I assume that's a typo and you meant:
为什么新列表中的第一个URL仍然有“Customer”。我猜这是个错字,你的意思是:
- http://example.com/List
- http://example.com/List
- http://example.com/List/Page/2
- http://example.com/List/Page/2
- http://example.com/List
- http://example.com/List
- http://example.com/View/8372
- http://example.com/View/8372
- http://example.com/Search/foo/Page/5
- http://example.com/Search/foo/Page/5
The following routes work for me:
以下是我的工作路线:
routes.MapRoute(
"CustomerSearch"
, "Search/{query}/Page/{page}"
, new { controller = "Customer", action = "Search" }
);
routes.MapRoute(
"CustomerGeneric"
, "{action}/{id}/Page/{page}"
, new { controller = "Customer" }
);
//-- Default Route
routes.MapRoute(
"Default",
"{action}/{id}",
new { controller = "Customer", action = "Index", id = "" }
);
How are you generating your links. Since the Controller is no longer in the URL of your route (aka, you don't have "{controller}" in the route URL), but it's a default value, you need to make sure to specify the controller when generating routes.
如何生成链接。由于控制器不在路由的URL中(也就是说,在路由URL中没有“{Controller}”),但是它是一个默认值,所以您需要确保在生成路由时指定控制器。
Thus instead of
因此,而不是
Html.ActionLink("LinkText", "ActionName")
do
做
Html.ActionLink("LinkText", "ActionName", "Customer")
Why? Suppose you had the following routes.
为什么?假设您有以下路线。
routes.MapRoute(
"Default",
"foo/{action}",
new { controller = "Cool" }
);
routes.MapRoute(
"Default",
"bar/{action}",
new { controller = "Neat" }
);
Which route did you mean when you call this?
你说这是哪条路线?
<%= Html.ActionLink("LinkText", "ActionName") %>
You can differentiate by specifying the controller and we'll pick the one that has a default value that matches the specified one.
您可以通过指定控制器来进行区分,我们将选择具有与指定值匹配的默认值的那个。
#3
2
You can create a route that is constrained to only match actions in your Customer
controller.
您可以创建一个约束为仅匹配客户控制器中的操作的路由。
public static class RoutingExtensions {
///<summary>Creates a route that maps URLs without a controller to action methods in the specified controller</summary>
///<typeparam name="TController">The controller type to map the URLs to.</typeparam>
public static void MapDefaultController<TController>(this RouteCollection routes) where TController : ControllerBase {
routes.MapControllerActions<TController>(typeof(TController).Name, "{action}/{id}", new { action = "Index", id = UrlParameter.Optional });
}
///<summary>Creates a route that only matches actions from the given controller.</summary>
///<typeparam name="TController">The controller type to map the URLs to.</typeparam>
public static void MapControllerActions<TController>(this RouteCollection routes, string name, string url, object defaults) where TController : ControllerBase {
var methods = typeof(TController).GetMethods()
.Where(m => !m.ContainsGenericParameters)
.Where(m => !m.IsDefined(typeof(ChildActionOnlyAttribute), true))
.Where(m => !m.IsDefined(typeof(NonActionAttribute), true))
.Where(m => !m.GetParameters().Any(p => p.IsOut || p.ParameterType.IsByRef))
.Select(m => m.GetActionName());
routes.Add(name, new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults) { { "controller", typeof(TController).Name.Replace("Controller", "") } },
Constraints = new RouteValueDictionary { { "action", new StringListConstraint(methods) } }
});
}
private static string GetActionName(this MethodInfo method) {
var attr = method.GetCustomAttribute<ActionNameAttribute>();
if (attr != null)
return attr.Name;
return method.Name;
}
class StringListConstraint : IRouteConstraint {
readonly HashSet<string> validValues;
public StringListConstraint(IEnumerable<string> values) { validValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase); }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
return validValues.Contains(values[parameterName]);
}
}
#region GetCustomAttributes
///<summary>Gets a custom attribute defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<returns>The first attribute of the type defined on the member, or null if there aren't any</returns>
public static TAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider provider) where TAttribute : Attribute {
return provider.GetCustomAttribute<TAttribute>(false);
}
///<summary>Gets the first custom attribute defined on a member, or null if there aren't any.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<param name="inherit">Whether to look up the hierarchy chain for attributes.</param>
///<returns>The first attribute of the type defined on the member, or null if there aren't any</returns>
public static TAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider provider, bool inherit) where TAttribute : Attribute {
return provider.GetCustomAttributes<TAttribute>(inherit).FirstOrDefault();
}
///<summary>Gets the custom attributes defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
public static TAttribute[] GetCustomAttributes<TAttribute>(this ICustomAttributeProvider provider) where TAttribute : Attribute {
return provider.GetCustomAttributes<TAttribute>(false);
}
///<summary>Gets the custom attributes defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<param name="inherit">Whether to look up the hierarchy chain for attributes.</param>
public static TAttribute[] GetCustomAttributes<TAttribute>(this ICustomAttributeProvider provider, bool inherit) where TAttribute : Attribute {
if (provider == null) throw new ArgumentNullException("provider");
return (TAttribute[])provider.GetCustomAttributes(typeof(TAttribute), inherit);
}
#endregion
}
#1
16
don't mix a rule like: "{action}/{id}"
with one that's "{controller}/{action}/{id}"
... specially when id in the later has a default value i.e. is optional.
不要将诸如:“action}/{id}”这样的规则与“{controller}/{action}/{id}”混合在一起。特别是当后面的id有一个默认值时,即是可选的。
In that case you have nothing that allows routing to know which one is the right one to use.
在这种情况下,没有任何东西允许路由知道使用哪个是正确的。
A workaround, if that's what you need, would be to add a constrain (see this) to the action in the earlier to a set of values i.e. List, View. Of course that with these types of rules, you can't have a controller with the same name of an action.
一个解决方案,如果您需要的话,是在前面的操作中添加一个约束(请参见这个)到一组值,即List, View。当然,对于这些类型的规则,您不能有一个具有相同名称的操作的控制器。
Also remember that if you specify a default action & id in the "{action}/{id}"
rule, that will be used when you hit the route of your site.
还要记住,如果您在“{action}/{id}”规则中指定了一个默认的操作和id,那么当您点击站点的路由时将使用该操作和id。
#2
10
Why does the first URL in the new list still have "Customer". I assume that's a typo and you meant:
为什么新列表中的第一个URL仍然有“Customer”。我猜这是个错字,你的意思是:
- http://example.com/List
- http://example.com/List
- http://example.com/List/Page/2
- http://example.com/List/Page/2
- http://example.com/List
- http://example.com/List
- http://example.com/View/8372
- http://example.com/View/8372
- http://example.com/Search/foo/Page/5
- http://example.com/Search/foo/Page/5
The following routes work for me:
以下是我的工作路线:
routes.MapRoute(
"CustomerSearch"
, "Search/{query}/Page/{page}"
, new { controller = "Customer", action = "Search" }
);
routes.MapRoute(
"CustomerGeneric"
, "{action}/{id}/Page/{page}"
, new { controller = "Customer" }
);
//-- Default Route
routes.MapRoute(
"Default",
"{action}/{id}",
new { controller = "Customer", action = "Index", id = "" }
);
How are you generating your links. Since the Controller is no longer in the URL of your route (aka, you don't have "{controller}" in the route URL), but it's a default value, you need to make sure to specify the controller when generating routes.
如何生成链接。由于控制器不在路由的URL中(也就是说,在路由URL中没有“{Controller}”),但是它是一个默认值,所以您需要确保在生成路由时指定控制器。
Thus instead of
因此,而不是
Html.ActionLink("LinkText", "ActionName")
do
做
Html.ActionLink("LinkText", "ActionName", "Customer")
Why? Suppose you had the following routes.
为什么?假设您有以下路线。
routes.MapRoute(
"Default",
"foo/{action}",
new { controller = "Cool" }
);
routes.MapRoute(
"Default",
"bar/{action}",
new { controller = "Neat" }
);
Which route did you mean when you call this?
你说这是哪条路线?
<%= Html.ActionLink("LinkText", "ActionName") %>
You can differentiate by specifying the controller and we'll pick the one that has a default value that matches the specified one.
您可以通过指定控制器来进行区分,我们将选择具有与指定值匹配的默认值的那个。
#3
2
You can create a route that is constrained to only match actions in your Customer
controller.
您可以创建一个约束为仅匹配客户控制器中的操作的路由。
public static class RoutingExtensions {
///<summary>Creates a route that maps URLs without a controller to action methods in the specified controller</summary>
///<typeparam name="TController">The controller type to map the URLs to.</typeparam>
public static void MapDefaultController<TController>(this RouteCollection routes) where TController : ControllerBase {
routes.MapControllerActions<TController>(typeof(TController).Name, "{action}/{id}", new { action = "Index", id = UrlParameter.Optional });
}
///<summary>Creates a route that only matches actions from the given controller.</summary>
///<typeparam name="TController">The controller type to map the URLs to.</typeparam>
public static void MapControllerActions<TController>(this RouteCollection routes, string name, string url, object defaults) where TController : ControllerBase {
var methods = typeof(TController).GetMethods()
.Where(m => !m.ContainsGenericParameters)
.Where(m => !m.IsDefined(typeof(ChildActionOnlyAttribute), true))
.Where(m => !m.IsDefined(typeof(NonActionAttribute), true))
.Where(m => !m.GetParameters().Any(p => p.IsOut || p.ParameterType.IsByRef))
.Select(m => m.GetActionName());
routes.Add(name, new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults) { { "controller", typeof(TController).Name.Replace("Controller", "") } },
Constraints = new RouteValueDictionary { { "action", new StringListConstraint(methods) } }
});
}
private static string GetActionName(this MethodInfo method) {
var attr = method.GetCustomAttribute<ActionNameAttribute>();
if (attr != null)
return attr.Name;
return method.Name;
}
class StringListConstraint : IRouteConstraint {
readonly HashSet<string> validValues;
public StringListConstraint(IEnumerable<string> values) { validValues = new HashSet<string>(values, StringComparer.OrdinalIgnoreCase); }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) {
return validValues.Contains(values[parameterName]);
}
}
#region GetCustomAttributes
///<summary>Gets a custom attribute defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<returns>The first attribute of the type defined on the member, or null if there aren't any</returns>
public static TAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider provider) where TAttribute : Attribute {
return provider.GetCustomAttribute<TAttribute>(false);
}
///<summary>Gets the first custom attribute defined on a member, or null if there aren't any.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<param name="inherit">Whether to look up the hierarchy chain for attributes.</param>
///<returns>The first attribute of the type defined on the member, or null if there aren't any</returns>
public static TAttribute GetCustomAttribute<TAttribute>(this ICustomAttributeProvider provider, bool inherit) where TAttribute : Attribute {
return provider.GetCustomAttributes<TAttribute>(inherit).FirstOrDefault();
}
///<summary>Gets the custom attributes defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
public static TAttribute[] GetCustomAttributes<TAttribute>(this ICustomAttributeProvider provider) where TAttribute : Attribute {
return provider.GetCustomAttributes<TAttribute>(false);
}
///<summary>Gets the custom attributes defined on a member.</summary>
///<typeparam name="TAttribute">The type of attribute to return.</typeparam>
///<param name="provider">The object to get the attribute for.</param>
///<param name="inherit">Whether to look up the hierarchy chain for attributes.</param>
public static TAttribute[] GetCustomAttributes<TAttribute>(this ICustomAttributeProvider provider, bool inherit) where TAttribute : Attribute {
if (provider == null) throw new ArgumentNullException("provider");
return (TAttribute[])provider.GetCustomAttributes(typeof(TAttribute), inherit);
}
#endregion
}