Rory Becker
Rory Becker

Reputation: 15701

How do I combine CatchAll and EndsWith in an MVC route?

The following route will match any folder structure below BasePath:

http://BasePath/{*SomeFolders}/ 

How do I create another route which will match any zip file below the same BasePath structure?

I tried this...

http://BasePath/{*SomeFolders}/{ZipFile}

... but it errors with

A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter. Parameter name: routeUrl

How should I approach this?

*Update*

The original requirements are in fact flawed. {ZipFile} will match the final section regardless of what it contains. (File or Folder)

In reality I believe the route pattern I'm looking to match should be:

http://BasePath/{*SomeFolders}/{ZipFile}.zip

Upvotes: 6

Views: 3557

Answers (3)

Rory Becker
Rory Becker

Reputation: 15701

It seems that constraints are the answer here.

    routes.MapRoute( _
        "ViewFile", "BasePath/{*RestOfPath}", _
        New With {.controller = "File", .action = "DownloadFile"}, _
        New With {.RestOfPath = ".*\.zip"}
    )
    routes.MapRoute( _
        "ViewFolder", "BasePath/{*RestOfPath}", _
        New With {.controller = "Folder", .action = "ViewFolder"} _
    )

or for those of you who prefer C#...

    routes.MapRoute( 
        "ViewFile", "BasePath/{*RestOfPath}", 
        new {controller = "File", action = "DownloadFile"}, 
        new {RestOfPath = @".*\.zip"}
    );
    routes.MapRoute( 
        "ViewFolder", "BasePath/{*RestOfPath}", 
        new {controller = "Folder", action = "ViewFolder"} 
    );

(Erm I think that's right)

The same route is registered twice, with the first variation being given the additional constraint that the RestOfPath parameter should end with ".zip"

I am given to understand that Custom Constraints are also possible using derivatives of IRouteConstraint.

Upvotes: 6

Robert Koritnik
Robert Koritnik

Reputation: 105029

Catch all anywhere in the URL - exactly what you need

I've written such Route class that allows you to do exactly what you describe. It allows you to put catch-all segment as the first one in the route definition (or anywhere else actually). It will allow you to define your route as:

"BasePath/{*SomeFolders}/{ZipFile}"

The whole thing is described into great detail on my blog post where you will find the code to this Route class.

Additional info

Based on added info I would still rather use the first route definition that doesn't exclude file extension out of the route segment parameter but rather add constraint for the last segment to be

"[a-zA-Z0-9_]+\.zip"

So routing should still be defined as stated above in my answer, but contraint for the ZipFile should be defined as previously written. This would make my special route work out of the box as it is now.

To also make it work for other route delimiters (like dot in your example) code should be changed considerably, but if you know routing very well how it works, you can change it to work that way.

But I'd rather suggest you keep it simple and add a constraint.

Upvotes: 4

danludwig
danludwig

Reputation: 47375

As the error says, your catchall has to be the last param in your route.

To get the ZipFile part out, you will have to parse it from the SomeFolders catchall:

public ActionResult MyAction(string SomeFolders)
{
    // parse the ZipFile out from the SomeFolders argument
}

So if you have /folder1/folder2/folder3/file.zip, you can do this:

var zipFile = SomeFolders.SubString(SomeFolders.LastIndexOf("/") + 1);

Upvotes: 1

Related Questions