创建两个不会在ASP.NET MVC中生成404错误的路由的问题

时间:2022-06-05 04:03:11

I'm trying to build my tutorial project with routing. My main objective is to build two routes which won't generate 404 error in any case. By this I mean that if the path is wrong I want routing to use /Home/Index path. I have two following routes -

我正在尝试使用路由构建我的教程项目。我的主要目标是构建两条路线,在任何情况下都不会产生404错误。我的意思是,如果路径错误,我希望路由使用/ Home / Index路径。我有两条以下路线 -

    routes.MapRoute("Default", "{controller}/{action}", 
                        new {controller = "Home", action = "Index"}
                        );

    routes.MapRoute("Second", "{*catchall}",
                        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
                        );

It works fine when I use nonexistent path which doesn't matches the first route, like this -

当我使用与第一条路线不匹配的不存在的路径时,它工作正常,像这样 -

创建两个不会在ASP.NET MVC中生成404错误的路由的问题

But if it does, then I have the following -

但如果确实如此,那么我有以下几点 -

创建两个不会在ASP.NET MVC中生成404错误的路由的问题

or

要么

创建两个不会在ASP.NET MVC中生成404错误的路由的问题

I understand the reason why it happens. However at the present moment, I only managed to find 'some sort' of a solution. Adding the following code to web.config file -

我理解它发生的原因。然而,目前,我只能找到“某种”解决方案。将以下代码添加到web.config文件 -

<customErrors mode="On">
      <error statusCode="404" redirect="~/Home/Index"/>
</customErrors>

However, I don't think that it is the best way to solve this problem. Because, as far as I can understand, it simply catches all errors and redirects it to the correct path, without actually using routing. So I don't think I need this global handling.

但是,我不认为这是解决这个问题的最佳方法。因为,据我所知,它只是捕获所有错误并将其重定向到正确的路径,而不实际使用路由。所以我认为我不需要这种全局处理。

So could somebody please give me a hint or provide me with a good solution to my problem. Thank you.

那么有人可以给我一个提示,或者为我提供一个很好的解决方案。谢谢。

6 个解决方案

#1


2  

Well, you didn't really define what "wrong" routing is, so here is my definition:

好吧,你没有真正定义“错误”路由是什么,所以这是我的定义:

  1. The controller or action does not exist in the project.
  2. 项目中不存在控制器或操作。
  3. If an "id" is passed, it must exist on the action method.
  4. 如果传递“id”,则它必须存在于action方法中。

Routes

I used constraints to do this. AFAIK, it is not possible to use a constraint on an optional parameter. This means that in order to make the id parameter optional, you need 3 routes.

我用约束来做到这一点。 AFAIK,无法对可选参数使用约束。这意味着为了使id参数可选,您需要3条路线。

routes.MapRoute(
   name: "DefaultWithID",
   url: "{controller}/{action}/{id}",
   defaults: new { controller = "Home", action = "Index" },
   constraints: new { action = new ActionExistsConstraint(), id = new ParameterExistsConstraint() }
);

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

routes.MapRoute(
    name: "Second",
    url: "{*catchall}",
    defaults: new { controller = "Home", action = "Index" }
);

ActionExistsConstraint

public class ActionExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.Where(t => t.Name == (controller + "Controller")).SingleOrDefault();

            // Ensure the action method exists
            return type != null && type.GetMethod(action) != null;
        }

        return true;
    }
}

ParameterExistsConstraint

public class ParameterExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.Where(t => t.Name == (controller + "Controller")).SingleOrDefault();
            var method = type.GetMethod(action);

            if (type != null && method != null)
            {
                // Ensure the parameter exists on the action method
                var param = method.GetParameters().Where(p => p.Name == parameterName).FirstOrDefault();
                return param != null;
            }
            return false;
        }

        return true;
    }
}

#2


0  

You can do this by using a custom RouteConstraint.

您可以使用自定义RouteConstraint执行此操作。

