0

I have a Web API method that looks like this:

[Route("datatable")]
public HttpResponseMessage getDataTable([FromUri] decimal a, [FromUri] int p, [FromUri] decimal i, [FromUri] decimal m = 0, [FromUri] decimal e = 0) { ... }

What I'm trying to implement is that any one among i, m, or e must be provided. All can be provided, two of three can be provided, or any single one can be provided, but it is invalid for none to be provided.

As it stands right now, i is required. I'm currently passing in 0 when i should be undefined, but this is messy. If I make all three parameters optional, then it is considered valid to provide none of them.

The following calls should be considered valid and should be routed to the method:

http://myhost.local/api/datatable?a=10&p=5&e=1&i=2&m=3

http://myhost.local/api/datatable?a=10&i=1&p=5

http://myhost.local/api/datatable?a=10&i=0&p=5&m=1

http://myhost.local/api/datatable?a=10&p=5&e=1

http://myhost.local/api/datatable?a=10&m=50&p=4

http://myhost.local/api/datatable?e=6&m=2&a=50&p=0

But

http://myhost.local/api/datatable?a=50&p=0

should be invalid and not routed to the method.

I use HTTP attribute routing. I would like for the routing to fail (an API generated 404 "No route matched" message) rather than having to handle all three parameters being undefined in my own code if at all possible. (For example, suppose I do decide later to implement a separate method for the case where no values are provided - I would want the routing to still work.)

Can this be done?

fdmillion
  • 4,823
  • 7
  • 45
  • 82
  • It would be a lot simpler to just handle all calls to the `/datatable` route and then parse the querystring. 1 block of code for all calls, with 1 block of logic. – Reinstate Monica Cellio Jan 24 '18 at 16:06
  • "If I make all three parameters optional, then it is considered valid to provide none of them." Not if you write some logic in your action method to check the incoming data, and then return a BadRequest response if it doesn't meet your rules. Not everything has to be decided up front by the model binder. – ADyson Jan 24 '18 at 16:09
  • @ADyson I was trying to avoid that scenario - having to manually generate an error response. For example, if you have a required parameter and it is not provided, the API routing will fail to route the call to your method and you'll get a 404 error. I could do that manually within my own code, but I was hoping to see if the routing framework itself could support something like that. That would also leave the door open for providing a completely separate method (via routing) to handle the case of a request with none of the required parameters. – fdmillion Jan 24 '18 at 16:15
  • If you make a DTO object rather than a series of individual fields then you could use data attributes on the properties of the objects to set the rules, including, if you add in an extra package like ExpressiveAnnotations, the flexibility to write validation attributes which take the value of other attributes into account. You could also consider using nullable types to help with this instead of defaulting to 0. – ADyson Jan 24 '18 at 16:17

1 Answers1

2

I'm afraid there is no that kind of conditionally-optional-parameters -concept in routing level.

Optional parameters are optional and they work just like you have noticed (and discussed also i.e here). There is no way to provide conditional logic at routing level.

I'll prefer you to change signature as following (optional i)..

[Route("datatable")]
public HttpResponseMessage getDataTable([FromUri] decimal a, [FromUri] int p, [FromUri] decimal i = 0, [FromUri] decimal m = 0, [FromUri] decimal e = 0)

..and to add custom logic inside the method on parameter validation. When you get invalid parameter combination, BadRequest-status is good way to inform caller.

Risto M
  • 2,919
  • 1
  • 14
  • 27
  • 1
    After looking into this as deeply as I could, it turns out indeed you can't do an "a or b" conditional in routing. So I ended up doing exactly what many have suggested (And what I initially thought of but tried to avoid) - logic in the controller method. Thanks – fdmillion Jan 25 '18 at 16:56