所以,我在我的MVC应用程序中配置了一个路由,如下所示:
的 Routing.xml 的的
<route name="note" url="{noteId}-{title}">
<constraints>
<segment name="noteId" value="\d+" />
</constraints>
</route>
不要注意XML格式。
所以,我有点冲刺&#34; - &#34;在段之间,似乎MVC路由处理程序有一些问题。
如果我转到 / 45689-anything-here / 这样的网址,则找不到该路线。
但是,如果我将短划线改为下划线&#34; _&#34;在路由配置中:
{noteId}_{title}
路线已正确映射,我可以转到 / 45689_anything- here /
似乎问题是破折号。不幸的是,我需要在网址中加入破折号,你们知道如何解决这个问题吗?
提前致谢!
答案 0 :(得分:1)
我搜索了几个小时没有结果。我发现的最好的方法是使用 Catch All 和 ErrorController 循环遍历映射的路由并以正则表达式方式测试它们。因此,当一个请求没有匹配的路由时,它会进入ErrorController,它会以我的方式测试所有现有路由(所以那里没有重复的路由),如果匹配,行动被召唤。所以这是代码。
最后路线:
routes.MapRoute(
"NotFound",
"{*url}",
new { controller = "Error", action = "PageNotFound" }
);
ErrorController:路由调用PageNotFound操作。如果匹配,则调用专用的Run操作以使用参数运行操作。
public class ErrorController : Controller
{
private static readonly List<string> UrlNotContains = new List<string>{ "images/" };
// GET: Error
public ActionResult PageNotFound(string url)
{
if (url == null)
return HttpNotFound();
var routes = System.Web.Routing.RouteTable.Routes; /* Get routes */
foreach (var route in routes.Take(routes.Count - 3).Skip(2)) /* iterate excluding 2 firsts and 3 lasts (in my case) */
{
var r = (Route)route;
// Replace parameters by regex catch groups
var pattern = Regex.Replace(r.Url, @"{[^{}]*}", c =>
{
var parameterName = c.Value.Substring(1, c.Value.Length - 2);
// If parameter's constaint is a string, use it as the body
var body = r.Constraints.ContainsKey(parameterName) && r.Constraints[parameterName] is string
? r.Constraints[parameterName]
: @".+";
return $@"(?<{parameterName}>{body})";
});
// Test regex !
var regex = new Regex(pattern);
Match match = regex.Match(url);
if (!match.Success)
continue;
// If match, call the controller
var controllerName = r.Defaults["controller"].ToString();
var actionName = r.Defaults["action"].ToString();
var parameters = new Dictionary<string, object>();
foreach(var groupName in regex.GetGroupNames().Skip(1)) /* parameters are the groups catched by the regex */
parameters.Add(groupName, match.Groups[groupName].Value);
return Run(controllerName, actionName, parameters);
}
return HttpNotFound();
}
private ActionResult Run(string controllerName, string actionName, Dictionary<string, object> parameters)
{
// get the controller
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(this.Request.RequestContext, controllerName) as Controller;
var ctrlContext = new ControllerContext(this.Request.RequestContext, ctrl);
var ctrlDesc = new ReflectedControllerDescriptor(ctrl.GetType());
// get the action
var actionDesc = ctrlDesc.FindAction(ctrlContext, actionName);
// Change the route data so the good default view will be called in time
foreach (var parameter in parameters)
if (!ctrlContext.RouteData.Values.ContainsKey(parameter.Key))
ctrlContext.RouteData.Values.Add(parameter.Key, parameter.Value);
ctrlContext.RouteData.Values["controller"] = controllerName;
ctrlContext.RouteData.Values["action"] = actionName;
// To call the action in the controller, the parameter dictionary needs to have a value for each parameter, even the one with a default
var actionParameters = actionDesc.GetParameters();
foreach (var actionParameter in actionParameters)
{
if (parameters.ContainsKey(actionParameter.ParameterName)) /* If we already have a value for the parameter, change it's type */
parameters[actionParameter.ParameterName] = Convert.ChangeType(parameters[actionParameter.ParameterName], actionParameter.ParameterType);
else if (actionParameter.DefaultValue != null) /* If we have no value for it but it has a default value, use it */
parameters[actionParameter.ParameterName] = actionParameter.DefaultValue;
else if (actionParameter.ParameterType.IsClass) /* If the expected parameter is a class (like a ViewModel) */
{
var obj = Activator.CreateInstance(actionParameter.ParameterType); /* Instanciate it */
foreach (var propertyInfo in actionParameter.ParameterType.GetProperties()) /* Feed the properties */
{
// Get the property alias (If you have a custom model binding, otherwise, juste use propertyInfo.Name)
var aliasName = (propertyInfo.GetCustomAttributes(typeof(BindAliasAttribute), true).FirstOrDefault() as BindAliasAttribute)?.Alias ?? propertyInfo.Name;
var matchingKey = parameters.Keys.FirstOrDefault(k => k.Equals(aliasName, StringComparison.OrdinalIgnoreCase));
if (matchingKey != null)
propertyInfo.SetValue(obj, Convert.ChangeType(parameters[matchingKey], propertyInfo.PropertyType));
}
parameters[actionParameter.ParameterName] = obj;
}
else /* Parameter missing to call the action! */
return HttpNotFound();
}
// Set culture on the thread (Because my BaseController.BeginExecuteCore won't be called)
CultureHelper.SetImplementedCulture();
// Return the other action result as the current action result
return actionDesc.Execute(ctrlContext, parameters) as ActionResult;
}
}
相信我,这是我发现这样做的唯一好方法。因此,此处成功的关键是该系统使用参数约束进行匹配。所以在这种情况下,问题就会解决,因为你的noteId参数有一个约束。
限制:
答案 1 :(得分:0)
这是一个相当多的猜测(诚然),但可能是由于解析分段的价值所带来的歧义?即我可以看到这个例子的两种解释:
解释1:
/45689-anything-here/
||||| \\\\\\\\\\\\\
nodeid title
解释2:
/45689-anything-here/
|||||||||||||| \\\\
nodeid title
......所以它会消失吗?
答案 2 :(得分:0)
这是在解析操作中的路径值时执行此操作的一种方法。
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
name: "CatchAll",
url: "{*catchall}",
defaults: new { controller = "Home", action = "CatchAll" }
);
}
}
public class HomeController : Controller
{
public ActionResult CatchAll(string catchall)
{
catchall = catchall ?? "null";
var index = catchall.IndexOf("-");
if (index >= 0)
{
var id = catchall.Substring(0, index);
var title = catchall.Substring(index+1);
return Content(string.Concat("id: ", id, " title: ", title));
}
return Content(string.Concat("No match: ", catchall));
}
}