First, set your routes like this:

首先,设置这样的路线:

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

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

        );

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

Now you can create the ControllerNameConstraint. I've done a very simple implementation here, you will need to change it so that it works with what you are trying to achieve (you have a bunch of options using reflection if you don't want to update it all the time)

现在您可以创建ControllerNameConstraint。我在这里做了一个非常简单的实现,你需要改变它,以便它与你想要实现的目标一起工作(如果你不想一直更新它,你有一堆使用反射的选项)

public class ControllerNameConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values[parameterName].ToString() == "Home" || values[parameterName].ToString() == "Account")
        {
            //the route is matched and will run the constrained ("Default") route
            return true;
        }
        //the route is not matched to the constraint and will fall through to your wildcard route
        return false;
    }
}

#3


0  

I'm doing something very similar in an attempt to kind of create my own dynamic routing based on a database-controlled site navigation. Essentially, I wanted anything that hit a true defined route to go through the normal routing process, but then I could have URLs for things like content pages controlled entirely by their placement in the navigation. Anyways, for that I relied on the httpErrors Web.config declaration:

我正在做一些非常相似的事情,试图根据数据库控制的网站导航创建我自己的动态路由。从本质上讲,我希望任何能够通过正常路由过程的任何内容都能通过正常的路由过程,但是我可以拥有完全由其在导航中的位置控制的内容页面的URL。无论如何,为此我依赖于httpErrors Web.config声明:

<system.webServer>
    ...
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404" />
        <error statusCode="404" responseMode="ExecuteURL" path="/error/404" />
    </httpErrors>

Then, I have an ErrorController with an action to process 404s. In that action, I check the attempted URL against the database and if I find a matching item, I hand off the request to the appropriate place. If there's no match, then I just return a view, which is a custom 404 view I set up. The path portion above needs to be the URL to get to your 404 action. Mine is /error/404 because I'm using attribute routing and can make it whatever I want. If you're relying on the default route, you can't have an action named 404, so it would have to be something like /error/http404 or /error/notfound.

然后,我有一个ErrorController,其中包含一个处理404s的操作。在该操作中,我检查针对数据库的尝试URL,如果找到匹配项,我将请求移交给适当的位置。如果没有匹配,那么我只返回一个视图,这是我设置的自定义404视图。上面的路径部分需要是到达404操作的URL。我是/ error / 404,因为我正在使用属性路由,可以随心所欲。如果您依赖于默认路由,则不能使用名为404的操作,因此必须使用/ error / http404或/ error / notfound。

#4


0  

The way your are specifying your default route is looking for any controller/action paring and if they are not found to substitute with the defaults. If you call out the exact home route and leave out the controller and action keywords in the url when the map is created it will only match on those and all others will be caught by your catch all.

您指定默认路由的方式是查找任何控制器/操作配对,以及是否找不到替换默认路由。如果您在创建地图时调出确切的归属路线并在网址中省略控制器和操作关键字,则只会匹配这些路线,而其他所有路由将被您的全部捕获。

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

I do not know of any other routes you may have so I could not test with your exact scenario. Try this out and see if it helps you.

我不知道您可能有任何其他路线,所以我无法测试您的具体情况。试试这个,看看它是否对你有帮助。

#5


0  

The answer from NightOwl888 is working for me, however the code for ActionExistsConstraint and ParameterExistsConstraint need to modify a little, to remove case-sensitive comparison. Here is my new code and it works perfectly in my case

NightOwl888的答案对我有用,但ActionExistsConstraint和ParameterExistsConstraint的代码需要稍微修改一下,以删除区分大小写的比较。这是我的新代码,它在我的情况下完美运行

