ASPNET Core Web API项目的Hateoas实现,该项目提供了完全控制哪些链接,以适用于您API返回的模型。为了将不同状态传达给最终用户,该库将与授权完全集成,并允许任意条件确定是否显示或隐藏API资源之间的Hateoas链接。
从nuget.org安装软件包
PM > Install-Package RiskFirst.Hateoas这将包括在3.0.0中介绍的依赖关系风险first.hateoas.models,以从引用linkContainer基类的汇编中删除AspnetCore依赖项。
配置每个型号的链接以包含。
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 } " ) ;
} ) ;自定义转换的两种方式都可以在linkConfigurationsample中看到。
您可能希望控制每个模型中包含哪些链接,而一个常见的要求是仅显示当前用户被授权的链接。该库完全集成到授权管道中,并将应用您应用于链接操作的任何授权策略。
在链接上启用授权提供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不会向无法访问其授权策略定义的那些路由的用户显示。有关ASPNET Core WebAPI项目中有关验证的更多信息,请参见Microsoft文档。
与上述示例一样,还有其他条件方法,可以使您指定策略名称,绝对策略或一组要求。
您还可以使用Assert条件有条件地显示基于任何布尔逻辑的链接。例如,有一种方法允许您将常见的分页链接添加到对象的分页结果中。如果总共只有一页的结果,您可能会认为这是不值得的。
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ;您可以使用LinksPolicyBuilder上的通用Requires方法来添加自己的要求。此外,您必须编写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控制着在转换过程中如何创建链接的转换上下文。IRouteMap ,控制如何索引您的API以允许路由之间的链接。从1.0.x版本到1.1.x版本的更改大多是非破坏的,但是,如果您实现了任何自定义需求处理程序,如上面的示例所述的任何自定义要求处理程序,则base类LinksHandler的签名略有更改以删除通用类型TResource的重复声明。
在v1.0.x中,您的代码可能看起来像:
public class MyCustomHandler : ILinksHandler { .. . }现在,它应该从LinksHandler<TRequirement>继承,使实现更简单,并给出类型安全的HandleRequirementAsync替代,使您可以访问正确的要求。
public class MyCustomHandler : LinksHandler < MyCustomRequirement >