6

As pointed out here, having a double slash in a URL is valid.

I have an ASP Net Core project that uses attribute routing, a controller named GroupController for handling operations on Groups and an action for PUTting RuleParts of a group, specified by its ImportId of type string.

[Route("/api/[controller]")]
public class GroupController : ControllerBase
{
    [HttpPut("{groupImportId?}/ruleParts")]
    public async Task<IActionResult> PutRuleParts(string groupImportId, [FromBody]List<RulePartDto> ruleParts)
    {
        return null; //actual code ommitted for brevity
    }
}

A URL like http://localhost/api/group/groupImportId/ruleParts matches as expected.

I would expect that null groupImportIds, i.e. URLs like http://localhost/api/group//ruleParts would call that same action, since the groupImportId route parameter has been marked as optional. However, when trying to call this URL, I get a 404 error and the action is not hit.

Is there a possibility to match an empty URL path segment in ASP Net Core?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Thaoden
  • 3,460
  • 3
  • 33
  • 45
  • Do not use the double slash. add another route so that `http://localhost/api/group/ruleParts` will also be valid. – Nkosi May 09 '18 at 08:45
  • @Nkosi I don't get to choose what a web-client is going to call, hence I can't control if he's using a double slash. Please don't mark the question as dup when it's not actually a dup. – Thaoden May 09 '18 at 08:47
  • Also, optional parameters are suppose to be used at the end of a URL. wont work when embedded in the middle. What you expected and what the framework handles are different. – Nkosi May 09 '18 at 08:49
  • Then please remove the dup-flag, reopen, write this as an answer along with some link for me to get more information about the expected placement of optional params in AspNet Core WebApi and I even get to mark an answer as accepted. – Thaoden May 09 '18 at 08:52
  • Curious though. Why would an import have an optional Id? what is the case for that scenario? – Nkosi May 09 '18 at 09:14
  • Maybe I'm overengineering here, but I wanted to handle the case sketched out above: An error on the client side resulting in a call with an empty groupImportId (ie, double forward slash). – Thaoden May 09 '18 at 09:22
  • Ok. makes sense, but in that case getting a 404 would be the correct response. So I would say you do not even need the second route. Remove the optional parameter and thus any invalid calls will get the appropriate response. – Nkosi May 09 '18 at 09:25
  • I'm currently returning a 404 with some extra information. But for that extra information, I need an action to manually populate the response. – Thaoden May 09 '18 at 09:27
  • Btw, would returning a 404 or a 400 (Bad request) be the "correct" (as far as correct goes with Status Codes) code to return here? After all, it's a malformed request. – Thaoden May 09 '18 at 09:35
  • 404. If you were able to catch the request and assert that the id was null then you would have been able to say 400. – Nkosi May 09 '18 at 09:39
  • Sounds right. Thanks. – Thaoden May 09 '18 at 09:43

1 Answers1

5

Do not use the optional parameter in the middle of the route template.

Optional parameters are supposed to be used at the end of a URL. Routes won't match when optional parameters embedded in the middle segments are omitted as this will result in not found errors.

Instead attribute routing allows for multiple routes to be mapped to the same action.

[HttpPut("ruleParts")] // PUT api/group/ruleParts
[HttpPut("{groupImportId}/ruleParts")] //PUT api/group/123456/ruleParts
public async Task<IActionResult> PutRuleParts([FromBody]List<RulePartDto> ruleParts, string groupImportId = null) {
    //...
}

The groupImportId argument of the action is made optional to allow the optional parameter to be omitted in the URL when the other route is requested.

Reference Routing to Controller Actions in ASP.NET Core

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 2
    So basically, the Asp Net Core framework is not able to handle all valid URLs? Thanks for the input, I'll have to work on my API then. – Thaoden May 09 '18 at 09:25
  • @Thaoden testing a theory here. put the parameter back as optional like how you originally had it `"{groupImportId?}/ruleParts"`, but use the action definition I provided in my answer `string groupImportId = null`. Lets see what happens now when you cll `/api/group//ruleParts` – Nkosi May 09 '18 at 09:31
  • 1
    Would have been nice to see that behaviour documented somewhere though. – Thaoden May 09 '18 at 09:42