public class ActionExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.FirstOrDefault(t => t.Name.Equals(controller + "Controller", StringComparison.OrdinalIgnoreCase));

            // Ensure the action method exists
            return type != null &&
                   type.GetMethods().Any(x => x.Name.Equals(action, StringComparison.OrdinalIgnoreCase));
        }

        return true;
    }
}

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.FirstOrDefault(t => t.Name .Equals(controller + "Controller", StringComparison.OrdinalIgnoreCase));
            var method = type.GetMethods().FirstOrDefault(x => x.Name.Equals(action, StringComparison.OrdinalIgnoreCase));

            if (type != null && method != null)
            {
                // Ensure the parameter exists on the action method
                var param = method.GetParameters().FirstOrDefault(p => p.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase));
                return param != null;
            }
            return false;
        }

        return true;
    }

#6


-2  

Please change route declaration with

请改变路线声明

 routes.MapRoute("Second", "{*catchall}",
                        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
                        );

routes.MapRoute("Default", "{controller}/{action}", 
                        new {controller = "Home"`enter code here`, action = "Index"}

                    );

#1


2  

Well, you didn't really define what "wrong" routing is, so here is my definition:

好吧,你没有真正定义“错误”路由是什么,所以这是我的定义:

  1. The controller or action does not exist in the project.
  2. 项目中不存在控制器或操作。
  3. If an "id" is passed, it must exist on the action method.
  4. 如果传递“id”,则它必须存在于action方法中。

Routes

I used constraints to do this. AFAIK, it is not possible to use a constraint on an optional parameter. This means that in order to make the id parameter optional, you need 3 routes.

我用约束来做到这一点。 AFAIK,无法对可选参数使用约束。这意味着为了使id参数可选,您需要3条路线。

routes.MapRoute(
   name: "DefaultWithID",
   url: "{controller}/{action}/{id}",
   defaults: new { controller = "Home", action = "Index" },
   constraints: new { action = new ActionExistsConstraint(), id = new ParameterExistsConstraint() }
);

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

routes.MapRoute(
    name: "Second",
    url: "{*catchall}",
    defaults: new { controller = "Home", action = "Index" }
);

ActionExistsConstraint

public class ActionExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.Where(t => t.Name == (controller + "Controller")).SingleOrDefault();

            // Ensure the action method exists
            return type != null && type.GetMethod(action) != null;
        }

        return true;
    }
}

ParameterExistsConstraint

public class ParameterExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.Where(t => t.Name == (controller + "Controller")).SingleOrDefault();
            var method = type.GetMethod(action);

            if (type != null && method != null)
            {
                // Ensure the parameter exists on the action method
                var param = method.GetParameters().Where(p => p.Name == parameterName).FirstOrDefault();
                return param != null;
            }
            return false;
        }

        return true;
    }
}

#2


0  

You can do this by using a custom RouteConstraint.

您可以使用自定义RouteConstraint执行此操作。

First, set your routes like this:

首先,设置这样的路线:

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

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

        );

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

Now you can create the ControllerNameConstraint. I've done a very simple implementation here, you will need to change it so that it works with what you are trying to achieve (you have a bunch of options using reflection if you don't want to update it all the time)

现在您可以创建ControllerNameConstraint。我在这里做了一个非常简单的实现,你需要改变它,以便它与你想要实现的目标一起工作(如果你不想一直更新它,你有一堆使用反射的选项)

public class ControllerNameConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values[parameterName].ToString() == "Home" || values[parameterName].ToString() == "Account")
        {
            //the route is matched and will run the constrained ("Default") route
            return true;
        }
        //the route is not matched to the constraint and will fall through to your wildcard route
        return false;
    }
}

#3


0  

I'm doing something very similar in an attempt to kind of create my own dynamic routing based on a database-controlled site navigation. Essentially, I wanted anything that hit a true defined route to go through the normal routing process, but then I could have URLs for things like content pages controlled entirely by their placement in the navigation. Anyways, for that I relied on the httpErrors Web.config declaration:

我正在做一些非常相似的事情,试图根据数据库控制的网站导航创建我自己的动态路由。从本质上讲,我希望任何能够通过正常路由过程的任何内容都能通过正常的路由过程,但是我可以拥有完全由其在导航中的位置控制的内容页面的URL。无论如何,为此我依赖于httpErrors Web.config声明:

