Лучший способ представить глубокое дерево ресурсов? - PullRequest
0 голосов
/ 31 января 2019

Существует ли простой способ повторно использовать «сегмент» маршрута вместе с логикой контроллера, которая используется для проверки и использования параметров в сегменте?

Например, я хотел бы представить мои ресурсы, аналогичныеэто:

  • дома
    • {адрес дома}
      • архитектура
        • крыша
        • сайдинг
      • комнаты
        • жилые
          • двери
          • окна
        • столовая
          • двери
          • окна
        • кухня
          • двери
          • окна
        • спальни
          • [0]
            • двери
            • окна
          • [1]
            • двери
            • окна

Ниже приведен один из возможных способов. Какой путь лучше?

[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} также должен включать информацию об идентификаторе дома.Затем мне нужно было бы проанализировать 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;

1 Ответ

0 голосов
/ 31 января 2019

Это невозможно, как вы указываете, но все же достижимо.Во-первых, вы хотите положиться на префиксы маршрута, применяемые к самому классу контроллера.Другими словами, вместо:

[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 доступно, и может использовать его соответствующим образом, только сосредоточившись на своем собственном поведении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...