【问题标题】:ASP.NET MVC Routing - Dashes support in UrlASP.NET MVC 路由 - Url 中的破折号支持
【发布时间】:2014-06-26 07:24:20
【问题描述】:

所以,我在 MVC 应用程序中配置了一个路由,如下所示:

Routing.xml

<route name="note" url="{noteId}-{title}">
     <constraints>
          <segment name="noteId" value="\d+" />
     </constraints>
</route>

不要注意XML格式。

所以,我在段之间有一个小破折号“-”,似乎 MVC 路由处理程序对此有一些问题。

如果我转到 /45689-anything-here/ 之类的网址,则找不到路线。

但是,如果我在路由配置中将该破折号更改为下划线“_”:

{noteId}_{title}

路线已正确映射,我可以前往/45689_anything-here/

看来问题出在破折号上。不幸的是,我需要在 url 中有这个破折号,你们知道如何解决这个问题吗?

提前致谢!

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-3 oop asp.net-mvc-routing


    【解决方案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 参数有一个约束。

    限制:

    • 如果存在多个无法通过正则表达式定义的模棱两可的参数

    【讨论】:

      【解决方案2】:

      这是一个相当多的猜测(诚然),但这可能是由于在解析段时值所呈现的模糊性吗? IE。我可以看到对这个例子的两种解释:

      解读一:

      /45689-anything-here/
       ||||| \\\\\\\\\\\\\
      nodeid    title
      

      解读2:

      /45689-anything-here/
       |||||||||||||| \\\\
       nodeid          title
      

      ...所以它就坏掉了?

      【讨论】:

      • 这是一个很好的猜测,但请注意“noteId”有一个约束“\d+”,这意味着它只能由数字组成。第二种解释是不正确的。我很确定这个问题与歧义有关
      • @MatiCicero 问题是,我相信使用约束是为了接受/拒绝路由(或者更确切地说是路由模板)。我不认为 MVC 使用约束来拒绝解释并为同一路线尝试不同的解释。也许这就是 MVC 的救命稻草?您是否尝试过使用RouteDebugger 来查看它在哪里拒绝它?
      • 我没试过。我要试一试,然后带着消息回来。谢谢!
      • 看来我的路由正在使用 123456-mati,但使用 123456-mati-cicero 失败。我觉得你对 MVC 使用约束是正确的
      • @MatiCicero 哦,如果这不起作用,那么解决方法可能是使用 URLRewrite 模块将 dddd-abc-def 更改为 dddd/abc-def 然后模板很容易(除非您使用路由链接一代,然后另一种痛苦又回来了)。
      【解决方案3】:

      这是在操作中解析路由值的一种方法。

      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));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-14
        • 2011-02-21
        • 2020-11-18
        • 2015-11-23
        相关资源
        最近更新 更多