ASP。NET MVC路由默认控制器

时间:2021-07-19 17:42:19

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”。我猜这是个错字,你的意思是:

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”。我猜这是个错字,你的意思是:

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
}