REST API مع C# و AspNet بسهولة من كتابته من نقطة الصفر مرارًا وتكرارًا في مشاريع مختلفة. 
REST API Controller مع CRUD الكامل على 20 سطرًا من التعليمات البرمجية فقط (~ 10 واردات)GET الأساليب دعم ترحيل مدمج ؛GET الأساليب تحتوي على فرز وفلتر مدمج بواسطة معلمات الاستعلام ؛Create Update Delete ) على مستوى وحدة التحكم &&IModelManager ) ؛ دعم جيد في EntityFramework (انظر فئة EfModelManager ). انظر تطبيق WeatherControl الذي يحتوي على مشروعين لـ API على الويب:Wissance.WeatherControl.WebApi يستخدم EntityFramework ؛Wissance.WeatherControl.WebApi.V2 يستخدم EdgeDb .المفاهيم الرئيسية:
Controller هي فئة تتعامل مع HTTP-requests إلى REST Resource .REST Resource يساوي Entity class / Database TableREST Resource تنتج JSON مع DTO كمخرجات. نحن نفترض استخدام فئة DTO واحدة فقط مع جميع أساليب REST . دروس DTO :
OperationResultDto نتيجة التشغيل التي تغير البيانات في DB ؛PagedDataDto جزءًا (صفحة) من نفس الكائنات (أي نوع) ؛ فصول Controllers - فصول مجردة
BasicReadController ) على طريقتين:GET /api/[controller]/?[page={page}&size={size}&sort={sort}&order={order}] للحصول على PagedDataDto<T> الآن لدينا أيضًا إمكانية إرسال أي عدد من المعلمات الاستعلام ، عليك فقط تمرير func func إلى EfModelManager أو القيام به في طريقك مثل weatherTrol. نقوم أيضًا بتمرير فرز (اسم العمود) && order ( asc أو desc ) إلى فئات المدير ، يسمح EfModelManager بالفرز بأي عمود . Swagger لإظهار استخدام معلمات الاستعلام !!!1.6.0 من الممكن رؤية جميع المعلمات في Swagger واستخدامها.GET /api/[controller]/{id} للحصول على كائن واحد idCRUD الكاملة ( BasicCrudController ) = وحدة تحكم القراءة الأساسية ( BasicReadController ) + Create Update Delete العمليات:POST /api/[controller] - لإنشاء كائن جديدPUT /api/[controller]/{id} - لتحرير كائن بالمعرفDELETE /api/[controller]/{id} - لحذف كائن عن طريق المعرفCRUD الكامل مع العمليات بالجملة (العمليات على كائنات متعددة في وقت واحد) ، الفئة الأساسية - BasicBulkCrudController = وحدة تحكم القراءة الأساسية ( BasicReadController ) + العمليات BulkCreate و BulkUpdate و BulkDelete :POST /api/bulk/[controller] - لإنشاء كائنات جديدةPUT /api/bulk/[controller] - لتحرير الكائنات التي تمر في جسم طلبDELETE /api/bulk/[controller]/{idList} - لحذف كائنات متعددة عن طريق المعرف.تتوقع فصول وحدات التحكم أن يتم تنفيذ جميع العمليات باستخدام فئات المدير (يجب أن يكون لكل وحدة تحكم مديرها)
فصول المديرين - الفصول التي تنفذ منطق العمل للتطبيق
IModelManager - واجهة تصف العمليات الأساسيةEfModelManager - فئة مجردة تحتوي على تنفيذ عمليات Get DeleteEfSoftRemovableModelManager فئة مجردة تحتوي على تنفيذ عمليات Get Delete مع نماذج قابلة للإزالة ناعمة (تم إزالة نموذج الوسائل IsDeleted = true ) مثال على مدى أسرع بالجملة مقابل عدم الالتواء: 
Elapsed time in Non-Bulk REST API with EF is 0.9759984016418457 secs.
Elapsed time in Bulk API with EF is 0.004002094268798828 secs.
نتيجة لذلك ، حصلنا على ~ 250 x أسرع API .
لا يوجد سوى متطلبات واحدة : يجب على جميع فئات الكيانات لأي تخزين للثبات الذي يستخدم مع وحدات التحكم والمديرين أن ينفذوا IModelIdentifiable<T> من Wissance.WebApiToolkit.Data.Entity . إذا كان يجب استخدام مجموعة الأدوات هذه مع EntityFramework فيجب عليك استخلاص مدير الموارد من EfModelManager ، فهي لديها طرق مدمجة لـ:
get many العناصرget one by iddelete العنصر by id تم ذكر المثال الكامل في القسم 6 (انظر أدناه). ولكن إذا كنت تبدأ في إنشاء API REST Resource الجديدة ، فيجب عليك القيام بمتابعة:
model ( entity ) فئة تنفيذ IModelIdentifiable<T> و DTO فئة لتمثيل تكنولوجيا المعلومات ( للإزالة الناعمة أيضًا إضافة IModelSoftRemovable ) ، أي: public class BookEntity : IModelIdentifiable < int >
{
public int Id { get ; set ; }
public string Title { get ; set ; }
public string Authors { get ; set ; } // for simplicity
public DateTimeOffset Created { get ; set ; }
public DateTimeOffset Updated { get ; set ; }
}
public class BookDto
{
public int Id { get ; set ; }
public string Title { get ; set ; }
public string Authors { get ; set ; }
}Model إلى DTO IE: public static class BookFactory
{
public static BookDto Create ( BookEntity entity )
{
return new BookDto
{
Id = entity . Id ,
Title = entity . Title ,
Authors = entity . Authors ;
} ;
}
}IModelContext التي تجعلك BookEntity كفئة تنفيذ DbSet وفئة تنفيذ مستمدة أيضًا من DbContext ( EF Abstract Class ): public interface IModelContext
{
DbSet < BookEntity > Books { get ; set ; }
}
public MoidelContext : DbContext < ModelContext > , IModelContext
{
// todo: not mrntioned here constructor, entity mapping and so on
public DbSet < BookEntity > Books { get ; set ; }
}ModelContext كصاحب DbContext عبر DI بدء التشغيلController وزوج فئة مدير ، أي اعتبار هنا CRUD الكامل [ ApiController ]
public class BookController : BasicCrudController < BookDto , BookEntity , int , EmptyAdditionalFilters >
{
public BookController ( BookManager manager )
{
Manager = manager ; // this is for basic operations
_manager = manager ; // this for extended operations
}
private BookManager _manager ;
}
public class BookManager : EfModelManager < BookEntity , BookDto , int , EmptyAdditionalFilters >
{
public BookManager ( ModelContext modelContext , ILoggerFactory loggerFactory ) : base ( modelContext , BookFactory . Create , loggerFactory )
{
_modelContext = modelContext ;
}
public override async Task < OperationResultDto < StationDto > > CreateAsync ( StationDto data )
{
// todo: implement
}
public override async Task < OperationResultDto < StationDto > > UpdateAsync ( int id , StationDto data )
{
// todo: implement
}
private readonly ModelContext _modelContext ;
} المعلمة العامة الأخيرة في مثال أعلاه - EmptyAdditionalFilters هي فئة تحمل معلمات إضافية للبحث لرؤية في Swagger ، فقط حدد فئة جديدة تنفذ IReadFilterable IE:
public class BooksFilterable : IReadFilterable
{
public IDictionary < string , string > SelectFilters ( )
{
IDictionary < string , string > additionalFilters = new Dictionary < string , string > ( ) ;
if ( ! string . IsNullOrEmpty ( Title ) )
{
additionalFilters . Add ( FilterParamsNames . TitleParameter , Title ) ;
}
if ( Authors != null && Authors . Length > 0 )
{
additionalFilters . Add ( FilterParamsNames . AuthorsParameter , string . Join ( "," , Authors ) ) ;
}
return additionalFilters ;
}
[ FromQuery ( Name = "title" ) ] public string Title { get ; set ; }
[ FromQuery ( Name = "author" ) ] public string [ ] Authors { get ; set ; }
}يمكن أن تجد nuget package هنا
[ ApiController ]
public class StationController : BasicCrudController < StationDto , StationEntity , int , EmptyAdditionalFilters >
{
public StationController ( StationManager manager )
{
Manager = manager ; // this is for basic operations
_manager = manager ; // this for extended operations
}
private StationManager _manager ;
} public class StationManager : EfModelManager < StationEntity , StationDto , int >
{
public StationManager ( ModelContext modelContext , ILoggerFactory loggerFactory ) : base ( modelContext , StationFactory . Create , loggerFactory )
{
_modelContext = modelContext ;
}
public override async Task < OperationResultDto < StationDto > > CreateAsync ( StationDto data )
{
try
{
StationEntity entity = StationFactory . Create ( data ) ;
await _modelContext . Stations . AddAsync ( entity ) ;
int result = await _modelContext . SaveChangesAsync ( ) ;
if ( result >= 0 )
{
return new OperationResultDto < StationDto > ( true , ( int ) HttpStatusCode . Created , null , StationFactory . Create ( entity ) ) ;
}
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , "An unknown error occurred during station creation" , null ) ;
}
catch ( Exception e )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , $ "An error occurred during station creation: { e . Message } " , null ) ;
}
}
public override async Task < OperationResultDto < StationDto > > UpdateAsync ( int id , StationDto data )
{
try
{
StationEntity entity = StationFactory . Create ( data ) ;
StationEntity existingEntity = await _modelContext . Stations . FirstOrDefaultAsync ( s => s . Id == id ) ;
if ( existingEntity == null )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . NotFound , $ "Station with id: { id } does not exists" , null ) ;
}
// Copy only name, description and positions, create measurements if necessary from MeasurementsManager
existingEntity . Name = entity . Name ;
existingEntity . Description = existingEntity . Description ;
existingEntity . Latitude = existingEntity . Latitude ;
existingEntity . Longitude = existingEntity . Longitude ;
int result = await _modelContext . SaveChangesAsync ( ) ;
if ( result >= 0 )
{
return new OperationResultDto < StationDto > ( true , ( int ) HttpStatusCode . OK , null , StationFactory . Create ( entity ) ) ;
}
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , "An unknown error occurred during station update" , null ) ;
}
catch ( Exception e )
{
return new OperationResultDto < StationDto > ( false , ( int ) HttpStatusCode . InternalServerError , $ "An error occurred during station update: { e . Message } " , null ) ;
}
}
private readonly ModelContext _modelContext ;
}فقط فئتان بسيطتان جدًا ^ ^ باستخدام webapitoolkit
النظر في أننا نود إضافة البحث عن الطريقة إلى وحدة التحكم الخاصة بنا:
[ HttpGet ]
[ Route ( "api/[controller]/search" ) ]
public async Task < PagedDataDto < BookDto > > > SearchAsync ( [ FromQuery ] string query , [ FromQuery ] int page , [ FromQuery ] int size )
{
OperationResultDto < Tuple < IList < BookDto > , long > > result = await Manager . GetAsync ( page , size , query ) ;
if ( result == null )
{
HttpContext . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
}
HttpContext . Response . StatusCode = result . Status ;
return new PagedDataDto < TRes > ( pageNumber , result . Data . Item2 , GetTotalPages ( result . Data . Item2 , pageSize ) , result . Data . Item1 ) ;
} لدينا مشروع إضافي لحماية API من خلال Keycloak OpenId-Connect . تمرير IHttpContextAccessor إلى فئة Manager والتحقق من شيء من هذا القبيل: ClaimsPrincipal principal = _httpContext.HttpContext.User;
يمكنك رؤية مقالاتنا حول استخدام مجموعة الأدوات: