Skip to content

Commit f4ded72

Browse files
committed
Merge branch 'release/v3.0.0'
2 parents 981894b + b44aa9f commit f4ded72

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+3001
-37
lines changed

README.md

+124-4
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
![GitHub Release Date](https://img.shields.io/github/release-date/wissance/WebApiToolkit)
66
![GitHub release (latest by date)](https://img.shields.io/github/downloads/wissance/WebApiToolkit/v2.0.0/total?style=plastic)
77

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.
99

1010
![WebApiToolkit helps to build application easily](/img/cover.png)
1111

12+
1213
### 1. Key Features
1314
* `REST API Controller` with **full `CRUD`** contains ***only 20 lines*** of code (~ 10 are imports)
1415
- `GET` methods have ***built-in paging*** support;
1516
- `GET` methods have ***built-in sorting and filter*** by query parameters;
1617
* support ***BULK operations*** with objects (Bulk `Create`, `Update` and `Delete`) on a Controller && interface level
1718
* 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:
1819
- `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`)
2022

2123
Key concepts:
2224
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
6769

6870

6971
### 4. Toolkit usage algorithm with EntityFramework
72+
73+
#### 4.1 REST Services
74+
7075
Full example is mentioned in section 6 (see below). But if you are starting to build new `REST Resource`
7176
`API` you should do following:
7277
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
178183
[FromQuery(Name = "author")] public string[] Authors { get; set; }
179184
}
180185
```
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
181210

182211
### 5. Nuget package
183212
You could find nuget-package [here](https://www.nuget.org/packages/Wissance.WebApiToolkit)
184213

185214
### 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
187218

188219
```csharp
189220
[ApiController]
@@ -261,7 +292,96 @@ public class StationManager : EfModelManager<StationDto, StationEntity, int>
261292
private readonly ModelContext _modelContext;
262293
}
263294
```
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**.
265385

266386
### 7. Extending API
267387

Wissance.WebApiToolkit/Wissance.WebApiToolkit.Data/Entity/IModelIdentifiable.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
{
33
public interface IModelIdentifiable<TId>
44
{
5-
TId Id { get; }
5+
TId Id { get; set; }
66
}
77
}

Wissance.WebApiToolkit/Wissance.WebApiToolkit.Data/Wissance.WebApiToolkit.Data.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
</PropertyGroup>
66

77
<ItemGroup>
8-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.2" />
9-
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
8+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.17" />
9+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
1010
</ItemGroup>
1111

1212
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Wissance.WebApiToolkit.Controllers;
2+
using Wissance.WebApiToolkit.Data;
3+
using Wissance.WebApiToolkit.TestApp.Data.Entity;
4+
using Wissance.WebApiToolkit.TestApp.Dto;
5+
using Wissance.WebApiToolkit.TestApp.Managers;
6+
7+
namespace Wissance.WebApiToolkit.TestApp.Controllers
8+
{
9+
public sealed class CodeController : BasicReadController<CodeDto, CodeEntity, int, EmptyAdditionalFilters>
10+
{
11+
public CodeController(CodeManager manager)
12+
{
13+
Manager = manager;
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Wissance.WebApiToolkit.Controllers;
2+
using Wissance.WebApiToolkit.Data;
3+
using Wissance.WebApiToolkit.TestApp.Data.Entity;
4+
using Wissance.WebApiToolkit.TestApp.Dto;
5+
using Wissance.WebApiToolkit.TestApp.Managers;
6+
7+
namespace Wissance.WebApiToolkit.TestApp.Controllers
8+
{
9+
public class OrganizationController : BasicCrudController<OrganizationDto, OrganizationEntity, int, EmptyAdditionalFilters>
10+
{
11+
public OrganizationController(OrganizationManager manager)
12+
{
13+
Manager = manager;
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
using System.Collections.Generic;
3+
using Wissance.WebApiToolkit.Data.Entity;
4+
5+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity
6+
{
7+
public class CodeEntity : IModelIdentifiable<int>
8+
{
9+
public int Id { get; set; }
10+
public string Code { get; set; }
11+
public string Name { get; set; }
12+
13+
public virtual IList<OrganizationEntity> Organizations { get; set; }
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
2+
3+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity.Mapping
4+
{
5+
internal static class CodeMapper
6+
{
7+
public static void Map(this EntityTypeBuilder<CodeEntity> builder)
8+
{
9+
builder.HasKey(p => p.Id);
10+
builder.Property(p => p.Code).IsRequired();
11+
builder.Property(p => p.Name).IsRequired();
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
2+
3+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity.Mapping
4+
{
5+
internal static class OrganizationMapper
6+
{
7+
public static void Map(this EntityTypeBuilder<OrganizationEntity> builder)
8+
{
9+
builder.HasKey(p => p.Id);
10+
builder.Property(p => p.Name).IsRequired();
11+
builder.Property(p => p.ShortName).IsRequired();
12+
builder.Property(p => p.TaxNumber).IsRequired();
13+
builder.HasMany(p => p.Users)
14+
.WithOne(p => p.Organization)
15+
.HasForeignKey(p => p.OrganizationId);
16+
builder.HasMany(p => p.Codes).WithMany(p => p.Organizations);
17+
}
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
2+
3+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity.Mapping
4+
{
5+
internal static class UserMapper
6+
{
7+
public static void Map(this EntityTypeBuilder<UserEntity> builder)
8+
{
9+
builder.HasKey(p => p.Id);
10+
}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
using Wissance.WebApiToolkit.Data.Entity;
3+
4+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity
5+
{
6+
public class OrganizationEntity : IModelIdentifiable<int>
7+
{
8+
public int Id { get; set; }
9+
public string Name { get; set; }
10+
public string ShortName { get; set; }
11+
public string TaxNumber { get; set; }
12+
public virtual IList<UserEntity> Users { get; set; }
13+
public virtual IList<CodeEntity> Codes { get; set; }
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity
2+
{
3+
public class ProfileEntity
4+
{
5+
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Wissance.WebApiToolkit.Data.Entity;
2+
3+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity
4+
{
5+
public class RoleEntity : IModelIdentifiable<int>
6+
{
7+
public int Id { get; set; }
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Wissance.WebApiToolkit.Data.Entity;
2+
3+
namespace Wissance.WebApiToolkit.TestApp.Data.Entity
4+
{
5+
public class UserEntity : IModelIdentifiable<int>
6+
{
7+
public int Id { get; set; }
8+
public string FullName { get; set; }
9+
public string Login { get; set; }
10+
public int OrganizationId { get; set; }
11+
public virtual OrganizationEntity Organization { get; set; }
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Wissance.WebApiToolkit.TestApp.Data.Entity;
3+
using Wissance.WebApiToolkit.TestApp.Data.Entity.Mapping;
4+
5+
namespace Wissance.WebApiToolkit.TestApp.Data
6+
{
7+
public class ModelContext : DbContext
8+
{
9+
public ModelContext()
10+
{
11+
}
12+
13+
public ModelContext(DbContextOptions<ModelContext> options)
14+
:base(options)
15+
{
16+
}
17+
18+
public override int SaveChanges()
19+
{
20+
try
21+
{
22+
return base.SaveChanges();
23+
}
24+
catch (Exception e)
25+
{
26+
return -1;
27+
}
28+
}
29+
30+
public async Task<int> SaveChangesAsync()
31+
{
32+
return await base.SaveChangesAsync();
33+
}
34+
35+
36+
protected override void OnModelCreating(ModelBuilder modelBuilder)
37+
{
38+
base.OnModelCreating(modelBuilder);
39+
40+
modelBuilder.Entity<CodeEntity>().Map();
41+
modelBuilder.Entity<OrganizationEntity>().Map();
42+
modelBuilder.Entity<UserEntity>().Map();
43+
}
44+
45+
public DbSet<CodeEntity> Codes { get; set; }
46+
public DbSet<OrganizationEntity> Organizations { get; set; }
47+
public DbSet<UserEntity> Users { get; set; }
48+
}
49+
}

0 commit comments

Comments
 (0)