|
5 | 5 | 
|
6 | 6 | 
|
7 | 7 |
|
8 |
| -#### This lib helps to build `REST API` with `C#` and `AspNet` easily than writing it from scratch over and over in different projects. |
| 8 | +#### This lib helps to build `REST API` with `C#` and `AspNet` easier than writing it from scratch over and over in different projects. It helps to build consistent API (with same `REST` routes scheme) with minimal amount of code: minimal REST controller contains 10 lines of code. |
9 | 9 |
|
10 | 10 | 
|
11 | 11 |
|
| 12 | + |
12 | 13 | ### 1. Key Features
|
13 | 14 | * `REST API Controller` with **full `CRUD`** contains ***only 20 lines*** of code (~ 10 are imports)
|
14 | 15 | - `GET` methods have ***built-in paging*** support;
|
15 | 16 | - `GET` methods have ***built-in sorting and filter*** by query parameters;
|
16 | 17 | * support ***BULK operations*** with objects (Bulk `Create`, `Update` and `Delete`) on a Controller && interface level
|
17 | 18 | * support to work with ***any persistent storage*** (`IModelManager` interface); Good built-in EntityFramework support (see `EfModelManager` class). See [WeatherControl App](https://github.com/Wissance/WeatherControl) which has 2 WEB API projects:
|
18 | 19 | - `Wissance.WeatherControl.WebApi` uses `EntityFramework`;
|
19 |
| - - `Wissance.WeatherControl.WebApi.V2` uses `EdgeDb`. |
| 20 | + - `Wissance.WeatherControl.WebApi.V2` uses `EdgeDb` |
| 21 | +* support writing `GRPC` services with examples (see `Wissance.WebApiToolkit.TestApp` and `Wissance.WebApiToolkit.Tests`) |
20 | 22 |
|
21 | 23 | Key concepts:
|
22 | 24 | 1. `Controller` is a class that handles `HTTP-requests` to `REST Resource`.
|
@@ -67,6 +69,9 @@ If this toolkit should be used with `EntityFramework` you should derive you reso
|
67 | 69 |
|
68 | 70 |
|
69 | 71 | ### 4. Toolkit usage algorithm with EntityFramework
|
| 72 | + |
| 73 | +#### 4.1 REST Services |
| 74 | + |
70 | 75 | Full example is mentioned in section 6 (see below). But if you are starting to build new `REST Resource`
|
71 | 76 | `API` you should do following:
|
72 | 77 | 1. Create a `model` (`entity`) class implementing `IModelIdentifiable<T>` and `DTO` class for it representation (**for soft remove** also **add** `IModelSoftRemovable` implementation), i.e.:
|
@@ -178,12 +183,38 @@ public class BooksFilterable : IReadFilterable
|
178 | 183 | [FromQuery(Name = "author")] public string[] Authors { get; set; }
|
179 | 184 | }
|
180 | 185 | ```
|
| 186 | + |
| 187 | +#### 4.2 GRPC Services |
| 188 | + |
| 189 | +Starting from `v3.0.0` it possible to create GRPC Services and we have algorithm for this with example based on same Manager classes with service classes that works as a proxy for generating GRPC-services, here we have 2 type of services: |
| 190 | +1. `RO` service with methods for Read data - `ResourceBasedDataManageableReadOnlyService` (GRPC equivalent to `BasicReadController`) |
| 191 | +2. `CRUD` service with methods Read + Create + Update and Delete - `ResourceBasedDataManageableCrudService` |
| 192 | + |
| 193 | +For building GRPC services based on these service implementation we just need to pass instance of this class to constructor, consider that we are having `CodeService` |
| 194 | + |
| 195 | +```csharp |
| 196 | +public class CodeGrpcService : CodeService.CodeServiceBase |
| 197 | +{ |
| 198 | + public CodeGrpcService(ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters> serviceImpl) |
| 199 | + { |
| 200 | + _serviceImpl = serviceImpl; |
| 201 | + } |
| 202 | + |
| 203 | + // GRPC methods impl |
| 204 | + |
| 205 | + private readonly ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters> _serviceImpl; |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +Unfortunately GRPC generates all types Request and therefore we should implement additional mapping to convert `DTO` to Response, see full example in this solution in the `Wissance.WebApiToolkit.TestApp` project |
181 | 210 |
|
182 | 211 | ### 5. Nuget package
|
183 | 212 | You could find nuget-package [here](https://www.nuget.org/packages/Wissance.WebApiToolkit)
|
184 | 213 |
|
185 | 214 | ### 6. Examples
|
186 |
| -### Here we consider only Full CRUD controllers because **Full CRUD = Read Only + Additional Operations (CREATE, UPDATE, DELETE)**, a **full example = full application** created with **Wissance.WebApiToolkit** could be found here: https://github.com/Wissance/WeatherControl |
| 215 | +Here we consider only Full CRUD controllers because **Full CRUD = Read Only + Additional Operations (CREATE, UPDATE, DELETE)**, a **full example = full application** created with **Wissance.WebApiToolkit** could be found [here]( https://github.com/Wissance/WeatherControl) |
| 216 | + |
| 217 | +#### 6.1 REST Service example |
187 | 218 |
|
188 | 219 | ```csharp
|
189 | 220 | [ApiController]
|
@@ -261,7 +292,96 @@ public class StationManager : EfModelManager<StationDto, StationEntity, int>
|
261 | 292 | private readonly ModelContext _modelContext;
|
262 | 293 | }
|
263 | 294 | ```
|
264 |
| -JUST 2 VERY SIMPLE CLASSES ^^ USING WebApiToolkit |
| 295 | + |
| 296 | +*JUST 2 VERY SIMPLE CLASSES ^^ USING WebApiToolkit* |
| 297 | + |
| 298 | +#### 6.2 GRPC Service example |
| 299 | + |
| 300 | +For building GRPC service all what we need: |
| 301 | +1. `.proto` file, consider our CodeService example, we have the following GRPC methods: |
| 302 | +```proto |
| 303 | +service CodeService { |
| 304 | + rpc ReadOne(OneItemRequest) returns (CodeOperationResult); |
| 305 | + rpc ReadMany(PageDataRequest) returns (CodePagedDataOperationResult); |
| 306 | +} |
| 307 | +``` |
| 308 | +2. `DI` for making service implementation: |
| 309 | +```csharp |
| 310 | +private void ConfigureWebServices(IServiceCollection services) |
| 311 | +{ |
| 312 | + services.AddScoped<ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters>>( |
| 313 | + sp => |
| 314 | + { |
| 315 | + return new ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters>(sp.GetRequiredService<CodeManager>()); |
| 316 | + }); |
| 317 | +} |
| 318 | +``` |
| 319 | +3. GRPC Service that derives from generated service and use as a proxy to `ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters>`: |
| 320 | +```csharp |
| 321 | +public class CodeGrpcService : CodeService.CodeServiceBase |
| 322 | + { |
| 323 | + public CodeGrpcService(ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters> serviceImpl) |
| 324 | + { |
| 325 | + _serviceImpl = serviceImpl; |
| 326 | + } |
| 327 | + |
| 328 | + public override async Task<CodePagedDataOperationResult> ReadMany(PageDataRequest request, ServerCallContext context) |
| 329 | + { |
| 330 | + OperationResultDto<PagedDataDto<CodeDto>> result = await _serviceImpl.ReadAsync(request.Page, request.Size, request.Sort, request.Order, |
| 331 | + new EmptyAdditionalFilters()); |
| 332 | + context.Status = GrpcErrorCodeHelper.GetGrpcStatus(result.Status, result.Message); |
| 333 | + CodePagedDataOperationResult response = new CodePagedDataOperationResult() |
| 334 | + { |
| 335 | + Success = result.Success, |
| 336 | + Message = result.Message ?? String.Empty, |
| 337 | + Status = result.Status, |
| 338 | + }; |
| 339 | + |
| 340 | + if (result.Data != null) |
| 341 | + { |
| 342 | + response.Data = new CodePagedDataResult() |
| 343 | + { |
| 344 | + Page = result.Data.Page, |
| 345 | + Pages = result.Data.Pages, |
| 346 | + Total = result.Data.Total, |
| 347 | + Data = {result.Data.Data.Select(c => Convert(c))} |
| 348 | + }; |
| 349 | + } |
| 350 | + |
| 351 | + return response; |
| 352 | + } |
| 353 | + |
| 354 | + public override async Task<CodeOperationResult> ReadOne(OneItemRequest request, ServerCallContext context) |
| 355 | + { |
| 356 | + OperationResultDto<CodeDto> result = await _serviceImpl.ReadByIdAsync(request.Id); |
| 357 | + context.Status = GrpcErrorCodeHelper.GetGrpcStatus(result.Status, result.Message); |
| 358 | + CodeOperationResult response = new CodeOperationResult() |
| 359 | + { |
| 360 | + Success = result.Success, |
| 361 | + Message = result.Message ?? String.Empty, |
| 362 | + Status = result.Status, |
| 363 | + Data = Convert(result.Data) |
| 364 | + }; |
| 365 | + return response; |
| 366 | + } |
| 367 | + |
| 368 | + private Code Convert(CodeDto dto) |
| 369 | + { |
| 370 | + if (dto == null) |
| 371 | + return null; |
| 372 | + return new Code() |
| 373 | + { |
| 374 | + Id = dto.Id, |
| 375 | + Code_ = dto.Code, |
| 376 | + Name = dto.Name |
| 377 | + }; |
| 378 | + } |
| 379 | + |
| 380 | + private readonly ResourceBasedDataManageableReadOnlyService<CodeDto, CodeEntity, int, EmptyAdditionalFilters> _serviceImpl; |
| 381 | + } |
| 382 | +``` |
| 383 | + |
| 384 | +**Full example how it all works see in `Wissance.WebApiToolkit.TestApp` project**. |
265 | 385 |
|
266 | 386 | ### 7. Extending API
|
267 | 387 |
|
|
0 commit comments