Implementasi HateOA untuk proyek API Web Inti ASPNET yang memberikan kontrol penuh tentang tautan mana yang akan diterapkan pada model yang dikembalikan dari API Anda. Untuk mengkomunikasikan berbagai keadaan kepada pengguna akhir, perpustakaan ini sepenuhnya terintegrasi dengan otorisasi, dan memungkinkan kondisi sewenang-wenang untuk menentukan apakah akan menunjukkan atau menyembunyikan hubungan kebencian antara sumber daya API.
Instal paket dari nuget.org
PM > Install-Package RiskFirst.HateoasIni akan mencakup ketergantungan RiskFirst.hateoas.models yang diperkenalkan dalam versi 3.0.0 untuk menghapus dependensi ASPNETCore dari rakitan yang merujuk pada kelas dasar LinkContainer.
Konfigurasikan tautan yang akan disertakan untuk masing -masing model Anda.
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 } ) ;
} ) ;
} ) ;
}
} Suntikkan ILinksService ke pengontrol apa pun (atau kelas lain dalam proyek Anda) untuk menambahkan tautan ke model.
[ 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 .. //
}
}Kode di atas akan menghasilkan respons sebagai contoh di bawah ini
{
"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 "
}
}
}atau jika Anda menggunakan 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 > Dimungkinkan untuk menentukan beberapa kebijakan bernama untuk model selama startup dengan memberikan nama kebijakan untuk AddPolicy . Misalnya, Anda dapat memiliki kebijakan default (tanpa nama) memberikan tautan dasar ketika model adalah bagian dari daftar, tetapi informasi yang lebih rinci ketika model dikembalikan sendiri.
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 } ) ;
} ) ;
} ) ;
}
} Dengan kebijakan bernama, ini dapat diterapkan saat runtime menggunakan kelebihan AddLinksAsync yang mengambil nama kebijakan:
await linksService . AddLinksAsync ( model , "FullInfo" ) ; Anda juga dapat menandai metode pengontrol Anda dengan LinksAttribute untuk mengganti kebijakan default yang diterapkan. Kode di bawah ini akan menerapkan profil "fullInfo" ke model yang dikembalikan tanpa harus menentukan nama kebijakan dalam panggilan untuk 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 ;
}
} Cara lain untuk mencapai hal yang sama adalah dengan menandai objek yang sebenarnya dengan 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 ;
}
} Ada kelebihan lebih lanjut dari AddLinksAsync yang mengambil contoh ILinksPolicy atau serangkaian ILinksRequirement yang akan dievaluasi saat runtime. Ini harus memberikan kendali penuh dari tautan mana yang diterapkan pada titik mana pun dalam kode API Anda.
Seharusnya tidak ada banyak kebutuhan untuk mengubah bagaimana Href diubah, namun salah satu persyaratan umum adalah untuk menghasilkan relatif alih -alih URI absolut. Ini dapat dicoba dalam sampel dasar
services . AddLinks ( config =>
{
config . UseRelativeHrefs ( ) ;
.. .
} ) ; Baik HREF dan transformasi REL dapat sepenuhnya dikendalikan dengan memasok kelas atau jenis yang mengimplementasikan ILinkTransformation .
services . AddLinks ( config =>
{
// supply a type implementing ILinkTransformation
config . UseHrefTransformation < MyHrefTransformation > ( ) ;
// or supply an instance
config . UseRelTransformation ( new MyRelTransformation ( ) ) ;
} ) ;Atau, transformasi dapat dikonfigurasi menggunakan sintaks pembangun
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 } " ) ;
} ) ;Kedua cara transformasi kustomisasi dapat dilihat di LinkConfigurations contoh.
Kemungkinan Anda ingin mengontrol tautan mana yang disertakan dengan masing -masing model, dan salah satu persyaratan umum adalah untuk hanya menampilkan tautan yang pengguna saat ini diotorisasi. Perpustakaan ini sepenuhnya terintegrasi ke dalam pipa otorisasi dan akan menerapkan kebijakan otorisasi apa pun yang telah Anda terapkan pada tindakan terkait.
Untuk memungkinkan otorisasi pada tautan memberikan kondisi 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 ( ) ) ;
} ) ;
} ) ;
}
} Dalam contoh di atas, GetParentModelRoute , GetSubModelsRoute & DeleteModelRoute tidak akan ditunjukkan kepada pengguna yang tidak memiliki akses ke rute tersebut sebagaimana didefinisikan oleh kebijakan otorisasi mereka. Lihat dokumentasi Microsoft untuk informasi lebih lanjut tentang authrization dalam proyek WebAPI Core ASPNET.
Seperti halnya contoh -contoh di atas, ada metode kondisi lebih lanjut yang memungkinkan Anda untuk menentukan nama kebijakan, kebijakan absolut atau serangkaian persyaratan.
Anda juga dapat secara kondisional menampilkan tautan berdasarkan logika Boolean apa pun dengan menggunakan kondisi Assert . Misalnya, ada metode yang memungkinkan Anda menambahkan tautan paging umum ke hasil objek paging. Anda dapat memutuskan ini tidak bermanfaat jika ada total satu halaman hasil.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; Anda bebas untuk menambahkan persyaratan Anda sendiri menggunakan metode generik Requires pada LinksPolicyBuilder . Selain itu, Anda harus menulis implementasi ILinksHandler untuk menangani kebutuhan Anda. Misalnya, Anda mungkin memiliki persyaratan tentang tanggapan tertentu untuk memberikan tautan kembali ke dokumen root API Anda. Tentukan persyaratan sederhana untuk tautan ini.
using RiskFirst . Hateoas ;
public class ApiRootLinkRequirement : ILinksRequirement
{
public ApiRootLinkRequirement ( )
{
}
public string Id { get ; set ; } = "root" ;
} Mengingat persyaratan ini, kami membutuhkan kelas untuk menanganinya, yang harus mengimplementasikan ILinkHandler dan menangani kebutuhan Anda.
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 ;
}
} Akhirnya daftarkan pawang Anda dengan IServicesCollection dan gunakan persyaratan dalam kebijakan tautan Anda
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 > ( ) ;
}
} Contoh ini ditunjukkan dalam CustomRequirementSample
Ada banyak bagian tambahan dari kerangka kerja yang dapat diperpanjang dengan menulis implementasi Anda sendiri dari antarmuka yang sesuai dan mendaftarkannya dengan IServicesCollection untuk injeksi ketergantungan. Misalnya, Anda dapat mengubah cara tautan dievaluasi dan diterapkan pada wadah tautan Anda dengan menerapkan ILinksEvaluator Anda sendiri
using RiskFirst . Hateoas ;
public class Startup
{
public void ConfigureServices ( IServicesCollection services )
{
services . AddLinks ( options => {
.. .
} ) ;
services . AddTransient < ILinksEvaluator , MyLinksEvaluator > ( ) ;
}
}Daftar antarmuka yang memiliki implementasi default, tetapi yang dapat diganti adalah:
ILinkAuthorizationService , mengontrol bagaimana tautan diizinkan selama evaluasi kondisi tautan.ILinksEvaluator , mengontrol bagaimana tautan dievaluasi dan diubah sebelum ditulis ke model yang dikembalikan.ILinksHandlerContextFactory , mengontrol bagaimana konteksnya dibuat yang dilewatkan melalui penangan persyaratan selama pemrosesan.ILinksPolicyProvider , memberikan pencarian untuk instance ILinkPolicy berdasarkan jenis sumber daya dan nama.ILinksService , titik masuk utama ke dalam kerangka kerja, antarmuka ini disuntikkan ke kode pengguna untuk menerapkan tautan ke sumber daya API.ILinkTransformationContextFactory , mengontrol bagaimana konteks transformasi dibuat selama transformasi untuk rel & href tautan.IRouteMap , mengontrol bagaimana API Anda diindeks untuk memungkinkan tautan antar rute. Perubahan dari versi 1.0.x ke 1.1.x sebagian besar tidak melanggar, namun jika Anda telah mengimplementasikan penangan persyaratan khusus seperti yang dijelaskan dalam contoh di atas tanda tangan LinksHandler kelas dasar diubah sedikit untuk menghapus deklarasi duplikat dari tipe generik TResource .
Dalam v1.0.x kode Anda mungkin terlihat seperti:
public class MyCustomHandler : ILinksHandler { .. . } Sekarang harus mewarisi dari LinksHandler<TRequirement> Membuat implementasi lebih sederhana, dan memberikan tipe-aman yang aman dari HandleRequirementAsync yang memberikan akses ke persyaratan yang diketik dengan benar.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >