MVC: How to manage slahses in URL int the first route parameter

前端 未结 4 1388
忘掉有多难
忘掉有多难 2021-01-12 10:01

I need to map two variables that could contain slashes, to a controller, in my ASP MVC application. Let\'s see this with an example.

相关标签:
4条回答
  • 2021-01-12 10:04

    Looks like a custom route might cut the mustard:

    public class MyRoute: Route
    {
        public MyRoute()
            : base("{*catchall}", new MvcRouteHandler())
        {
        }
    
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            var rd = base.GetRouteData(httpContext);
            if (rd == null)
            {
                // we do not have a match for {*catchall}, although this is very
                // unlikely to ever happen :-)
                return null;
            }
    
            var segments = httpContext.Request.Url.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            if (segments.Length < 4)
            {
                // we do not have the minimum number of segments
                // in the url to have a match
                return null;
            }
    
            if (!string.Equals("items", segments[1], StringComparison.InvariantCultureIgnoreCase) &&
                !string.Equals("items", segments[2], StringComparison.InvariantCultureIgnoreCase))
            {
                // we couldn't find "items" at the expected position in the url
                return null;
            }
    
            // at this stage we know that we have a match and can start processing
    
            // Feel free to find a faster and more readable split here
            string repository = string.Join("/", segments.TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)));
            string path = string.Join("/", segments.Reverse().TakeWhile(segment => !string.Equals("items", segment, StringComparison.InvariantCultureIgnoreCase)).Reverse());
    
            rd.Values["controller"] = "items";
            rd.Values["action"] = "index";
            rd.Values["repository"] = repository;
            rd.Values["path"] = path;
            return rd;
        }
    }
    

    which could be registered before the standard routes:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.Add("myRoute", new MyRoute());
    
        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );
    }
    

    And if you intend to put arbitrary strings in the path portion of your urls I hope you are aware of the Zombie Operating Systems which might surprise you.

    0 讨论(0)
  • 2021-01-12 10:07

    If you cannot live with "old fashioned" style parameters and URL encoding, I think the only way you can achieve it is like this. Note that this is not tested but should basically work. Also I've put the controller name at the start and the Items separator is now essentially meaningless apart from acting as a delimiter.

    Controller

    Create a controller with a single, parameterless method:

    public class GetRepo : Controller
    {
        public ActionResult Index()
        {
            //TBC
            return View();
        }
    }
    

    Routing

    Ensure routing is set up to allow http://www.example.com/GetRepo/anything to route to your index method.

    Note that the GetRepo part is important as otherwise what happens if your URL is www.example.com/blah/repo/items/other/stuff and you happen to have a controller called blah?

    The Magic

    Now you deconstruct the Url manually by using Request.Url.AbsolutePath.

    var urlPath = Request.Url.AbsolutePath;
    
    //Split the path by '/', drop the first entry as it's the action method
    var parts = urlPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
        .Skip(1).ToList();
    
    //Find where "Items" separator appears:
    var posOfItems = parts.IndexOf("Items");
    
    //Everything before separator is the repo:
    var repo = string.Join("/", parts.Take(posOfItems));
    
    //Everything after separator is the path:
    var path = string.Join("/", parts.Skip(posOfItems + 1));
    
    //Now do something with repo/path variables
    
    0 讨论(0)
  • 2021-01-12 10:07

    You cannot do this mapping correctly, because of nature of this problem. Try to get a pencil and map following URL to repository and path:

    http://mysite/rep/Items/Items/Items/Items/Items/Items/Items/Items/Items
    

    There are multiple mappings:

    1) Repository = Items Path = Items/Items/Items/Items/Items/Items/Items

    2) Repository = Items/Items Path = Items/Items/Items/Items/Items/Items

    and so on....

    So either you should

    1. Pass parameters as query string
    2. Define multiple routes for each of format of repository (and add parts to full repository name in controller method)
    0 讨论(0)
  • 2021-01-12 10:21

    Finally, based in the answer of Darin Dimitrov, I implemented the following custom route, that solves my problem:

    public class RepositoryRoute : Route
    {
        public RepositoryRoute(string name, string url, object defaults)
            : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
        {
            string moduleUrl = url.Replace(
                REPOSITORY_PARAMETER, REPOSITORY_PARAMETER + MODULE_PARAMETER);
            mModuleRoute = new Route(
                moduleUrl, new RouteValueDictionary(defaults), new MvcRouteHandler());
        }
    
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            RouteData rd = mModuleRoute.GetRouteData(httpContext);
    
            if (rd == null)
                return base.GetRouteData(httpContext);
    
            if (!rd.Values.ContainsKey(MODULE))
                return rd;
    
            // set repository as repo/submodule format
            // if a submodule is present in the URL
            string repository = string.Format("{0}/{1}",
                rd.Values[REPOSITORY],
                rd.Values[MODULE]);
    
            rd.Values.Remove(MODULE);
            rd.Values[REPOSITORY] = repository;
    
            return rd;
        }
    
        Route mModuleRoute;
    
        const string REPOSITORY = "repository";
        const string MODULE = "module";
    
        const string REPOSITORY_PARAMETER = "{" + REPOSITORY + "}/"; // {repository}/
        const string MODULE_PARAMETER = "{" + MODULE + "}/"; // {module}/
    }
    

    Which is registered in the following way:

           routes.Add(new RepositoryRoute(
                            "Items",
                            "{repository}/Items/{*path}",
                            new { controller = "Items", action = "Index", path = "/" }
            ));
    

    The route uses an internal route, that defines a module parameter, and if it's found, I concat it to the repository, and remove it. So mapping repository or repository/module is transparent.

    0 讨论(0)
提交回复
热议问题