ASP.NET MVC路由 - 在Url中使用Dashhes

时间:2014-05-08 18:20:41

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

所以,我在我的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 /

似乎问题是破折号。不幸的是,我需要在网址中加入破折号,你们知道如何解决这个问题吗?

提前致谢!

3 个答案:

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