تنفيذ Hatoas لمشاريع ASPNET Core Web API التي توفر التحكم الكامل في الروابط لتطبيقها على النماذج التي يتم إرجاعها من واجهة برمجة التطبيقات الخاصة بك. من أجل توصيل حالة متفاوتة إلى المستخدم النهائي ، تتكامل هذه المكتبة تمامًا مع التفويض ، وتتيح الظروف التعسفية لتحديد ما إذا كان سيتم عرض أو إخفاء روابط Hatoas بين موارد API.
قم بتثبيت الحزمة من nuget.org
PM > Install-Package RiskFirst.Hateoasوسيشمل ذلك riskfirst.hateoas.hateoas.models التي تم تقديمها في الإصدار 3.0.0 لإزالة تبعيات ASPNETCORE من التجميعات التي تشير إلى فئات قاعدة LinkContainer.
قم بتكوين الروابط لتضمين لكل طراز الخاص بك.
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 ، ولكن أحد المتطلبات الشائعة هو إخراج نسبي بدلاً من URIs المطلقة. يمكن تجربتها في العينة الأساسية
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 } " ) ;
} ) ;يمكن رؤية كلا الطريقين لتحويلات تخصيص التخصيص في insconfigurations ampl.
من المحتمل أن ترغب في التحكم في الروابط التي يتم تضمينها مع كل نموذج ، وشرط واحد شائع هو إظهار الروابط التي يتم تصريح المستخدم الحالي بها. تتكامل هذه المكتبة بالكامل في خط أنابيب التفويض وستطبق أي سياسة تفويض قمت بتطبيقها على الإجراء المرتبط.
لتمكين التفويض على رابط توفير حالة 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 على مستخدم لا يمكنه الوصول إلى هذه الطرق كما هو محدد في سياسات التفويض الخاصة بهم. راجع وثائق Microsoft لمزيد من المعلومات حول AuthRization ضمن مشروع ASPNET Core WebAPI.
كما هو الحال مع الأمثلة المذكورة أعلاه ، هناك طرق أخرى للشرط تتيح لك تحديد اسم السياسة أو السياسة المطلقة أو مجموعة من المتطلبات.
يمكنك أيضًا إظهار ارتباط مشروط بناءً على أي منطق منطقي باستخدام حالة Assert . على سبيل المثال ، هناك طريقة تتيح لك إضافة روابط ترحيل شائعة إلى نتائج الكائنات المليئة. قد تقرر أن هذه ليست جديرة بالاهتمام إذا كان هناك ما مجموعه صفحة واحدة فقط من النتائج.
options . AddPolicy < IPageLinkContainer > ( policy =>
{
policy . RequireelfLink ( "all" )
. RequirePagingLinks ( condition => condition . Assert ( x => x . PageCount > 1 ) ) ;
} ) ; أنت حر في إضافة متطلباتك الخاصة باستخدام طريقة Generic Requires على LinksPolicyBuilder . بالإضافة إلى ذلك ، يجب عليك كتابة تطبيق 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 ، في كيفية إنشاء سياق التحويل أثناء التحول لـ Rel & Href من الروابط.IRouteMap ، يتحكم في كيفية فهرسة API الخاصة بك للسماح بالروابط بين الطرق. كان التغيير من الإصدار 1.0.x إلى 1.1.x في الغالب غير مفوض ، ولكن إذا قمت بتطبيق أي معالجات متطلبات مخصصة كما هو موضح في المثال أعلاه ، تغير توقيع Class Class LinksHandler قليلاً لإزالة الإعلان المكررة للنوع العام TResource .
في v1.0.x ، قد يكون الكود الخاص بك قد بدا:
public class MyCustomHandler : ILinksHandler { .. . } يجب أن يرث الآن من LinksHandler<TRequirement> جعل التنفيذ أكثر بساطة ، وإعطاء تخطي نوع آمن من HandleRequirementAsync يتيح الوصول إلى متطلباتك المغطاة بشكل صحيح.
public class MyCustomHandler : LinksHandler < MyCustomRequirement >