<system.webServer>
    ...
    <httpErrors errorMode="Custom" existingResponse="Replace">
        <remove statusCode="404" />
        <error statusCode="404" responseMode="ExecuteURL" path="/error/404" />
    </httpErrors>

Then, I have an ErrorController with an action to process 404s. In that action, I check the attempted URL against the database and if I find a matching item, I hand off the request to the appropriate place. If there's no match, then I just return a view, which is a custom 404 view I set up. The path portion above needs to be the URL to get to your 404 action. Mine is /error/404 because I'm using attribute routing and can make it whatever I want. If you're relying on the default route, you can't have an action named 404, so it would have to be something like /error/http404 or /error/notfound.

然后,我有一个ErrorController,其中包含一个处理404s的操作。在该操作中,我检查针对数据库的尝试URL,如果找到匹配项,我将请求移交给适当的位置。如果没有匹配,那么我只返回一个视图,这是我设置的自定义404视图。上面的路径部分需要是到达404操作的URL。我是/ error / 404,因为我正在使用属性路由,可以随心所欲。如果您依赖于默认路由,则不能使用名为404的操作,因此必须使用/ error / http404或/ error / notfound。

#4


0  

The way your are specifying your default route is looking for any controller/action paring and if they are not found to substitute with the defaults. If you call out the exact home route and leave out the controller and action keywords in the url when the map is created it will only match on those and all others will be caught by your catch all.

您指定默认路由的方式是查找任何控制器/操作配对,以及是否找不到替换默认路由。如果您在创建地图时调出确切的归属路线并在网址中省略控制器和操作关键字,则只会匹配这些路线,而其他所有路由将被您的全部捕获。

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

I do not know of any other routes you may have so I could not test with your exact scenario. Try this out and see if it helps you.

我不知道您可能有任何其他路线,所以我无法测试您的具体情况。试试这个,看看它是否对你有帮助。

#5


0  

The answer from NightOwl888 is working for me, however the code for ActionExistsConstraint and ParameterExistsConstraint need to modify a little, to remove case-sensitive comparison. Here is my new code and it works perfectly in my case

NightOwl888的答案对我有用,但ActionExistsConstraint和ParameterExistsConstraint的代码需要稍微修改一下,以删除区分大小写的比较。这是我的新代码,它在我的情况下完美运行

public class ActionExistsConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.FirstOrDefault(t => t.Name.Equals(controller + "Controller", StringComparison.OrdinalIgnoreCase));

            // Ensure the action method exists
            return type != null &&
                   type.GetMethods().Any(x => x.Name.Equals(action, StringComparison.OrdinalIgnoreCase));
        }

        return true;
    }
}

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (routeDirection == RouteDirection.IncomingRequest)
        {
            var action = values["action"] as string;
            var controller = values["controller"] as string;

            var thisAssembly = this.GetType().Assembly;

            Type[] types = thisAssembly.GetTypes();

            Type type = types.FirstOrDefault(t => t.Name .Equals(controller + "Controller", StringComparison.OrdinalIgnoreCase));
            var method = type.GetMethods().FirstOrDefault(x => x.Name.Equals(action, StringComparison.OrdinalIgnoreCase));

            if (type != null && method != null)
            {
                // Ensure the parameter exists on the action method
                var param = method.GetParameters().FirstOrDefault(p => p.Name.Equals(parameterName, StringComparison.OrdinalIgnoreCase));
                return param != null;
            }
            return false;
        }

        return true;
    }

#6


-2  

Please change route declaration with

请改变路线声明

 routes.MapRoute("Second", "{*catchall}",
                        new {controller = "Home", action = "Index", id = UrlParameter.Optional}
                        );

routes.MapRoute("Default", "{controller}/{action}", 
                        new {controller = "Home"`enter code here`, action = "Index"}

                    );