NETCORE MVC - How to work with nested, multi-parameterized routes

James Dudley picture James Dudley · Aug 2, 2017 · Viewed 9.5k times · Source

Looking for best practices when working with nested routes in .NET Core MVC.

Let's say CampusController.cs works with a base model:

[Route("api/campus/")]
public class CampusController : Controller
{
    ...
    [HttpGet]
    [Route("{campusId}")]
    public IActionResult GetCampusInfo ([FromQuery]int campusId) { ... }
}

And BuildingController.cs works with a child model:

[Route("api/campus/{campusId}/building")]
public class BuildingController : Controller
{
    ...
    [HttpGet]
    [Route("{buildingId}")]
    public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }

    [Route("{buildingId}/")]
    public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
    ....
    (more Action Methods)
}

If buildingId maps directly to the database it could retrieved even if the provided campusId isn't the parent. To keep the URL clean when calling /api/campus/{campusId}/building/{buildingId} I'd like to validate {campusId} and return a 4xx coded IActionResult if it's invalid. There has to be a better way than including validation logic in every Action Method inside BuildingController.

  • Is there a way to cascade multiple Action methods on different controllers? So that a validation method on CampusController would be called first and in turn call a method onBuildingController?
  • Is there a way to have a controller-level verification of campusId that could short circuit and return a ActionResult if validation fails?

EDIT: When I refer to validation logic I mean API signals; not the business-logic that actually determines if campusId is/isn't valid.

Thanks in advance!

Answer

Nkosi picture Nkosi · Aug 2, 2017

If using placeholder in the route prefix you would also need to include it in the action itself

[Route("api/campus/{campusId:int}/building")]
public class BuildingController : Controller {
    //...

    [HttpGet]
    [Route("{buildingId:int}")] // Matches GET api/campus/123/building/456
    public IActionResult GetBuilding ([FromRoute]int campusId, [FromRoute]int buildingId) { 
        //... validate campus id along with building id 
    }    
}

If concerned about repeated code for validation then create a base controller for campus related request and have a shared validation method.

Another option is to have a service/repository that can be used to verify campus id and its relation to the provided building id if needed.