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
.
CampusController
would be called first and in turn call a method onBuildingController
?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!
you could add a route like: routes. MapRoute( "ArtistImages", // Route name "{controller}/{action}/{artistName}/{apikey}", // URL with parameters new { controller = "Home", action = "Index", artistName = "", apikey = "" } // Parameter defaults );
Multiple Routes You can also configure a custom route using the MapRoute extension method. You need to provide at least two parameters in MapRoute, route name, and URL pattern. The Defaults parameter is optional. You can register multiple custom routes with different names.
UseRouting adds route matching to the middleware pipeline. This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request. UseEndpoints adds endpoint execution to the middleware pipeline. It runs the delegate associated with the selected endpoint.
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.
It sounds like you want your users to provide a campusId
when talking to the BuildingController
, and your BuildingController
to validate campusId
in a DRY kind of way.
If that's the case, you can create an input model for your BuildingController
methods:
public class BuildingIdInput { [Required] public int? CampusId { get; set; } [Required] public int? BuildingId { get; set; } }
Then you can let MVC bind user input to this model.
[Route("api/campus")] public class BuildingController : Controller { [HttpGet] [Route("{campusId}/building/{buildingId}")] public IActionResult GetBuilding (BuildingIdInput input) { if (ModelState.IsValid) {...} } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With