Una implementación de OdOaS para los proyectos de API web de ASPNET Core que proporciona el control total de qué enlaces se aplicarán a los modelos que regresan desde su API. Para comunicar un estado variable al usuario final, esta biblioteca se integra completamente con la autorización y permite que las condiciones arbitrarias determinen si mostrar u ocultar los vínculos de odioas entre los recursos de la API.
Instale el paquete en nuget.org
PM > Install-Package RiskFirst.HateoasEsto incluirá el riesgo de dependencia.
Configure los enlaces para incluir para cada uno de sus 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 } ) ;
} ) ;
} ) ;
}
} Inyecte ILinksService en cualquier controlador (u otra clase de su proyecto) para agregar enlaces a un 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 .. //
}
}El código anterior produciría una respuesta como el siguiente ejemplo
{
"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 "
}
}
}o si estás 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 > Es posible especificar múltiples políticas con nombre para un modelo durante el inicio proporcionando un nombre de política a AddPolicy . Por ejemplo, puede tener la política predeterminada (sin nombre) dar enlaces básicos cuando el modelo es parte de una lista, pero información más detallada cuando un modelo se devuelve solo.
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 } ) ;
} ) ;
} ) ;
}
} Con una política con nombre, esto se puede aplicar en tiempo de ejecución utilizando una sobrecarga de AddLinksAsync que toma un nombre de política:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; También puede marcar su método de controlador con un LinksAttribute para anular la política predeterminada aplicada. El siguiente código aplicaría el perfil "FullInfo" al modelo devuelto sin tener que especificar el nombre de la política en la llamada a 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 ;
}
} Otra forma de lograr lo mismo es marcar el objeto real con 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 ;
}
} Hay más sobrecargas de AddLinksAsync que toman una instancia de ILinksPolicy o una variedad de requisitos de ILinksRequirement que se evaluará en tiempo de ejecución. Esto debería dar un control completo de qué enlaces se aplican en cualquier punto dentro de su código API.
No debería tener mucha necesidad de cambiar la forma en que se transforma el Href , sin embargo, un requisito común es generar relativo en lugar de URI absoluto. Esto se puede probar en la muestra básica
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; Las transformaciones HREF y REL se pueden controlar completamente suministrando una clase o tipo que implementa ILinkTransformation .
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Alternativamente, las transformaciones se pueden configurar utilizando una sintaxis de constructor
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 } " ) ;
} ) ;Ambas formas de transformaciones personalizadas se pueden ver en la muestra de LinkConfigurations.
Es probable que desee controlar qué enlaces se incluyen con cada modelo, y un requisito común es mostrar solo enlaces para los cuales el usuario actual está autorizado. Esta biblioteca se integra completamente en la tubería de autorización y aplicará cualquier política de autorización que haya aplicado a la acción vinculada.
Para habilitar la autorización en un enlace, proporcione la condición 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 ( ) ) ;
} ) ;
} ) ;
}
} En el ejemplo anterior, GetParentModelRoute , GetSubModelsRoute y DeleteModelRoute no se mostrarán a un usuario que no tiene acceso a esas rutas según lo definido por sus políticas de autorización. Consulte la documentación de Microsoft para obtener más información sobre la autrización dentro de un proyecto ASPNET Core WebAPI.
Al igual que con los ejemplos anteriores, existen métodos de condición adicionales que le permiten especificar un nombre de política, una política absoluta o un conjunto de requisitos.
También puede mostrar condicionalmente un enlace basado en cualquier lógica booleana utilizando la condición Assert . Por ejemplo, hay un método que le permite agregar enlaces de paginación comunes a los resultados de los objetos. Puede decidir que no valen la pena si hay un total de una sola página de resultados.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Usted es libre de agregar sus propios requisitos utilizando el método Generic Requires en LinksPolicyBuilder . Además, debe escribir una implementación de ILinksHandler para manejar sus requisitos. Por ejemplo, puede tener un requisito de ciertas respuestas para proporcionar un enlace a su documento root de API. Defina un requisito simple para este enlace.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Dado este requisito, necesitamos una clase para manejarlo, que debe implementar ILinkHandler y manejar sus 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 su controlador con IServicesCollection y use el requisito dentro de su Política de enlace
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 ejemplo se demuestra en la CustomRequirementSample
Hay muchas partes adicionales del marco que se pueden extender escribiendo su propia implementación de la interfaz apropiada y registrándola con IServicesCollection para la inyección de dependencia. Por ejemplo, puede cambiar la forma en que se evalúan y aplican los enlaces a su contenedor de enlace implementando su propio ILinksEvaluator
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}La lista de interfaces que tienen una implementación predeterminada, pero que se puede reemplazar es:
ILinkAuthorizationService , controla cómo se autorizan los enlaces durante la evaluación de la condición del enlace.ILinksEvaluator , controla cómo se evalúan y transforman los enlaces antes de ser escritos en el modelo devuelto.ILinksHandlerContextFactory , controla cómo se crea el contexto que se pasa a través de los controladores de requisitos durante el procesamiento.ILinksPolicyProvider , proporciona una búsqueda de instancias ILinkPolicy por tipo de recurso y nombre.ILinksService , el punto de entrada principal en el marco, esta interfaz se inyecta en el código de usuario para aplicar enlaces a los recursos de API.ILinkTransformationContextFactory , controla cómo se crea el contexto de transformación durante la transformación para las proporciones rel & href de enlaces.IRouteMap , controla cómo se indexa su API para permitir enlaces entre rutas. El cambio de la versión 1.0.x a 1.1.x fue en su mayoría sin roto, sin embargo, si ha implementado algún controlador de requisitos personalizados como se describe en el ejemplo anterior de la firma de la clase base, LinksHandler cambió ligeramente para eliminar la declaración duplicada del TResource de tipo genérico.
En v1.0.x, su código puede haber parecido:
public class MyCustomHandler : ILinksHandler { .. . } Ahora debería heredar de LinksHandler<TRequirement> haciendo que la implementación sea más simple, y dar una anulación de tipo a prueba de HandleRequirementAsync que brinde acceso a su requisito correctamente tipado.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >