Eine Implementierung von Hateoas für ASPNET -Kern -Web -API -Projekte, mit denen die vollständige Kontrolle über die von Ihrer API zurückgegebenen Modelle gelten. Um dem Endbenutzer einen unterschiedlichen Zustand zu vermitteln, integriert sich diese Bibliothek vollständig in die Autorisierung und ermöglicht willkürliche Bedingungen, zu bestimmen, ob Hateoas-Verbindungen zwischen API-Ressourcen angezeigt oder verbergt werden sollen.
Installieren Sie das Paket von nuget.org
PM > Install-Package RiskFirst.HateoasDies umfasst die Abhängigkeitsrisikofirst.hateoas.models, die in Version 3.0.0 eingeführt wurde, um die ASPNETCORE -Abhängigkeiten aus Baugruppen zu entfernen, die auf die LinkContainer -Basisklassen verweisen.
Konfigurieren Sie die Links so für jedes Ihrer Modelle.
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 } ) ;
} ) ;
} ) ;
}
} Injizieren Sie ILinksService in einen Controller (oder eine andere Klasse in Ihrem Projekt), um Links zu einem Modell hinzuzufügen.
[ 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 .. //
}
}Der obige Code würde als Beispiel unten eine Antwort erzeugen
{
"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 "
}
}
}oder wenn Sie XML verwenden
<? 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 > Es ist möglich, mehrere benannte Richtlinien für ein Modell während des Starts anzugeben, indem AddPolicy einen Richtliniennamen angibt. Sie können beispielsweise die Standard -Richtlinie (unbenannt) grundlegende Links geben, wenn das Modell Teil einer Liste ist, aber detailliertere Informationen, wenn ein Modell alleine zurückgegeben wird.
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 } ) ;
} ) ;
} ) ;
}
} Mit einer benannten Richtlinie kann dies zur Laufzeit unter Verwendung einer Überladung von AddLinksAsync angewendet werden, die einen Richtliniennamen aufnehmen:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; Sie können Ihre Controller -Methode auch mit einem LinksAttribute markieren, um die angewendete Standardrichtlinie zu überschreiben. Der folgende Code würde das Profil "fullInfo" auf das zurückgegebene Modell anwenden, ohne den Richtliniennamen im Anruf bei AddLinksAsync angeben zu müssen.
[ 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 ;
}
} Eine andere Möglichkeit, dasselbe zu erreichen, besteht darin, das tatsächliche Objekt mit der LinksAttribute zu markieren:
[ 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 ;
}
} Es gibt weitere Überladungen von AddLinksAsync , die eine Instanz von ILinksPolicy oder eine Reihe von ILinksRequirement benötigen, die zur Laufzeit bewertet werden. Dies sollte die vollständige Kontrolle darüber geben, welche Links an jedem Punkt in Ihrem API -Code angewendet werden.
Es sollte nicht viel ändern, wie sich der Href transformiert, aber eine häufige Anforderung ist, relativ anstelle von absoluten URIs auszugeben. Dies kann in der Grundprobe ausprobiert werden
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; Sowohl HREF- als auch REL -Transformationen können vollständig gesteuert werden, indem eine Klasse oder eine Art oder einen Typ liefert, der ILinkTransformation implementiert.
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Alternativ können Transformationen mithilfe einer Buildersyntax konfiguriert werden
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 } " ) ;
} ) ;Beide Möglichkeiten zur Anpassung von Transformationen sind im LinkConfigurationsample zu sehen.
Es ist wahrscheinlich, dass Sie steuern möchten, welche Links in jedes Modell enthalten sind, und eine häufige Anforderung besteht darin, nur Links anzuzeigen, für die der aktuelle Benutzer autorisiert ist. Diese Bibliothek integriert sich vollständig in die Autorisierungspipeline und wendet alle Autorisierungsrichtlinien an, die Sie auf die verknüpfte Aktion angewendet haben.
Um die Autorisierung auf einem Link zu ermöglichen, geben Sie die AuthorizeRoute -Bedingung an.
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 ( ) ) ;
} ) ;
} ) ;
}
} Im obigen Beispiel wird GetParentModelRoute , GetSubModelsRoute & DeleteModelRoute einem Benutzer nicht angezeigt, der keinen Zugriff auf diese Routen hat, wie sie durch ihre Autorisierungsrichtlinien definiert sind. In der Microsoft -Dokumentation finden Sie weitere Informationen zur Authrisierung in einem ASPNET -Kern -Webapi -Projekt.
Wie bei den oben genannten Beispielen gibt es weitere Bedingungsmethoden, mit denen Sie einen Richtliniennamen, eine absolute Richtlinie oder eine Reihe von Anforderungen spezifizieren können.
Sie können auch einen Link basierend auf einer beliebigen Booleschen Logik unter Verwendung der Assert -Bedingung auferlegen. Beispielsweise gibt es eine Methode, mit der Sie den ausgelasteten Ergebnissen von Objekten gemeinsame Paging -Links hinzufügen können. Sie können entscheiden, dass sich diese nicht lohnt, wenn insgesamt nur eine Seite mit Ergebnissen vorhanden ist.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Sie können Ihre eigenen Anforderungen mit der Generika hinzufügen, die eine Methode auf LinksPolicyBuilder Requires . Darüber hinaus müssen Sie eine Implementierung von ILinksHandler schreiben, um Ihre Anforderungen zu erfüllen. Beispielsweise haben Sie möglicherweise eine Anforderung an bestimmte Antworten, um einen Link zu Ihrem API -Root -Dokument zurückzugeben. Definieren Sie eine einfache Anforderung für diesen Link.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Angesichts dieser Anforderung benötigen wir eine Klasse, um sie zu handhaben, die ILinkHandler implementieren und Ihre Anforderungen erledigen muss.
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 ;
}
} Registrieren Sie Ihren Handler schließlich bei IServicesCollection und verwenden Sie die Anforderung in Ihrer Link -Richtlinie
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 > ( ) ;
}
} Dieses Beispiel wird im CustomRequirementSample demonstriert
Es gibt viele zusätzliche Teile des Frameworks, die durch das Schreiben Ihrer eigenen Implementierung der geeigneten Schnittstelle erweitert werden können und sie mit IServicesCollection für die Abhängigkeitsinjektion registrieren. Sie können beispielsweise die Art und Weise ändern, wie Links bewertet und auf Ihren Link -Container angewendet werden, indem Sie Ihren eigenen ILinksEvaluator implementieren
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}Die Liste der Schnittstellen, die eine Standardimplementierung haben, aber ersetzt werden können, lautet:
ILinkAuthorizationService , steuert, wie Links während der Bewertung des Verbindungszustands autorisiert werden.ILinksEvaluator steuert, wie Links bewertet und transformiert werden, bevor sie in das zurückgegebene Modell geschrieben werden.ILinksHandlerContextFactory steuert, wie der Kontext erstellt wird, der die Anforderungen während der Verarbeitung durchlaufen wird.ILinksPolicyProvider bietet nach ILinkPolicy -Instanzen nach Ressourcentyp und -namen.ILinksService , der Haupteintrag in das Framework, wird diese Schnittstelle in den Benutzercode injiziert, um Links zu API -Ressourcen anzuwenden.ILinkTransformationContextFactory steuert, wie der Transformationskontext während der Transformation für REL & HREF -Rel- und HREF -Relationsversuche erstellt wird.IRouteMap steuert, wie Ihre API indexiert ist, um Verbindungen zwischen den Routen zu ermöglichen. Die Änderung von Version 1.0.x zu 1.1.x war größtenteils nicht sprechend. Wenn Sie jedoch im Beispiel im Beispiel für benutzerdefinierte Anforderungen implementiert wurden, wie in der obigen Signatur der LinksHandler leicht geändert, um die doppelte Deklaration des Generikentyps TResource zu entfernen.
In v1.0.x hat Ihr Code möglicherweise ausgesehen wie:
public class MyCustomHandler : ILinksHandler { .. . } Es sollte nun von LinksHandler<TRequirement> erben und die Implementierung einfacher gestalten und eine Überschreiung von HandleRequirementAsync ermöglicht, die Zugriff auf Ihre korrekte Anforderung ermöglicht.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >