Multiple optional parameters routing
To solve your problem you must take into account this things:
- you can register more than one route. The first registered route that can handle an URL, will handle it.
- you can use something apart from slash
/
as separator, to make parts of a route distinguishable - you can use parameter constraints, usually regular expressions, to make it easier to discover if a parameter is of one or other kind
- you can specify default values for your parameters, and, if you do so, the action method must have default values for them (unless MVC, that only requires them to be nullable or of reference type)
As you didn't tell how your URL looks like I'll show you my own examples.
Let's suppose that you have a TestController
Web API Controller class with an action like this:
// GET api/Test/TestAction/ ...
[HttpGet]
public object TestAction(int param1, DateTime startDate, DateTime endDate,
int? param2 = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
NOTE: with the default routes a Web API controller's method named GetXxx
is available to HTTP GET, a method named PostXxx
is available to HTTP POST and so on. However, once you include Controller
and Action
in the URL template, you must use the [HttpXxx]
attributes to make your method available to the required HTTP method.
Optional parameter(s) in the middle
In this first example, I suppose that both param1
, and param2
are integer numbers, and stardDate
and endDate
are dates:
http://myhost/api/Mycontroller/Myaction/12/22/2014-12-01/2014-12-31
http://myhost/api/Mycontroller/Myaction/22/2014-12-01/2014-12-31
If you want the first URL to match parameters like these:
param1 = 12; param2 = 22; startDate = 2014-12-01; endData = 2014-12-31
and the second like these:
param1 = 12; param2 = null; startDate = 2014-12-01; endData = 2014-12-31
You need to register two routes, one that will match each possible URL structure, i.e.
// for the 1st
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}"
// for the 2nd
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}"
Note that, in this case, both routes are mutually exclusive, i.e. a single URL can match only one of the routes, so you can register them in any other.
However, you must notice that the second URL doesn't define a value for param2
, and the TestAction
method requires it. This wouldn't work: you must include a default value for this parameter, both in the controler's method and in the route registration:
- action parameter
int? param2 = null
(C# requires optional parameter to be the last ones). - the route must include the default:
defaults: new { param2 = RouteParameter.Optional }
This is the way to solve the optional parameter in the middle problem. In general, you'll need to define several routes, depending on how many optional parameters there are, and declare this parameters, with default values, in the Web API action method.
NOTE: as I wrote above, in MVC you don't need to specify a default value in the method parameter for this to work
Parameter constraints
Specifying constrains for a route parameter has two consequences:
- There's a warranty that the parameter value has the expected format
- Most importantly, the route will only handle the URL if the format is the expected one. So this helps you make your URL more selective, thus making it more flexible.
You simply need to add a constraint
parameter on the route registration, like this:
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = @"20\d\d-[0-1]?\d-[0-3]?\d", // regex
endDate = @"20\d\d-[0-1]?\d-[0-3]?\d" // regex
},
defaults: new object { }
);
Note that it's necessary to specify a defaults
parameter, even if it's empty.
NOTE: the constraints in this case are a regex that only matches dates in the year 20XX, the month expressed as a single digit, or as 0x or 1x, and the date as a single digit or 0x, 1x, 2x or 3x, separated by dashes. So this regex will match 2012-1-1
or 2015-12-30
, but not 1920-12-30
. You should adapt the regex to your needs.
Optional parameters at the end
By this time I've explained how to support optional parameters, and how to specify formats (constraints) for them, to match a route template.
The usual way to define optional parameters is to do it at the end of the URL template, and, in this case, if there are missing params in a route, they must be all at the end of the route. (Compare this with optional in the middle: they require different routes).
In this example, if you want to make optional the param2
, and the startDate
and endDate
, you must define them in the route registration, and set default parameter values in the action method.
The final code would look like this:
[HttpGet]
public object TestAction(int param1, int? param2 = null, DateTime? startDate = null,
DateTime? endDate = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
config.Routes.MapHttpRoute(
name: "Multiparam1",
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}",
constraints: new
{
startDate = @"20\d\d-[0-1]?\d-[0-3]?\d",
endDate = @"20\d\d-[0-1]?\d-[0-3]?\d"
},
defaults: new
{
param2 = RouteParameter.Optional,
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = @"(20\d\d-[0-1]?\d-[0-3]?\d)?",
endDate = @"(20\d\d-[0-1]?\d-[0-3]?\d)?"
},
defaults: new
{
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
Note, that, in this case:
- the routes could be mismatched, so they must be registered in the right order, as shown. If you registered first the
Multiparam2
route, it would erroneously handle an URL like this:http://localhost:1179/api/test/testaction/1/2014-12-12/2015-1-1
, withparam1=1; param2="2014-12-12"; startDate="2015-1-1"
. (You could avoid this with an additional constraint on param2 that only accepts numbers, likeparam2=@"\d+"
) - the action must have default values for
startDate
andendDate
.
Conclusions
You can handle default parameters in different positions by carefully:
- registering routes in the right order
- define default parameters in the route, and also default values in the controller's action
- use constraints
If you plan carefully how your routes look like, you can get what you need with a few routes and optional parameters.