Une implémentation de Hateoas pour les projets ASPNET Core Web API qui donne un contrôle total aux liens à appliquer aux modèles renvoyés de votre API. Afin de communiquer un état variable à l'utilisateur final, cette bibliothèque s'intègre pleinement à l'autorisation et permet aux conditions arbitraires de déterminer s'il faut afficher ou masquer les liens Hateoas entre les ressources de l'API.
Installez le package à Nuget.org
PM > Install-Package RiskFirst.HateoasCela comprendra la dépendance RiskFirst.hateoas.models qui a été introduite dans la version 3.0.0 pour supprimer les dépendances ASPNetCore des assemblages faisant référence aux classes de base de LinkContainer.
Configurez les liens à inclure pour chacun de vos modèles.
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 } ) ;
} ) ;
} ) ;
}
} Injectez ILinksService dans n'importe quel contrôleur (ou autre classe de votre projet) pour ajouter des liens vers un modèle.
[ 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 .. //
}
}Le code ci-dessus produirait une réponse comme l'exemple ci-dessous
{
"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 si vous utilisez 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 > Il est possible de spécifier plusieurs politiques nommées pour un modèle pendant le démarrage en fournissant un nom de politique à AddPolicy . Par exemple, vous pouvez avoir la politique par défaut (sans nom) donner des liens de base lorsque le modèle fait partie d'une liste, mais des informations plus détaillées lorsqu'un modèle est retourné seul.
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 } ) ;
} ) ;
} ) ;
}
} Avec une stratégie nommée, cela peut être appliqué lors de l'exécution en utilisant une surcharge de AddLinksAsync qui prend un nom de politique:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; Vous pouvez également marquer votre méthode de contrôleur avec un LinksAttribute pour remplacer la stratégie par défaut appliquée. Le code ci-dessous appliquerait le profil "FulLinfo" au modèle retourné sans avoir à spécifier le nom de la politique dans l'appel à 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 ;
}
} Une autre façon de réaliser la même chose consiste à marquer l'objet réel avec le 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 ;
}
} Il y a d'autres surcharges d' AddLinksAsync qui prennent une instance d' ILinksPolicy ou un tableau d' ILinksRequirement qui sera évalué au moment de l'exécution. Cela devrait donner un contrôle complet aux liens appliqués à tout moment dans votre code API.
Il ne devrait pas avoir beaucoup besoin de modifier la façon dont le Href est transformé, mais une exigence commune est de sortir relative au lieu d'Uris absolus. Cela peut être essayé dans l'échantillon de base
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; Les transformations HREF et REL peuvent être entièrement contrôlées en fournissant une classe ou un type qui implémente ILinkTransformation .
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Alternativement, les transformations peuvent être configurées à l'aide d'une syntaxe de générateur
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 } " ) ;
} ) ;Les deux façons de personnaliser les transformations peuvent être visibles dans l'échantillon de linkconfigurations.
Il est probable que vous souhaitiez contrôler les liens inclus avec chaque modèle, et une exigence commune consiste à afficher uniquement les liens pour lesquels l'utilisateur actuel est autorisé. Cette bibliothèque s'intègre pleinement dans le pipeline d'autorisation et appliquera toute politique d'autorisation que vous avez appliquée à l'action liée.
Pour activer l'autorisation sur un lien, fournissez la condition 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 ( ) ) ;
} ) ;
} ) ;
}
} Dans l'exemple ci-dessus, GetParentModelRoute , GetSubModelsRoute & DeleteModelRoute ne sera pas montré à un utilisateur qui n'a pas accès à ces itinéraires tels que définis par leurs politiques d'autorisation. Consultez la documentation Microsoft pour plus d'informations sur l'auteur dans un projet ASPNET Core WebAPI.
Comme pour les exemples ci-dessus, il existe d'autres méthodes de condition qui vous permettent de spécifier un nom de politique, une politique absolue ou un ensemble d'exigences.
Vous pouvez également afficher conditionnellement un lien basé sur n'importe quelle logique booléenne en utilisant l'état Assert . Par exemple, il existe une méthode qui vous permet d'ajouter des liens de pagination communs aux résultats paginés des objets. Vous pouvez décider que cela ne vaut pas la peine s'il n'y a un total d'une seule page de résultats.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Vous êtes libre d'ajouter vos propres exigences en utilisant la méthode Générique Requires sur LinksPolicyBuilder . De plus, vous devez rédiger une implémentation d' ILinksHandler pour gérer vos besoins. Par exemple, vous pouvez avoir une exigence sur certaines réponses pour fournir un lien vers votre document racine API. Définissez une exigence simple pour ce lien.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Compte tenu de cette exigence, nous avons besoin d'un cours pour le gérer, qui doit implémenter ILinkHandler et gérer vos besoins.
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 ;
}
} Enfin, enregistrez votre gestionnaire auprès d' IServicesCollection et utilisez l'exigence dans votre stratégie de liaison
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 > ( ) ;
}
} Cet exemple est démontré dans l' CustomRequirementSample
Il existe de nombreuses parties supplémentaires du cadre qui peuvent être étendues en écrivant votre propre implémentation de l'interface appropriée et en l'enregistrant avec IServicesCollection pour l'injection de dépendance. Par exemple, vous pouvez modifier la façon dont les liens sont évalués et appliqués à votre conteneur de liens en implémentant votre propre ILinksEvaluator
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}La liste des interfaces qui ont une implémentation par défaut, mais qui peut être remplacée est:
ILinkAuthorizationService , contrôle comment les liens sont autorisés lors de l'évaluation des conditions de liaison.ILinksEvaluator , contrôle comment les liens sont évalués et transformés avant d'être écrits au modèle retourné.ILinksHandlerContextFactory , contrôle la façon dont le contexte est créé qui est passé par les gestionnaires d'exigences pendant le traitement.ILinksPolicyProvider , fournit une recherche pour les instances ILinkPolicy par type de ressource et nom.ILinksService , Le point d'entrée principal dans le cadre, cette interface est injectée dans le code utilisateur pour appliquer des liens aux ressources de l'API.ILinkTransformationContextFactory , contrôle la façon dont le contexte de transformation est créé lors de la transformation pour les correctifs REL & HREF des liens.IRouteMap , contrôle la façon dont votre API est indexée pour permettre des liens entre les itinéraires. Le changement de la version 1.0.x à 1.1.x était principalement non révolutionnaire, mais si vous avez mis en œuvre des gestionnaires d'exigences personnalisés, comme décrit dans l'exemple ci-dessus, la signature de la classe de base LinksHandler a légèrement changé pour supprimer la déclaration en double du type générique TResource .
Dans v1.0.x, votre code a peut-être ressembler:
public class MyCustomHandler : ILinksHandler { .. . } Il devrait désormais hériter de LinksHandler<TRequirement> simplifier la mise en œuvre et donner un remplacement de type de type HandleRequirementAsync donnant accès à votre exigence correcte.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >