Маршрутизация ASP.NET MVC с контроллером по умолчанию
Для сценария у меня есть приложение 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
Эти 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 = "" }
);
Все прошло хорошо, пока не появилось новое требование и не хочет отбрасывать ключевое слово "Клиент" с 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
Изменить: исправленные примеры ссылок, благодаря @haacked.
Я попытался добавить новый MapRoutes
, чтобы принять только {action}
и установить контроллер по умолчанию для клиента. например, /
routes.MapRoute(
"CustomerFoo"
, "{action}"
, new { controller = "Customer", action = "Index" }
);
Кажется, что это работает, однако теперь все ссылки, созданные Html.ActionLink(), являются странными и больше не являются дружественными URL.
Итак, это достижимо? Я приближаюсь в правильном направлении?
Ответы
Ответ 1
не смешивать правило вроде "{action}/{id}"
с тем, которое "{controller}/{action}/{id}"
... особенно, когда id в более поздней версии имеет значение по умолчанию, то есть необязательно.
В этом случае у вас нет ничего, что позволяло бы маршрутизации знать, какой из них является правильным.
Обходной путь, если то, что вам нужно, - добавить ограничение (см. this) к действию в предыдущем наборе значений, т.е. List, View. Конечно, с этими типами правил вы не можете иметь контроллер с тем же именем действия.
Также помните, что если вы укажете действие по умолчанию и id в правиле "{action}/{id}"
, которое будет использоваться, когда вы нажмете маршрут вашего сайта.
Ответ 2
Почему первый URL-адрес в новом списке по-прежнему имеет "Клиент". Я предполагаю, что опечатка и вы имели в виду:
Для меня работают следующие маршруты:
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 = "" }
);
Как вы создаете свои ссылки. Поскольку Контроллер больше не находится в URL вашего маршрута (иначе, у вас нет "{controller}" в URL маршрута), но это значение по умолчанию, вам нужно обязательно указать контроллер при генерации маршрутов.
Таким образом, вместо
Html.ActionLink("LinkText", "ActionName")
делать
Html.ActionLink("LinkText", "ActionName", "Customer")
Почему? Предположим, у вас были следующие маршруты.
routes.MapRoute(
"Default",
"foo/{action}",
new { controller = "Cool" }
);
routes.MapRoute(
"Default",
"bar/{action}",
new { controller = "Neat" }
);
Какой маршрут вы имели в виду, когда называете это?
<%= Html.ActionLink("LinkText", "ActionName") %>
Вы можете различать, указав контроллер, и мы выберем тот, который имеет значение по умолчанию, которое соответствует указанному.
Ответ 3
Вы можете создать маршрут, который ограничен только действиями в вашем контроллере Customer
.
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
}