是否有一种简单的方法可以重用路由“段”以及用于验证和使用段中参数的控制器逻辑?
例如,我想代表我的资源与此类似:
以下是可以完成的一种方法。 什么是更好的方法?
[Route("[controller]")]
[ApiController]
class HousesController : ControllerBase
{
[HttpGet]
public IEnumerable<House> Get() => _housesRepo.GetAll();
[HttpGet("{houseId}")]
public ActionResult<House> Get(string houseId)
{
if (!_housesRepo.TryGetHouse(houseId, out var house))
return NotFound("Bad house ID");
}
}
class RoomsController : ControllerBase
{
[HttpGet("~houses/{houseId}/rooms/bedrooms/{bedroomId}")]
public ActionResult<Room> GetBedroom(string houseId, int bedroomId)
{
if (!_housesRepo.TryGetHouse(houseId, out var house))
return NotFound("Bad house ID");
var bedrooms = house.Bedrooms;
if (bedroomId < 0 || bedroomId >= bedrooms.Length)
return NotFound("Bad bedroom ID");
return bedrooms[bedroomId];
}
[HttpGet("~houses/{houseId}/rooms/{roomClass}")]
public ActionResult<Room> GetRoom(string houseId, string roomClass)
{
if (!_housesRepo.TryGetHouse(houseId, out var house))
return NotFound("Bad house ID");
var roomsByClass = house.RoomsByClass;
if (!roomsByClass.TryGetValue(roomClass, out var room))
return NotFound("Bad room class");
return room;
}
}
class ArchitectureController : ControllerBase
{
[HttpGet("~houses/{houseId}/architecture")]
public ActionResult<string[]> Get(string houseId)
{
if (!_housesRepo.TryGetHouse(houseId, out _))
return NotFound("Bad house ID");
return new [] { "roof", "siding" };
}
[HttpGet("~houses/{houseId}/architecture/roof")]
public ActionResult<Roof> GetRoof(string houseId)
{
if (!_housesRepo.TryGetHouse(houseId, out var house))
return NotFound("Bad house ID");
return house.Architecture.Roof;
}
[HttpGet("~houses/{houseId}/architecture/siding")]
public ActionResult<Siding> GetSiding(string houseId)
{
if (!_housesRepo.TryGetHouse(houseId, out var house))
return NotFound("Bad house ID");
return house.Architecture.Siding;
}
}
但是请注意有多少重复:
~houses/{houseId}
路线路段houseId
并检索关联的House
随着资源变得越来越复杂,重复只会变得更糟。
Microsoft似乎将人们推向了一个非常扁平的层次结构。例如:
~houses/{houseId}
~architecture/{architectureId}
~rooms/{roomId}
但这只会掩盖问题。例如,Room
仅作为House
的组成部分才有意义,因此{roomId}
也必须包括有关房屋ID的信息。然后,我将需要解析roomId
以提取houseId
来验证和提取关联的House
资源。
如果我能拥有这个功能,那就太好了
[Segment("HOUSE")]
[HttpGet("houses/{houseId}")]
public ActionResult<House> GetHouse(string houseId) => _housesRepo
.TryGetValue(houseId, out var house)
? new ActionResult<House>(house)
: NotFound("Bad house ID");
[HttpGet("[HOUSE]/architecture")]
public string[] GetArchitectureCategories(House house) => new [] { "roof", "siding" };
[HttpGet("[HOUSE]/architecture/roof")]
public Roof GetRoof(House house) => house.Architecture.Roof;
[HttpGet("[HOUSE]/architecture/siding")]
public Siding GetSiding(House house) => house.Architecture.Siding;
[Segment("BEDROOM")]
[HttpGet("[HOUSE]/rooms/bedrooms/{bedroomId}")]
public Room GetBedroom(House house, int bedroomId) => house.Bedrooms[bedroomId];
[Segment("ROOM")]
[HttpGet("[HOUSE]/rooms/{roomClass}")]
public ActionResult<Room> GetRoom(House house, string roomClass) => house
.RoomsByClass
.TryGetValue(roomClass, out var room)
? new ActionResult<Room>(room)
: NotFound("Bad room class");
这很容易建立。例如,我可以快速添加门窗控制器:
[HttpGet("[ROOM|BEDROOM]/doors")]
public Door[] GetDoors(Room room) => room.Doors;
[HttpGet("[ROOM|BEDROOM]/windows")]
public Window[] GetWindows(Room room) => room.Windows;
答案 0 :(得分:1)
这是不可能的,但是仍然可以实现。首先,您要依赖应用于控制器类本身的路由前缀。换句话说,代替:
[HttpGet("~houses/{houseId}/rooms/bedrooms/{bedroomId}")]
public ActionResult<Room> GetBedroom(string houseId, int bedroomId)
使用:
[Route("houses/{houseId}/rooms")]
public class RoomsController : ControllerBase
{
[HttpGet("bedrooms/{bedroomId}")]
public ActionResult<Room> GetBedroom(int bedroomId)
然后,还要注意我在那里取出了houseId
参数。可以将其逻辑移到OnActionExecutionAsync
的替代中:
private House House { get; set; }
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (context.RouteData.Values.TryGetValue("houseId", out var houseId))
{
House = await _context.Set<House>().FindAsync(houseId);
if (House == null)
context.Result = NotFound();
}
await base.OnActionExecutionAsync(context, next);
}
这看起来有点复杂,但是所有要做的就是从路线中拉出houseId
参数,并尝试找到关联的房屋。如果未找到,则立即返回404,否则,将使用该房屋设置控制器上的House
属性。结果,您现在在此控制器中的每个操作都可以依靠该House
属性可用并可以相应地使用它,而不必着重于自己的特定行为。