Uma implementação do Hateoas para projetos de API da ASPNET Core Web, que fornece controle total, cujos links aplicam aos modelos retornados da sua API. Para comunicar um estado variável ao usuário final, essa biblioteca se integra totalmente à autorização e permite que condições arbitrárias determinem se devem mostrar ou ocultar os links de hateoas entre os recursos da API.
Instale o pacote de nuget.org
PM > Install-Package RiskFirst.HateoasIsso incluirá o dependência riskfirst.hateoas.models, que foi introduzido na versão 3.0.0 para remover as dependências do ASPNETCORE de montagens que referenciam as classes base do LinkContainer.
Configure os links para incluir para cada um de seus modelos.
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 } ) ;
} ) ;
} ) ;
}
} Injete ILinksService em qualquer controlador (ou outra classe do seu projeto) para adicionar links a um modelo.
[ 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 .. //
}
}O código acima produziria uma resposta como o exemplo abaixo
{
"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 "
}
}
}ou se você estiver usando 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 > É possível especificar várias políticas nomeadas para um modelo durante a inicialização, fornecendo um nome de política para AddPolicy . Por exemplo, você pode ter a política padrão (sem nome) fornecer links básicos quando o modelo fizer parte de uma lista, mas informações mais detalhadas quando um modelo é retornado sozinho.
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 } ) ;
} ) ;
} ) ;
}
} Com uma política nomeada, isso pode ser aplicado em tempo de execução usando uma sobrecarga de AddLinksAsync , que leva um nome de política:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; Você também pode marcar o método do seu controlador com um LinksAttribute para substituir a política padrão aplicada. O código abaixo aplicaria o perfil "FullInfo" ao modelo retornado sem precisar especificar o nome da política na chamada para 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 ;
}
} Outra maneira de alcançar a mesma coisa é marcar o objeto real com o 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 ;
}
} Existem mais sobrecargas de AddLinksAsync que tomam uma instância de ILinksPolicy ou uma matriz de requisição de ILinksRequirement que será avaliada em tempo de execução. Isso deve fornecer controle completo de quais links são aplicados em qualquer ponto do seu código da API.
Não deve ter muita necessidade de alterar a forma como o Href é transformado, no entanto, um requisito comum é produzir relativos em vez de URIs absolutos. Isso pode ser tentado na amostra básica
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; As transformações HREF e REL podem ser totalmente controladas fornecendo uma classe ou tipo que implementa ILinkTransformation .
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Como alternativa, as transformações podem ser configuradas usando uma sintaxe do construtor
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 } " ) ;
} ) ;As duas maneiras de personalizar transformações podem ser vistas na amostra LinkConfigurations.
É provável que você deseje controlar quais links estão incluídos em cada modelo, e um requisito comum é mostrar apenas links para os quais o usuário atual está autorizado. Esta biblioteca se integra totalmente ao pipeline de autorização e aplicará qualquer política de autorização que você tenha aplicado à ação vinculada.
Para permitir a autorização em um link, forneça a condição 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 ( ) ) ;
} ) ;
} ) ;
}
} No exemplo acima, GetParentModelRoute , GetSubModelsRoute & DeleteModelRoute não será mostrado a um usuário que não tem acesso a essas rotas, conforme definido por suas políticas de autorização. Consulte a Documentação da Microsoft para obter mais informações sobre a autenticação em um projeto Webapi do ASPNET Core.
Como nos exemplos acima, existem métodos de condição adicionais que permitem especificar um nome de política, uma política absoluta ou um conjunto de requisitos.
Você também pode mostrar condicionalmente um link com base em qualquer lógica booleana usando a condição Assert . Por exemplo, existe um método que permite adicionar links comuns de paginação a resultados paginados de objetos. Você pode decidir que isso não vale a pena se houver um total de apenas uma página de resultados.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Você está livre para adicionar seus próprios requisitos usando o método genérico Requires no LinksPolicyBuilder . Além disso, você deve escrever uma implementação do ILinksHandler para lidar com seus requisitos. Por exemplo, você pode ter um requisito sobre certas respostas para fornecer um link para o seu documento da API Root. Defina um requisito simples para este link.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Dado esse requisito, precisamos de uma classe para lidar com isso, que deve implementar ILinkHandler e lidar com seus requisitos.
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 ;
}
} Finalmente, registre seu manipulador no IServicesCollection e use o requisito em sua política de link
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 > ( ) ;
}
} Este exemplo é demonstrado na amostra CustomRequirementSample
Existem muitas partes adicionais da estrutura que podem ser estendidas escrevendo sua própria implementação da interface apropriada e registrando -a no IServicesCollection para injeção de dependência. Por exemplo, você pode mudar a maneira como os links são avaliados e aplicados ao seu contêiner de link implementando seu próprio ILinksEvaluator
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}A lista de interfaces que têm uma implementação padrão, mas que pode ser substituída é:
ILinkAuthorizationService , controla como os links são autorizados durante a avaliação da condição de link.ILinksEvaluator , controla como os links são avaliados e transformados antes de serem gravados no modelo retornado.ILinksHandlerContextFactory , controla como o contexto é criado, que é passado pelos manipuladores de requisitos durante o processamento.ILinksPolicyProvider , fornece pesquisas para instâncias ILinkPolicy por tipo de recurso e nome.ILinksService , o principal ponto de entrada na estrutura, essa interface é injetada no código do usuário para aplicar links aos recursos da API.ILinkTransformationContextFactory , controla como o contexto de transformação é criado durante a transformação para os direitos relacionados dos links.IRouteMap , controla como sua API é indexada para permitir links entre as rotas. A alteração da versão 1.0.x para 1.1.x foi principalmente sem quebra, no entanto, se você implementou qualquer manipulador de requisitos personalizado, conforme descrito no exemplo acima da assinatura do LinksHandler da classe base mudou ligeiramente para remover a declaração duplicada do tipo genérico TResource .
Em v1.0.x, seu código pode ter parecido:
public class MyCustomHandler : ILinksHandler { .. . } Agora ele deve herdar da LinksHandler<TRequirement> tornando a implementação mais simples e fornecendo uma substituição de tipo de HandleRequirementAsync , fornecendo acesso ao seu requisito corretamente tipado.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >