Реализация HATEOAS для ASPNET CORE API Projects, которая дает полное контроль того, какие ссылки применяются к моделям, возвращаемым из вашего API. Чтобы сообщить о различном состоянии с конечным пользователем, эта библиотека полностью интегрируется с авторизацией и позволяет произвольным условиям определять, показывать ли или скрывать ссылки на HatoAS между ресурсами API.
Установите пакет с nuget.org
PM > Install-Package RiskFirst.HateoasЭто будет включать в себя зависимость rischfirst.hateoas.models, введенные в версии 3.0.0 для удаления зависимостей AspnetCore из сборки, ссылающихся на базовые классы LinkContainer.
Настройте ссылки, чтобы включить для каждой из ваших моделей.
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( config =>
{
config . AddPolicy < MyModel > ( policy => {
policy . RequireSelfLink ( )
. RequireRoutedLink ( "all" , "GetAllModelsRoute" )
. RequireRoutedLink ( "delete" , "DeleteModelRoute" , x => new { id = x . Id } ) ;
} ) ;
} ) ;
}
} Введите ILinksService в любой контроллер (или другой класс в вашем проекте), чтобы добавить ссылки на модель.
[ Route ( "api/[controller]" ) ]
public class MyController : Controller
{
private readonly ILinksService linksService ;
public MyController ( ILinksService linksService )
{
this . linksService = linksService ;
}
[ HttpGet ( "{id}" , Name = "GetModelRoute" ) ]
public async Task < MyModel > GetMyModel ( int id )
{
var model = await myRepository . GetMyModel ( id ) ;
await linksService . AddLinksAsync ( model ) ;
return model ;
}
[ HttpGet ( Name = "GetAllModelsRoute" ) ]
public async Task < IEnumerable < MyModel > > GetAllModels ( )
{
//... snip .. //
}
[ HttpDelete ( "{id}" , Name = "DeleteModelRoute" ) ]
public async Task < MyModel > DeleteMyModel ( int id )
{
//... snip .. //
}
}Приведенный выше код даст ответ в качестве примера ниже
{
"id" : 1 ,
"someOtherField" : " foo " ,
"_links" : {
"self" : {
"rel" : " MyController \ GetModelRoute " ,
"href" : " https://api.example.com/my/1 " ,
"method" : " GET "
},
"all" : {
"rel" : " MyController \ GetAllModelsRoute " ,
"href" : " https://api.example.com/my " ,
"method" : " GET "
},
"delete" : {
"rel" : " MyController \ DeleteModelRoute " ,
"href" : " https://api.example.com/my/1 " ,
"method" : " DELETE "
}
}
}или если вы используете XML
<? xml version = " 1.0 " ?>
< MyModel xmlns : xsi = " http://www.w3.org/2001/XMLSchema-instance " xmlns : xsd = " http://www.w3.org/2001/XMLSchema " >
< link href = " https://api.example.com/my/1 " method = " GET " rel = " self " />
< link href = " https://api.example.com/my " method = " GET " rel = " all " />
< link href = " https://api.example.com/my/1 " method = " DELETE " rel = " delete " />
< Id >1</ Id >
< SomeOtherField >foo</ SomeOtherField >
</ MyModel > Можно указать несколько именованных политик для модели во время стартапа, предоставив имя политики для AddPolicy . Например, вы можете иметь политику по умолчанию (неназванные), когда модель является частью списка, но более подробная информация, когда модель возвращается в одиночку.
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( config =>
{
config . AddPolicy < MyModel > ( policy => {
policy . RequireRoutedLink ( "self" , "GetModelRoute" , x => new { id = x . Id } )
} ) ;
config . AddPolicy < MyModel > ( "FullInfo" , policy => {
policy . RequireSelfLink ( )
. RequireRoutedLink ( "all" , "GetAllModelsRoute" )
. RequireRoutedLink ( "parentModels" , "GetParentModelRoute" , x => new { parentId = x . ParentId } ) ;
. RequireRoutedLink ( "subModels" , "GetSubModelsRoute" , x => new { id = x . Id } ) ;
. RequireRoutedLink ( "delete" , "DeleteModelRoute" , x => new { id = x . Id } ) ;
} ) ;
} ) ;
}
} С помощью именованной политики это может применяться во время выполнения, используя перегрузку AddLinksAsync , которая принимает имя политики:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; Вы также можете разметить свой метод контроллера с помощью LinksAttribute для переопределения примененной политики по умолчанию. Приведенный ниже код будет применять профиль «fullinfo» к возвращенной модели без необходимости указать имя политики в вызове для AddLinksAsync .
[ Route ( "api/[controller]" ) ]
public class MyController : Controller
{
private readonly ILinksService linksService ;
public MyController ( ILinksService linksService )
{
this . linksService = linksService ;
}
[ HttpGet ( "{id}" , Name = "GetModelRoute" ) ]
[ Links ( Policy = "FullInfo" ) ]
public async Task < MyModel > GetMyModel ( int id )
{
var model = await myRepository . GetMyModel ( id ) ;
await linksService . AddLinksAsync ( model ) ;
return model ;
}
} Другой способ достичь того же самого - отметить фактический объект с помощью LinksAttribute :
[ Links ( Policy = "FullInfo" ) ]
public class MyModel : LinkContainer
{ }
[ Route ( "api/[controller]" ) ]
public class MyController : Controller
{
private readonly ILinksService linksService ;
public MyController ( ILinksService linksService )
{
this . linksService = linksService ;
}
[ HttpGet ( "{id}" , Name = "GetModelRoute" ) ]
public async Task < MyModel > GetMyModel ( int id )
{
MyModel model = await myRepository . GetMyModel ( id ) ;
await linksService . AddLinksAsync ( model ) ;
return model ;
}
} Существуют дальнейшие перегрузки AddLinksAsync , которые принимают экземпляр ILinksPolicy или множество ILinksRequirement , которое будет оцениваться во время выполнения. Это должно дать полный контроль того, какие ссылки применяются в любой момент в вашем коде API.
Не должно быть много необходимости изменить то, как трансформируется Href , однако одно распространенным требованием является вывод относительно, а не абсолютного URI. Это можно попробовать в основном образце
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; И преобразования HREF, и REL можно полностью управлять путем предоставления класса или типа, который реализует ILinkTransformation .
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Альтернативно, преобразования можно настроить с помощью синтаксиса строителя
services . AddLinks ( config =>
{
// output a uri for the rel values
config . ConfigureRelTransformation ( transform => transform . AddProtocol ( )
. AddHost ( )
. AddVirtualPath ( ctx => $ "/rel/ { ctx . LinkSpec . ControllerName } / { ctx . LinkSpec . RouteName } " ) ;
} ) ;Оба способа настройки преобразований можно увидеть в примере LinkConfigurations.
Вполне вероятно, что вы хотите контролировать, какие ссылки включены в каждую модель, и одно распространенное требование - показывать только ссылки, для которых авторизован текущего пользователя. Эта библиотека полностью интегрируется в конвейер авторизации и будет применять любую политику авторизации, которую вы применили к связанным действиям.
Чтобы включить авторизацию по ссылке, предоставьте условие AuthorizeRoute .
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( config =>
{
config . AddPolicy < MyModel > ( "FullInfo" , policy => {
policy . RequireSelfLink ( )
. RequireRoutedLink ( "all" , "GetAllModelsRoute" )
. RequireRoutedLink ( "parentModels" , "GetParentModelRoute" ,
x => new { parentId = x . ParentId } , condition => condition . AuthorizeRoute ( ) ) ;
. RequireRoutedLink ( "subModels" , "GetSubModelsRoute" ,
x => new { id = x . Id } , condition => condition . AuthorizeRoute ( ) ) ;
. RequireRoutedLink ( "delete" , "DeleteModelRoute" ,
x => new { id = x . Id } , condition => condition . AuthorizeRoute ( ) ) ;
} ) ;
} ) ;
}
} В приведенном выше примере GetParentModelRoute , GetSubModelsRoute & DeleteModelRoute не будут показаны пользователю, который не имеет доступа к этим маршрутам, как определено их политиками авторизации. См. Документацию Microsoft для получения дополнительной информации об авторизации в рамках проекта ASPnet Core WebAPI.
Как и в приведенных выше примерах, существуют дальнейшие методы условий, которые позволяют вам определить имя политики, абсолютную политику или набор требований.
Вы также можете условно показать ссылку на основе любой логики логики, используя условие Assert . Например, существует метод, который позволяет добавлять общие ссылки на пейджингу в полученные результаты объектов. Вы можете решить, что это не стоит того, если есть только одна страница результатов.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Вы можете добавить свои собственные требования, используя Generic Requires Method на LinksPolicyBuilder . Кроме того, вы должны написать реализацию ILinksHandler для выполнения ваших требований. Например, у вас может быть требование к определенным ответам, чтобы предоставить ссылку на ваш корневой документ API. Определите простое требование для этой ссылки.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Учитывая это требование, нам нужен класс для его обработки, который должен реализовать ILinkHandler и выполнять ваши требования.
using RiskFirst . Hateoas ;
public class ApiRootLinkHandler : LinksHandler < ApiRootLinkRequirement >
{
protected override Task HandleRequirementAsync ( LinksHandlerContext context , ApiRootLinkRequirement requirement )
{
var route = context . RouteMap . GetRoute ( "ApiRoot" ) ; // Assumes your API has a named route "ApiRoot".
context . Links . Add ( new LinkSpec ( requirement . Id , route ) ) ;
context . Handled ( requirement ) ;
return Task . CompletedTask ;
}
} Наконец, зарегистрируйте свой обработчик с помощью IServicesCollection и используйте требование в рамках вашей политики ссылки
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( config =>
{
config . AddPolicy < MyModel > ( policy =>
{
policy . RequireRoutedLink ( "self" , "GetModelRoute" , x => new { id = x . Id } )
. Requires < ApiRootLinkRequirement > ( ) ;
} ) ;
} ) ;
services . AddTransient < ILinksHandler , ApiRootLinkHandler > ( ) ;
}
} Этот пример продемонстрирован в примере CustomRequirementSample
Существует множество дополнительных частей структуры, которые могут быть расширены путем написания собственной реализации соответствующего интерфейса и регистрации его с помощью IServicesCollection для инъекции зависимости. Например, вы можете изменить способ, которым ссылки оцениваются и применяются к контейнеру ссылки, внедрив свой собственный ILinksEvaluator
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}Список интерфейсов, которые имеют реализацию по умолчанию, но который можно заменить:
ILinkAuthorizationService , контролирует, как ссылки авторизованы во время оценки состояния ссылки.ILinksEvaluator , контролирует, как ссылки оцениваются и трансформируются до того, как будут записаны в возвращенную модель.ILinksHandlerContextFactory , контролирует, как создается контекст, который проходит через обработчики требований во время обработки.ILinksPolicyProvider обеспечивает поиск экземпляров ILinkPolicy по типу ресурса и имени.ILinksService , основная точка входа в структуру, этот интерфейс вводится в код пользователя, чтобы применить ссылки к ресурсам API.ILinkTransformationContextFactory , контролирует, как создается контекст преобразования во время преобразования для REL & HREF Собственных слоев ссылок.IRouteMap , контролирует, как ваш API индексируется, чтобы разрешить ссылки между маршрутами. Изменение от версии 1.0.x на 1.1.x было в основном не разрушающим, однако, если вы реализовали какие-либо пользовательские обработчики требований, как описано в примере выше подписи базового класса LinksHandler слегка изменившейся, чтобы удалить дубликату объявления общего типа TResource .
В v1.0.x ваш код, возможно, выглядел как:
public class MyCustomHandler : ILinksHandler { .. . } Теперь он должен унаследовать от LinksHandler<TRequirement> упростить реализацию и предоставлять типовую переопределение HandleRequirementAsync , предоставляя доступ к вашему требованию правильности.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >