目前我有一个控制器,看起来像这样:
public class MyController : Controller
{
public ActionResult Action1 (int id1, int id2)
{
}
public ActionResult Action2 (int id3, int id4)
{
}
}正如您所看到的,我的两个控制器都有相同的参数"pattern",即两个不可为空的有符号整数。
我的路由配置如下所示:
routes.MapRoute(
name: "Action2",
url: "My/Action2/{id3}-{id4}",
defaults: new { controller = "My", action = "Action2", id3 = 0, id4 = 0 }
);
routes.MapRoute(
name: "Action1",
url: "My/Action1/{id1}-{id2}",
defaults: new { controller = "My", action = "Action1", id1 = 0, id2 = 0 }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);然而,除了这两个之外,我还有更多的控制器操作,所以基本上我一直在为每个操作映射一个单独的路由,这让我觉得相当混乱。
我想知道的是,我是否可以使用两个参数而不是一个参数来执行类似默认路由的操作,例如:
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id1}-{id2}",
defaults: new { controller = "Home", action = "Index", id1 = 0, id2 = 0 }
);然而,由于我的参数并不总是被命名为id1和id2,这将不起作用(错误是“参数字典包含空的参数条目”。有什么办法可以做到这一点吗?(或者是一种完全不同的方式,哪个更好?)
谢谢你的帮助!
编辑:根据到目前为止的答案,我的问题似乎有点误导。我特别想要一个带有泛型参数的路由,这样就不会绑定到特定的参数名称。
我希望能够告诉路由管理器“嘿,如果您收到一个带有模式{controller}/{action}/{parameter}-{parameter}的请求,我希望您将这两个参数传递给指定的控制器和操作,而不管它们的类型或名称!
发布于 2012-11-19 22:04:46
好吧,我决定对Radim的想法进行扩展,设计我自己的自定义路由约束(在this question中提到。
基本上,不是有许多不同的路由,我现在只有一个路由,它匹配带有两个整数参数的任何内容:
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{param1}-{param2}",
defaults: new { controller = "Admin", action = "Index" },
constraints: new { lang = new CustomRouteConstraint(new RoutePatternCollection( new List<ParamType> { ParamType.INT, ParamType.INT })) }
);在Global.asax下的Application_Start()中,我为约束设置了控制器名称空间(这比每次尝试找出它更有效):
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//should be called before RegisterRoutes
CustomRouteConstraint.SetControllerNamespace("********.Controllers");
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}最后是自定义路由约束(我知道它有一大堆代码,可能过于复杂,但我认为它是不言而喻的):
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web;
using System.Linq;
using System.Reflection;
namespace ********.Code
{
public class CustomRouteConstraint : IRouteConstraint
{
private static string controllerNamespace;
RoutePatternCollection patternCollection { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CustomRouteConstraint"/> class.
/// </summary>
/// <param name="rPC">The route pattern collection to match.</param>
public CustomRouteConstraint(RoutePatternCollection rPC)
{
this.patternCollection = rPC;
if (string.IsNullOrWhiteSpace(controllerNamespace)) {
controllerNamespace = Assembly.GetCallingAssembly().FullName.Split(new string[1] {","}, StringSplitOptions.None)
.FirstOrDefault().Trim().ToString();
}
}
/// <summary>
/// Sets the controller namespace. Should be called before RegisterRoutes.
/// </summary>
/// <param name="_namespace">The namespace.</param>
public static void SetControllerNamespace(string _namespace)
{
controllerNamespace = _namespace;
}
/// <summary>
/// Attempts to match the current request to an action with the constraint pattern.
/// </summary>
/// <param name="httpContext">The current HTTPContext of the request.</param>
/// <param name="route">The route to which the constraint belongs.</param>
/// <param name="paramName">Name of the parameter (irrelevant).</param>
/// <param name="values">The url values to attempt to match.</param>
/// <param name="routeDirection">The route direction (this method will ignore URL Generations).</param>
/// <returns>True if a match has been found, false otherwise.</returns>
public bool Match(HttpContextBase httpContext, Route route, string paramName, RouteValueDictionary values, RouteDirection routeDirection)
{
if (routeDirection.Equals(RouteDirection.UrlGeneration)) {
return false;
}
Dictionary<string, object> unMappedList = values.Where(x => x.Key.Contains("param")).OrderBy(xi => xi.Key).ToDictionary(
kvp => kvp.Key, kvp => kvp.Value);
string controller = values["controller"] as string;
string action = values["action"] as string;
Type cont = TryFindController(controller);
if (cont != null) {
MethodInfo actionMethod = cont.GetMethod(action);
if (actionMethod != null) {
ParameterInfo[] methodParameters = actionMethod.GetParameters();
if (validateParameters(methodParameters, unMappedList)) {
for (int i = 0; i < methodParameters.Length; i++) {
var key = unMappedList.ElementAt(i).Key;
var value = values[key];
values.Remove(key);
values.Add(methodParameters.ElementAt(i).Name, value);
}
return true;
}
}
}
return false;
}
/// <summary>
/// Validates the parameter lists.
/// </summary>
/// <param name="methodParameters">The method parameters for the found action.</param>
/// <param name="values">The parameters from the RouteValueDictionary.</param>
/// <returns>True if the parameters all match, false if otherwise.</returns>
private bool validateParameters(ParameterInfo[] methodParameters, Dictionary<string, object> values)
{
//@TODO add flexibility for optional parameters
if (methodParameters.Count() != patternCollection.parameters.Count()) {
return false;
}
for (int i = 0; i < methodParameters.Length; i++) {
if (!matchType(methodParameters[i], patternCollection.parameters.ElementAt(i), values.ElementAt(i).Value)) {
return false;
}
}
return true;
}
/// <summary>
/// Matches the type of the found action parameter to the expected parameter, also attempts data conversion.
/// </summary>
/// <param name="actualParam">The actual parameter of the found action.</param>
/// <param name="expectedParam">The expected parameter.</param>
/// <param name="value">The value of the RouteValueDictionary corresponding to that parameter.</param>
/// <returns>True if the parameters match, false if otherwise.</returns>
private bool matchType(ParameterInfo actualParam, ParamType expectedParam, object value)
{
try {
switch (expectedParam) {
case ParamType.BOOL:
switch (actualParam.ParameterType.ToString()) {
case "System.Boolean":
Convert.ToBoolean(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.DOUBLE:
switch (actualParam.ParameterType.ToString()) {
case "System.Double":
Convert.ToDouble(value);
return true;
break;
case "System.Decimal":
Convert.ToDecimal(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.INT:
switch (actualParam.ParameterType.ToString()) {
case "System.Int32":
Convert.ToInt32(value);
return true;
break;
case "System.Int16":
Convert.ToInt16(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.LONG:
switch (actualParam.ParameterType.ToString()) {
case "System.Int64":
Convert.ToInt64(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.STRING:
switch (actualParam.ParameterType.ToString()) {
case "System.String":
Convert.ToString(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.UINT:
switch (actualParam.ParameterType.ToString()) {
case "System.UInt32":
Convert.ToUInt32(value);
return true;
break;
case "System.UInt16":
Convert.ToUInt16(value);
return true;
break;
default:
return false;
break;
}
break;
case ParamType.ULONG:
switch (actualParam.ParameterType.ToString()) {
case "System.UInt64":
Convert.ToUInt64(value);
return true;
break;
default:
return false;
break;
}
break;
default:
return false;
}
} catch (Exception) {
return false;
}
}
/// <summary>
/// Attempts to discover a controller matching the one specified in the route.
/// </summary>
/// <param name="_controllerName">Name of the controller.</param>
/// <returns>A System.Type containing the found controller, or null if the contoller cannot be discovered.</returns>
private Type TryFindController(string _controllerName)
{
string controllerFullName;
Assembly executingAssembly = Assembly.GetExecutingAssembly();
if (!string.IsNullOrWhiteSpace(controllerNamespace)) {
controllerFullName = string.Format(controllerNamespace + ".Controllers.{0}Controller", _controllerName);
Type controller = executingAssembly.GetType(controllerFullName);
if (controller == null) {
if (controllerNamespace.Contains("Controllers")) {
controllerFullName = string.Format(controllerNamespace + ".{0}Controller", _controllerName);
if ((controller = executingAssembly.GetType(controllerFullName)) == null) {
controllerFullName = string.Format(controllerNamespace + ".{0}", _controllerName);
controller = executingAssembly.GetType(controllerFullName);
}
} else {
controllerFullName = string.Format(controllerNamespace + "Controllers.{0}", _controllerName);
controller = executingAssembly.GetType(controllerFullName);
}
}
return controller;
} else {
controllerFullName = string.Format(Assembly.GetExecutingAssembly().FullName.Split(new string[1] {","}, StringSplitOptions.None)
.FirstOrDefault().Trim().ToString() + ".Controllers.{0}Controller", _controllerName);
return Assembly.GetExecutingAssembly().GetType(controllerFullName);
}
}
}
/// <summary>
/// A list of the exepected parameters in the route.
/// </summary>
public struct RoutePatternCollection
{
public List<ParamType> parameters { get; set; }
public RoutePatternCollection(List<ParamType> _params) : this()
{
this.parameters = _params;
}
}
/// <summary>
/// The valid parameter types for a Custom Route Constraint.
/// </summary>
public enum ParamType
{
STRING,
INT,
UINT,
LONG,
ULONG,
BOOL,
DOUBLE
}
}如果您看到它们,请随时提出改进建议!
发布于 2012-11-15 02:52:27
只需查看默认路由:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);关键位是UrlParameter.Optional。这使您可以提供如下所示的操作:
public ActionResult Action1()和:
public ActionResult Action2(int id)或者甚至是:
public ActionResult Action3(int? id)(后者允许参数为空值)
强制警告
请记住,使用通用路由有几个注意事项。MVC中的模型绑定器是极其宽松的。它只是尝试从任何可以收集信息的来源中填充尽可能多的参数。因此,即使您的特定路线只经过id1,也没有什么能阻止不正当的用户使用?id2=something。如果模型绑定器可以使用它,它就会使用它。
发布于 2012-11-15 12:50:39
我想向你展示另一种方法。这可能并不像你想要的那么容易。但关键部分是从声明转移到代码。所以你可以做更多的事情来调整你的设置。
与之前的答案相比,这个解决方案是有效的,做你想做的事情。我不是在讨论你的意图,所以请把它当作一个可行的解决方案。
首先,我们将使用一个约束来扩展声明,并对id参数进行一点新奇的命名:
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{theId1}-{theId2}",
defaults: new { controller = "Home", action = "Index", theId1 = 0, theId2 = 0 },
constraints: new { lang = new MyRouteConstraint() }
);现在,在约束中,我们可以扩展值集合(重复我们的参数)
public class MyRouteConstraint : IRouteConstraint
{
public bool Match(System.Web.HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// for now skip the Url generation
if (routeDirection.Equals(RouteDirection.UrlGeneration))
{
return false;
}
// try to find out our parameters
object theId1 = null;
object theId2 = null;
var parametersExist = values.TryGetValue("theId1", out theId1)
&& values.TryGetValue("theId2", out theId2);
// not our case
if( ! parametersExist)
{
return false;
}
// first argument re-inserted
values["id1"] = theId1;
values["id3"] = theId1;
// TODO add other, remove theId1
// second argument re-inserted
values["id2"] = theId2;
values["id4"] = theId2;
// TODO add other, remove theId2
return true;
}
}此解决方案不会消除url路由设计决策的复杂性。但它允许您根据需要以可扩展的方式(e.g.reflection)在代码中解决问题。
https://stackoverflow.com/questions/13382175
复制相似问题