创建两个不会在 ASP.NET MVC 中产生 404 错误的路由的问题
Problems with creating two routes which won't generate 404 error in ASP.NET MVC
我正在尝试使用路由构建我的教程项目。我的主要 objective 是构建两条在任何情况下都不会产生 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}
);
当我使用与第一条路线不匹配的不存在的路径时,它工作正常,就像这样 -
但如果确实如此,那么我有以下 -
或
我明白发生这种情况的原因。但是目前,我只设法找到了 'some sort' 的解决方案。将以下代码添加到 web.config 文件 -
<customErrors mode="On">
<error statusCode="404" redirect="~/Home/Index"/>
</customErrors>
但是,我认为这不是解决此问题的最佳方法。因为,据我所知,它只是捕获所有错误并将其重定向到正确的路径,而没有实际使用路由。所以我认为我不需要这种全局处理。
所以有人可以给我提示或为我的问题提供一个好的解决方案。谢谢。
请使用
更改路由声明
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"}
);
您可以使用自定义 RouteConstraint
。
首先,像这样设置你的路线:
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 }
);
}
现在您可以创建 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;
}
}
我正在做一些非常相似的事情,试图基于数据库控制的站点导航创建我自己的动态路由。本质上,我希望任何命中真正定义的路由的东西都可以通过正常的路由过程,但是对于内容页面之类的东西,我可以有 URLs 完全由它们在导航中的位置控制。无论如何,为此我依赖 httpErrors
Web.config 声明:
<system.webServer>
...
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/error/404" />
</httpErrors>
然后,我有一个 ErrorController
具有处理 404 的操作。在该操作中,我根据数据库检查尝试的 URL,如果找到匹配项,我将请求传递到适当的位置。如果没有匹配项,那么我只是 return 一个视图,这是我设置的自定义 404 视图。上面的路径部分需要是 URL 才能到达您的 404 操作。我的是 /error/404
因为我正在使用属性路由并且可以随心所欲地进行。如果您依赖默认路由,则不能有名为 404
的操作,因此它必须类似于 /error/http404
或 /error/notfound
.
好吧,你并没有真正定义 "wrong" 路由是什么,所以这是我的定义:
- 控制器或动作在项目中不存在。
- 如果传递 "id",它必须存在于操作方法中。
路线
我使用约束来做到这一点。 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;
}
}
参数存在约束
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;
}
}
您指定默认路由的方式是寻找任何 controller/action 配对,如果找不到则用默认路由替换。如果您在创建地图时在 url 中调出确切的家庭路线并在 url 中省略控制器和动作关键字,则它只会匹配这些关键字,而所有其他关键字都将被您的 catch all 捕获。
routes.MapRoute(
name: "Default",
url: "Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
我不知道你可能有任何其他路线,所以我无法测试你的确切场景。试试这个,看看它是否对你有帮助。
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;
}
我正在尝试使用路由构建我的教程项目。我的主要 objective 是构建两条在任何情况下都不会产生 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}
);
当我使用与第一条路线不匹配的不存在的路径时,它工作正常,就像这样 -
但如果确实如此,那么我有以下 -
或
我明白发生这种情况的原因。但是目前,我只设法找到了 'some sort' 的解决方案。将以下代码添加到 web.config 文件 -
<customErrors mode="On">
<error statusCode="404" redirect="~/Home/Index"/>
</customErrors>
但是,我认为这不是解决此问题的最佳方法。因为,据我所知,它只是捕获所有错误并将其重定向到正确的路径,而没有实际使用路由。所以我认为我不需要这种全局处理。
所以有人可以给我提示或为我的问题提供一个好的解决方案。谢谢。
请使用
更改路由声明 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"}
);
您可以使用自定义 RouteConstraint
。
首先,像这样设置你的路线:
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 }
);
}
现在您可以创建 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;
}
}
我正在做一些非常相似的事情,试图基于数据库控制的站点导航创建我自己的动态路由。本质上,我希望任何命中真正定义的路由的东西都可以通过正常的路由过程,但是对于内容页面之类的东西,我可以有 URLs 完全由它们在导航中的位置控制。无论如何,为此我依赖 httpErrors
Web.config 声明:
<system.webServer>
...
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" />
<error statusCode="404" responseMode="ExecuteURL" path="/error/404" />
</httpErrors>
然后,我有一个 ErrorController
具有处理 404 的操作。在该操作中,我根据数据库检查尝试的 URL,如果找到匹配项,我将请求传递到适当的位置。如果没有匹配项,那么我只是 return 一个视图,这是我设置的自定义 404 视图。上面的路径部分需要是 URL 才能到达您的 404 操作。我的是 /error/404
因为我正在使用属性路由并且可以随心所欲地进行。如果您依赖默认路由,则不能有名为 404
的操作,因此它必须类似于 /error/http404
或 /error/notfound
.
好吧,你并没有真正定义 "wrong" 路由是什么,所以这是我的定义:
- 控制器或动作在项目中不存在。
- 如果传递 "id",它必须存在于操作方法中。
路线
我使用约束来做到这一点。 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;
}
}
参数存在约束
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;
}
}
您指定默认路由的方式是寻找任何 controller/action 配对,如果找不到则用默认路由替换。如果您在创建地图时在 url 中调出确切的家庭路线并在 url 中省略控制器和动作关键字,则它只会匹配这些关键字,而所有其他关键字都将被您的 catch all 捕获。
routes.MapRoute(
name: "Default",
url: "Home/Index/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
我不知道你可能有任何其他路线,所以我无法测试你的确切场景。试试这个,看看它是否对你有帮助。
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;
}