Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

enter image description here

  • Repository and Path will be URL-encoded parameters.
  • Repository can have 0 slashes or 1 slash as a maximum (rep or rep/module)
  • Path can have an arbitrary number of slashes.

For example these are valid URLs:

http://mysite/rep/Items
http://mysite/rep/module/Items/foo/bar/file.c

Someone could give some suggestions about how to define this route?

like image 995
Daniel Peñalba Avatar asked Jul 24 '14 11:07

Daniel Peñalba


People also ask

What is right way to define routes MapRoute () in MVC?

Routing in ASP.NET MVC cs file in App_Start Folder, You can define Routes in that file, By default route is: Home controller - Index Method. routes. MapRoute has attributes like name, url and defaults like controller name, action and id (optional).

Can we add constraints to the route in MVC?

Attribute Routing is introduced in MVC 5.0. We can also define parameter constraints by placing a constraint name after the parameter name separated by a colon. There are many builtin routing constraints available. We can also create custom routing constraints.

What are the three segments for routing important in MVC?

The three segments of a default route contain the Controller, Action and Id.


2 Answers

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.

like image 91
Darin Dimitrov Avatar answered Oct 02 '22 22:10

Darin Dimitrov


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.

like image 37
Daniel Peñalba Avatar answered Oct 02 '22 21:10

Daniel Peñalba