{"id":22517591,"url":"https://github.com/wissance/webapitoolkit","last_synced_at":"2025-08-03T18:31:49.440Z","repository":{"id":133047600,"uuid":"438212782","full_name":"Wissance/WebApiToolkit","owner":"Wissance","description":"WebApi toolset that allows to make CRUD REST API like a cookie for ~ 20 lines of code, it also allow to build GRPC too","archived":false,"fork":false,"pushed_at":"2025-08-02T20:03:05.000Z","size":2523,"stargazers_count":39,"open_issues_count":22,"forks_count":8,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-08-02T22:14:03.527Z","etag":null,"topics":["crud","crud-api","csharp-library","csharp-web","library","rest-api","rest-api-template","rest-api-toolkit","web-application","webapi","webapi-core"],"latest_commit_sha":null,"homepage":"https://wissance.github.io/WebApiToolkit/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wissance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-12-14T10:37:32.000Z","updated_at":"2025-06-04T08:49:47.000Z","dependencies_parsed_at":"2023-12-14T08:27:48.022Z","dependency_job_id":"9ba2ddeb-e5a3-43d7-b0dd-1d4a7cebeacf","html_url":"https://github.com/Wissance/WebApiToolkit","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/Wissance/WebApiToolkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWebApiToolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWebApiToolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWebApiToolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWebApiToolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wissance","download_url":"https://codeload.github.com/Wissance/WebApiToolkit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wissance%2FWebApiToolkit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268594137,"owners_count":24275733,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crud","crud-api","csharp-library","csharp-web","library","rest-api","rest-api-template","rest-api-toolkit","web-application","webapi","webapi-core"],"created_at":"2024-12-07T04:10:00.375Z","updated_at":"2025-08-03T18:31:49.430Z","avatar_url":"https://github.com/Wissance.png","language":"C#","readme":"## Wissance.WebApiToolkit\n\n![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/wissance/WebApiToolkit?style=plastic) \n![GitHub issues](https://img.shields.io/github/issues/wissance/WebApiToolkit?style=plastic)\n![GitHub Release Date](https://img.shields.io/github/release-date/wissance/WebApiToolkit) \n![GitHub release (latest by date)](https://img.shields.io/github/downloads/wissance/WebApiToolkit/v2.0.0/total?style=plastic)\n\n#### 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.\n\n![WebApiToolkit helps to build application easily](/img/cover.png)\n\n * [1. Key Features](#1-key-features)\n  * [2. API Contract](#2-api-contract)\n  * [3. Requirements](#3-requirements)\n  * [4. Toolkit usage algorithm with EntityFramework](#4-toolkit-usage-algorithm-with-entityframework)\n    + [4.1 REST Services](#41-rest-services)\n    + [4.2 GRPC Services](#42-grpc-services)\n  * [5. Nuget package](#5-nuget-package)\n  * [6. Examples](#6-examples)\n    + [6.1 REST Service example](#61-rest-service-example)\n    + [6.2 GRPC Service example](#62-grpc-service-example)\n  * [7. Extending API](#7-extending-api)\n    + [7.1 Add new methods to existing controller](#71-add-new-methods-to-existing-controller)\n    + [7.2 Add security to protect you API](#72-add-security-to-protect-you-api)\n  * [8. Additional materials](#8-additional-materials)\n  * [9. Contributors](#9-contributors)\n\n### 1. Key Features\n* `REST API Controller` with **full `CRUD`** contains ***only 20 lines*** of code (~ 10 are imports)\n  - `GET` methods have ***built-in paging*** support;\n  - `GET` methods have ***built-in sorting and filter*** by query parameters;\n* support ***BULK operations*** with objects (Bulk `Create`, `Update` and `Delete`) on a Controller \u0026\u0026 interface level\n* 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: \n  - `Wissance.WeatherControl.WebApi` uses `EntityFramework`;\n  - `Wissance.WeatherControl.WebApi.V2` uses `EdgeDb`\n* support writing `GRPC` services with examples (see `Wissance.WebApiToolkit.TestApp` and `Wissance.WebApiToolkit.Tests`)\n  \nKey concepts:\n1. `Controller` is a class that handles `HTTP-requests` to `REST Resource`.\n2. `REST Resource` is equal to `Entity class / Database Table`\n3. Every operation on `REST Resource` produce `JSON` with `DTO` as output. We ASSUME to use only one `DTO` class with all `REST` methods.  \n\n### 2. API Contract\n* `DTO` classes:\n    - `OperationResultDto` represents result of operation that changes Data in db;\n    - `PagedDataDto` represents portion (page) of same objects (any type);\n* `Controllers` classes - abstract classes\n    - basic read controller (`BasicReadController`) contains 2 methods:\n        - `GET /api/[controller]/?[page={page}\u0026size={size}\u0026sort={sort}\u0026order={order}]` to get `PagedDataDto\u003cT\u003e`\n          now we also have possibility to send **ANY number of query params**, you just have to pass filter func to `EfModelManager` or do it in your own way like in [WeatherControl example with edgedb](https://github.com/Wissance/WeatherControl/blob/master/WeatherControl/Wissance.WeatherControl.WebApi.V2/Helpers/EqlResolver.cs). We also pass sort (column name) \u0026\u0026 order (`asc` or `desc`) to manager classes,\n          `EfModelManager` allows to sort **by any column**. \n          \u003cstrike\u003e Unfortunately here we have a ***ONE disadvantage*** - **we should override `Swagger` info to show query parameters usage!!!** \u003c/strike\u003e Starting from `1.6.0` it is possible to see all parameters in `Swagger` and use them.\n        - `GET /api/[controller]/{id}` to get one object by `id`\n    - full `CRUD` controller (`BasicCrudController`) = basic read controller (`BasicReadController`) + `Create`, `Update` and `Delete` operations :\n        - `POST   /api/[controller]` - for new object creation\n        - `PUT    /api/[controller]/{id}` - for edit object by id\n        - `DELETE /api/[controller]/{id}` - for delete object by id\n    - full `CRUD` with **Bulk** operations (operations over multiple objects at once), Base class - `BasicBulkCrudController` = basic read controller (`BasicReadController`) + `BulkCreate`, `BulkUpdate` and `BulkDelete` operations:\n        - `POST   /api/bulk/[controller]` - for new objects creation\n        - `PUT    /api/bulk/[controller]` - for edit objects passing in a request body\n        - `DELETE /api/bulk/[controller]/{idList}` - for delete multiple objects by id.\n        \n  Controllers classes expects that all operation will be performed using Manager classes (each controller must have it own manager)\n* Managers classes - classes that implements business logic of application\n    - `IModelManager` - interface that describes basic operations\n    - `EfModelManager`- is abstract class that contains implementation of `Get` and `Delete` operations\n    - `EfSoftRemovableModelManager` is abstract class that contains implementation of `Get` and `Delete` operations with soft removable models (`IsDeleted = true` means model was removed)\n    \nExample of how faster Bulk vs Non-Bulk:\n![Bulk vs Non Bulk](/img/bulk_performance.png)\n```\nElapsed time in Non-Bulk REST API with EF is 0.9759984016418457 secs.\nElapsed time in Bulk API with EF is 0.004002094268798828 secs.\n```\nas a result we got almost ~`250 x` faster `API`.\n\n### 3. Requirements\nThere is **only ONE requirement**: all Entity classes for any Persistence storage that are using with controllers \u0026 managers MUST implements `IModelIdentifiable\u003cT\u003e` from `Wissance.WebApiToolkit.Data.Entity`.\nIf this toolkit should be used with `EntityFramework` you should derive you resource manager from\n`EfModelManager` it have built-in methods for:\n* `get many` items\n* `get one` item `by id`\n* `delete` item `by id`\n\n\n### 4. Toolkit usage algorithm with EntityFramework\n\n#### 4.1 REST Services\n\nFull example is mentioned in section 6 (see below). But if you are starting to build new `REST Resource`\n`API` you should do following:\n1. Create a `model` (`entity`) class implementing `IModelIdentifiable\u003cT\u003e` and `DTO` class for it representation (**for soft remove** also **add** `IModelSoftRemovable` implementation), i.e.:\n```csharp\npublic class BookEntity : IModelIdentifiable\u003cint\u003e\n{\n    public int Id {get; set;}\n    public string Title {get; set;}\n    public string Authors {get; set;} // for simplicity\n    public DateTimeOffset Created {get; set;}\n    public DateTimeOffset Updated {get; set;}\n}\n\npublic class BookDto\n{\n    public int Id {get; set;}\n    public string Title {get; set;}\n    public string Authors {get; set;} \n}\n```\n2. Create a factory function (i.e. static function of a static class) that converts `Model` to `DTO` i.e.:\n```csharp\npublic static class BookFactory\n{\n    public static BookDto Create(BookEntity entity)\n    {\n        return new BookDto\n        {\n            Id = entity.Id,\n            Title = entity.Title,\n            Authors = entity.Authors;\n        };\n    }\n}\n```\n3. Create `IModelContext` interface that has you `BookEntity` as a `DbSet` and it's implementation class that also derives from `DbContext` (**Ef abstract class**):\n```csharp\npublic interface IModelContext\n{\n    DbSet\u003cBookEntity\u003e Books {get;set;}\n}\n\npublic MoidelContext: DbContext\u003cModelContext\u003e, IModelContext\n{\n    // todo: not mrntioned here constructor, entity mapping and so on\n    public DbSet\u003cBookEntity\u003e Books {get; set;}\n}\n```\n4. Configure to inject `ModelContext` as a `DbContext` via `DI` see [Startup](https://github.com/Wissance/WeatherControl/blob/master/WeatherControl/Wissance.WeatherControl/Startup.cs) class\n5. Create `Controller` class and a manager class pair, i.e. consider here full `CRUD`\n```csharp\n[ApiController]\npublic class BookController : BasicCrudController\u003cBookDto, BookEntity, int, EmptyAdditionalFilters\u003e\n{\n    public BookController(BookManager manager)\n    {\n        Manager = manager;  // this is for basic operations\n        _manager = manager; // this for extended operations\n    }\n    \n    private BookManager _manager;\n}\n\npublic class BookManager : EfModelManager\u003cBookDto, BookEntity, int, EmptyAdditionalFilters\u003e\n{\n    public BookManager(ModelContext modelContext, ILoggerFactory loggerFactory) : base(modelContext, BookFactory.Create, loggerFactory)\n    {\n        _modelContext = modelContext;\n    }\n    \n    public override async Task\u003cOperationResultDto\u003cStationDto\u003e\u003e CreateAsync(StationDto data)\n    {\n        // todo: implement\n    }\n    \n    public override async Task\u003cOperationResultDto\u003cStationDto\u003e\u003e UpdateAsync(int id, StationDto data)\n    {\n        // todo: implement\n    }\n    \n    private readonly ModelContext _modelContext;\n}\n```\n\nLast generic parameter in above example - `EmptyAdditionalFilters` is a class that holds\nadditional parameters for search to see in Swagger, just specify a new class implementing\n`IReadFilterable` i.e.:\n\n```csharp\npublic class BooksFilterable : IReadFilterable\n{\n    public IDictionary\u003cstring, string\u003e SelectFilters()\n    {\n            IDictionary\u003cstring, string\u003e additionalFilters = new Dictionary\u003cstring, string\u003e();\n            if (!string.IsNullOrEmpty(Title))\n            {\n                additionalFilters.Add(FilterParamsNames.TitleParameter, Title);\n            }\n\n            if (Authors != null \u0026\u0026 Authors.Length \u003e 0)\n            {\n                additionalFilters.Add(FilterParamsNames.AuthorsParameter, string.Join(\",\", Authors));\n            }\n\n            return additionalFilters;\n    }\n        \n    [FromQuery(Name = \"title\")] public string Title { get; set; }\n    [FromQuery(Name = \"author\")] public string[] Authors { get; set; }\n}\n```\n\n#### 4.2 GRPC Services\n\nStarting 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:\n1. `RO` service with methods for Read data - `ResourceBasedDataManageableReadOnlyService` (GRPC equivalent to `BasicReadController`)\n2. `CRUD` service with methods Read + Create + Update and Delete - `ResourceBasedDataManageableCrudService`\n\nFor 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` \n\n```csharp\npublic class CodeGrpcService : CodeService.CodeServiceBase\n{\n    public CodeGrpcService(ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e serviceImpl)\n    {\n        _serviceImpl = serviceImpl;\n    }\n    \n    // GRPC methods impl\n    \n    private readonly ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e _serviceImpl;\n}\n```\n\nUnfortunately 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 \n    \n### 5. Nuget package\nYou could find nuget-package [here](https://www.nuget.org/packages/Wissance.WebApiToolkit)\n    \n### 6. Examples\nHere 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)\n\n#### 6.1 REST Service example\n\n```csharp\n[ApiController]\npublic class StationController : BasicCrudController\u003cStationDto, StationEntity, int, EmptyAdditionalFilters\u003e\n{\n    public StationController(StationManager manager)\n    {\n        Manager = manager;  // this is for basic operations\n        _manager = manager; // this for extended operations\n    }\n    \n    private StationManager _manager;\n}\n```\n    \n```csharp\npublic class StationManager : EfModelManager\u003cStationDto, StationEntity, int\u003e\n{\n    public StationManager(ModelContext modelContext, ILoggerFactory loggerFactory) : base(modelContext, StationFactory.Create, loggerFactory)\n    {\n        _modelContext = modelContext;\n    }\n\n    public override async Task\u003cOperationResultDto\u003cStationDto\u003e\u003e CreateAsync(StationDto data)\n    {\n        try\n        {\n            StationEntity entity = StationFactory.Create(data);\n            await _modelContext.Stations.AddAsync(entity);\n            int result = await _modelContext.SaveChangesAsync();\n            if (result \u003e= 0)\n            {\n                return new OperationResultDto\u003cStationDto\u003e(true, (int)HttpStatusCode.Created, null, StationFactory.Create(entity));\n            }\n            return new OperationResultDto\u003cStationDto\u003e(false, (int)HttpStatusCode.InternalServerError, \"An unknown error occurred during station creation\", null);\n\n            }\n        catch (Exception e)\n        {\n            return new OperationResultDto\u003cStationDto\u003e(false, (int)HttpStatusCode.InternalServerError, $\"An error occurred during station creation: {e.Message}\", null);\n        }\n    }\n\n    public override async Task\u003cOperationResultDto\u003cStationDto\u003e\u003e UpdateAsync(int id, StationDto data)\n    {\n        try\n        {\n            StationEntity entity = StationFactory.Create(data);\n            StationEntity existingEntity = await _modelContext.Stations.FirstOrDefaultAsync(s =\u003e s.Id == id);\n            if (existingEntity == null)\n            {\n                return new OperationResultDto\u003cStationDto\u003e(false, (int)HttpStatusCode.NotFound, $\"Station with id: {id} does not exists\", null);\n            }\n\n            // Copy only name, description and positions, create measurements if necessary from MeasurementsManager\n            existingEntity.Name = entity.Name;\n            existingEntity.Description = existingEntity.Description;\n            existingEntity.Latitude = existingEntity.Latitude;\n            existingEntity.Longitude = existingEntity.Longitude;\n            int result = await _modelContext.SaveChangesAsync();\n            if (result \u003e= 0)\n            {\n                return new OperationResultDto\u003cStationDto\u003e(true, (int)HttpStatusCode.OK, null, StationFactory.Create(entity));\n            }\n            return new OperationResultDto\u003cStationDto\u003e(false, (int)HttpStatusCode.InternalServerError, \"An unknown error occurred during station update\", null);\n\n        }\n        catch (Exception e)\n        {\n             return new OperationResultDto\u003cStationDto\u003e(false, (int)HttpStatusCode.InternalServerError, $\"An error occurred during station update: {e.Message}\", null);\n        }\n            \n    }\n\n    private readonly ModelContext _modelContext;\n}\n```\n\n*JUST 2 VERY SIMPLE CLASSES ^^ USING WebApiToolkit*\n\n#### 6.2 GRPC Service example\n\nFor building GRPC service all what we need:\n1. `.proto` file, consider our CodeService example, we have the following GRPC methods:\n```proto\nservice CodeService {\n    rpc ReadOne(OneItemRequest) returns (CodeOperationResult);\n    rpc ReadMany(PageDataRequest) returns (CodePagedDataOperationResult);\n}\n```\n2. `DI` for making service implementation:\n```csharp\nprivate void ConfigureWebServices(IServiceCollection services)\n{\n    services.AddScoped\u003cResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e\u003e(\n        sp =\u003e\n        {\n            return new ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e(sp.GetRequiredService\u003cCodeManager\u003e());\n        });\n}\n```\n3. GRPC Service that derives from generated service and use as a proxy to `ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e`:\n```csharp\npublic class CodeGrpcService : CodeService.CodeServiceBase\n    {\n        public CodeGrpcService(ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e serviceImpl)\n        {\n            _serviceImpl = serviceImpl;\n        }\n\n        public override async Task\u003cCodePagedDataOperationResult\u003e ReadMany(PageDataRequest request, ServerCallContext context)\n        {\n            OperationResultDto\u003cPagedDataDto\u003cCodeDto\u003e\u003e result = await _serviceImpl.ReadAsync(request.Page, request.Size, request.Sort, request.Order,\n                new EmptyAdditionalFilters());\n            context.Status = GrpcErrorCodeHelper.GetGrpcStatus(result.Status, result.Message);\n            CodePagedDataOperationResult response = new CodePagedDataOperationResult()\n            {\n                Success = result.Success,\n                Message = result.Message ?? String.Empty,\n                Status = result.Status,\n            };\n\n            if (result.Data != null)\n            {\n                response.Data = new CodePagedDataResult()\n                {\n                    Page = result.Data.Page,\n                    Pages = result.Data.Pages,\n                    Total = result.Data.Total,\n                    Data = {result.Data.Data.Select(c =\u003e Convert(c))}\n                };\n            }\n\n            return response;\n        }\n\n        public override async Task\u003cCodeOperationResult\u003e ReadOne(OneItemRequest request, ServerCallContext context)\n        {\n            OperationResultDto\u003cCodeDto\u003e result = await _serviceImpl.ReadByIdAsync(request.Id);\n            context.Status = GrpcErrorCodeHelper.GetGrpcStatus(result.Status, result.Message);\n            CodeOperationResult response = new CodeOperationResult()\n            {\n                Success = result.Success,\n                Message = result.Message ?? String.Empty,\n                Status = result.Status,\n                Data = Convert(result.Data)\n            };\n            return response;\n        }\n\n        private Code Convert(CodeDto dto)\n        {\n            if (dto == null)\n                return null;\n            return new Code()\n            {\n                Id = dto.Id,\n                Code_ = dto.Code,\n                Name = dto.Name\n            };\n        }\n\n        private readonly ResourceBasedDataManageableReadOnlyService\u003cCodeDto, CodeEntity, int, EmptyAdditionalFilters\u003e _serviceImpl;\n    }\n```\n\n**Full example how it all works see in `Wissance.WebApiToolkit.TestApp` project**.\n\n### 7. Extending API\n\n#### 7.1 Add new methods to existing controller\nConsider we would like to add method search to our controller:\n```csharp\n[HttpGet]\n[Route(\"api/[controller]/search\")]\npublic async Task\u003cPagedDataDto\u003cBookDto\u003e\u003e\u003e SearchAsync([FromQuery]string query, [FromQuery]int page, [FromQuery]int size)\n{ \n    OperationResultDto\u003cTuple\u003cIList\u003cBookDto\u003e, long\u003e\u003e result = await Manager.GetAsync(page, size, query);\n    if (result == null)\n    {\n        HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;\n    }\n\n    HttpContext.Response.StatusCode = result.Status;\n    return new PagedDataDto\u003cTRes\u003e(pageNumber, result.Data.Item2, GetTotalPages(result.Data.Item2, pageSize), result.Data.Item1);\n}\n```\n\n#### 7.2 Add security to protect you API\n\nWe have [additional project](https://github.com/Wissance/Authorization) to protect `API` with `Keycloak` `OpenId-Connect`.\npass `IHttpContextAccessor` to `Manager` class and check something like this: `ClaimsPrincipal principal = _httpContext.HttpContext.User;`\n\n### 8. Additional materials\n\nYou could see our articles about Toolkit usage:\n* [Medium article about v1.0.x usage]( https://medium.com/@m-ushakov/how-to-reduce-amount-of-code-when-writing-netcore-rest-api-services-28352edcfca6)\n* [Dev.to article about v1.0.x usage]( https://dev.to/wissance/dry-your-web-api-net-core-with-our-toolkit-cbb)\n\n### 9. Contributors\n\n\u003ca href=\"https://github.com/Wissance/WebApiToolkit/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=Wissance/WebApiToolkit\" /\u003e\n\u003c/a\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwissance%2Fwebapitoolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwissance%2Fwebapitoolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwissance%2Fwebapitoolkit/lists"}