Я работаю над новой спокойной службой веб-API asp.net и провел некоторое время с некоторыми курсами Pluralsight по этому вопросу. Один из лучших глубоко погружается в дизайн и реализацию гипермедиа (HATEOAS).
Я следил за реализацией в видео, так как она была очень простой, и, будучи новичком в mvc/web api, было действительно полезно увидеть, как она работает от начала до конца.
Однако, как только я начал немного углубляться в свою реализацию, использование UrlHelper() для вычисления ссылки для возврата начало разваливаться.
В приведенном ниже коде у меня есть простой Get(), который возвращает набор определенных ресурсов, а затем Get(int id), который позволяет вернуть отдельный ресурс.
Все результаты проходят через ModelFactory, который преобразует мои POCO для возврата результатов и обратно при публикации, исправлении и размещении.
Я пытался сделать это более изощренным способом, позволив ModelFactory обрабатывать всю логику создания ссылок, поскольку она создается с использованием объекта Request.
Теперь я знаю, что мог бы решить все это, просто обрабатывая генерацию/включение ссылок прямо внутри моих методов, и, возможно, это ответ, но мне было любопытно, как другие справляются с этим.
Моя цель:
1) В наборы результатов (т. е. коллекции результатов, возвращаемых функцией «Get()»), чтобы включить общее количество элементов, общее количество страниц, следующую и предыдущую страницы по мере необходимости. Я реализовал собственный конвертер json, чтобы сбрасывать пустые ссылки на землю. Например, я не печатаю «prevPage», когда вы находитесь на первой странице. Это работает сегодня.
2) В отдельные результаты (т. е. результат, возвращаемый «Get (id)»), чтобы включить ссылки на себя, включить rel, метод, который представляет ссылка, и независимо от того, является ли он шаблонным. Это работает сегодня.
Что сломано:
Как вы увидите в выводе ниже, две вещи «неправильные». Когда вы смотрите на ссылку «POST» для нового отдельного элемента, URL-адрес правильный. Это потому, что я удаляю последнюю часть URI (отбрасывая идентификатор ресурса). Однако при возврате набора результатов URI для «POST» теперь неверен. Это связано с тем, что маршрут не включал идентификатор отдельного ресурса, поскольку был вызван «Get ()», а не «Get (id)».
Опять же, реализация может быть изменена, чтобы создавать разные ссылки в зависимости от того, какой метод был выбран, вытягивая их из фабрики в контроллер, но мне хотелось бы верить, что я просто упускаю что-то очевидное.
Любые указатели для этого новичка на маршрутизацию и веб-API?
Получить контроллер()
[HttpGet]
public IHttpActionResult Get(int pageSize = 50, int page = 0)
{
if (pageSize == 0)
{
pageSize = 50;
}
var links = new List<LinkModel>();
var baseQuery = _deliverableService.Query().Select();
var totalCount = baseQuery.Count();
var totalPages = Math.Ceiling((double) totalCount / pageSize);
var helper = new UrlHelper(Request);
if (page > 0)
{
links.Add(TheModelFactory.CreateLink(helper.Link("Deliverables",
new
{
pageSize,
page = page - 1
}),
"prevPage"));
}
if (page < totalPages - 1)
{
links.Add(TheModelFactory.CreateLink(helper.Link("Deliverables",
new
{
pageSize,
page = page + 1
}),
"nextPage"));
}
var results = baseQuery
.Skip(page * pageSize)
.Take(pageSize)
.Select(p => TheModelFactory.Create(p))
.ToList();
return Ok(new DeliverableResultSet
{
TotalCount = totalCount,
TotalPages = totalPages,
Links = links,
Results = results
}
);
}
Контроллер Get(id)
[HttpGet]
public IHttpActionResult GetById(int id)
{
var entity = _deliverableService.Find(id);
if (entity == null)
{
return NotFound();
}
return Ok(TheModelFactory.Create(entity));
}
Создание фабрики моделей()
public DeliverableModel Create(Deliverable deliverable)
{
return new DeliverableModel
{
Links = new List<LinkModel>
{
CreateLink(_urlHelper.Link("deliverables",
new
{
id = deliverable.Id
}),
"self"),
CreateLink(_urlHelper.Link("deliverables",
new
{
id = deliverable.Id
}),
"update", "PUT"),
CreateLink(_urlHelper.Link("deliverables",
new
{
id = deliverable.Id
}),
"delete", "DELETE"),
CreateLink(GetParentUri() , "new", "POST")
},
Description = deliverable.Description,
Name = deliverable.Name,
Id = deliverable.Id
};
}
ModelFactory CreateLink()
public LinkModel CreateLink(string href, string rel, string method = "GET", bool isTemplated = false)
{
return new LinkModel
{
Href = href,
Rel = rel,
Method = method,
IsTemplated = isTemplated
};
}
Результат Get()
{
totalCount: 10,
totalPages: 4,
links: [{
href: "https://localhost/Test.API/api/deliverables?pageSize=2&page=1",
rel: "nextPage"
}],
results: [{
links: [{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "self"
},
{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "update",
method: "PUT"
},
{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "delete",
method: "DELETE"
},
{
href: "https://localhost/Test.API/api/",
rel: "new",
method: "POST"
}],
name: "Deliverable1",
description: "",
id: 2
},
{
links: [{
href: "https://localhost/Test.API/api/deliverables/3",
rel: "self"
},
{
href: "https://localhost/Test.API/api/deliverables/3",
rel: "update",
method: "PUT"
},
{
href: "https://localhost/Test.API/api/deliverables/3",
rel: "delete",
method: "DELETE"
},
{
href: "https://localhost/Test.API/api/",
rel: "new",
method: "POST"
}],
name: "Deliverable2",
description: "",
id: 3
}]
}
Результат Get(id)
{
links: [{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "self"
},
{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "update",
method: "PUT"
},
{
href: "https://localhost/Test.API/api/deliverables/2",
rel: "delete",
method: "DELETE"
},
{
href: "https://localhost/Test.API/api/deliverables/",
rel: "new",
method: "POST"
}],
name: "Deliverable2",
description: "",
id: 2
}
Обновление 1
В пятницу я нашел и начал реализовывать решение, описанное здесь: http://benfoster.io/blog/generating-hypermedia-links-in-aspnet-web-api. Решение Бена очень хорошо продумано и позволяет мне поддерживать мои модели (хранящиеся в общедоступной библиотеке для использования в других решениях .NET (например, RestSharp)) и позволяет мне использовать AutoMapper вместо реализации моей собственной ModelFactory. AutoMapper потерпел неудачу, когда мне нужно было работать с контекстными данными (такими как запрос). Поскольку моя реализация HATEOAS была перенесена в MessageHandler, AutoMapper снова становится приемлемым вариантом.