{"id":13795116,"url":"https://github.com/saturn72/AnyService","last_synced_at":"2025-05-12T22:30:32.021Z","repository":{"id":106099324,"uuid":"195121342","full_name":"saturn72/AnyService","owner":"saturn72","description":"Create asp.net core services FAST 🐱‍🏍 Made with 💕 with asp.net core","archived":false,"fork":false,"pushed_at":"2023-06-27T23:14:07.000Z","size":32982,"stargazers_count":9,"open_issues_count":11,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-15T03:10:47.391Z","etag":null,"topics":["anyservice","asp","aspnetcore","caching","convention-over-configuration","crud","easy-to-use","fast","generic-programming","microservice-framework","middleware","repository-pattern","restful-api","robust","solid","typesafe","webapi","webservices"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saturn72.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}},"created_at":"2019-07-03T20:20:18.000Z","updated_at":"2023-11-08T18:23:29.000Z","dependencies_parsed_at":null,"dependency_job_id":"026a54ee-5564-4818-8814-a33c6bf8e95b","html_url":"https://github.com/saturn72/AnyService","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturn72%2FAnyService","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturn72%2FAnyService/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturn72%2FAnyService/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saturn72%2FAnyService/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saturn72","download_url":"https://codeload.github.com/saturn72/AnyService/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225157000,"owners_count":17429698,"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","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":["anyservice","asp","aspnetcore","caching","convention-over-configuration","crud","easy-to-use","fast","generic-programming","microservice-framework","middleware","repository-pattern","restful-api","robust","solid","typesafe","webapi","webservices"],"created_at":"2024-08-03T23:00:52.290Z","updated_at":"2024-11-18T09:31:42.763Z","avatar_url":"https://github.com/saturn72.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![anyservice ci](https://github.com/saturn72/AnyService/workflows/anyservice%20ci/badge.svg)\n\n\u003e ## Contributors are welcome\n\u003e Feel free to contact me directly or submit your pull request.\n\u003e Thank you,\n\u003e Roi\n\n# AnyService\n\nCreate asp.net core services FAST 🐱‍🏍\n\nMade with 💕 using asp.net core\n\n`AnyService` is simple to use, yet strong middleware for creating CRUD asp.net core services.\nThe boilerplate code is already in place. All you have to do now is to configure your objects and you are ready to go!\n\n## Goal\n\n`AnyService` main goal is to provide extremely easy and fast way to create CRUD based microservice using asp.net core technology.\n\n## Main Features\n\n- Fully comaptible with `asp.net core` - `AnyService` is framework built on top of `asp.net core`\n- Full CRUD support - generic way to Create, Read, Update and Delete entity\n- [Audity management](https://github.com/saturn72/AnyService/wiki/Advanced-Topics---Audity) - auto manage for creation, update and deletion of an domain's entities\n- Permission management - entity can be restricted as private or public over simple configuration\n- Event sourcing - All `CRUD` events for an entity are publish automatically\n- Full test coverage - code is fully tested so you can rely infrastructure was tested and verified!\n- Caching - to minimize database calls and increase performance\\*\n\n## Samples\nWorking examples of main framework features may be found in [`samples`](https://github.com/saturn72/AnyService/tree/master/samples) directory of this repository\n## Getting Started\n\n\\*Note: fully configured example may be found in `AnyService.SampleApp` project.\n\ninit step - Create new `webapi` project by using `dotnet new webapi --name AnyService.SampleApp` command.\n\n#### 1. Add reference to `AnyService` nuget package\n\n(see [here](https://www.nuget.org/packages/anyservice/))\n\n#### 2. Create a model (entity) that you want to use as resource\n\nThis model is used to perform all `CRUD` operations of the web service. It must implement `IDomainModelBase` to \"glue\" it to `AnyService`'s business logic.\n\n```\npublic class DependentModel : IDomainModelBase //Your model must implement IDomainModelBase\n{\n    public string Id { get; set; }\n    public string Value { get; set; }\n}\n```\n\n#### 3. Add `AnyService` components to `Startup.cs` file\n\nIn `ConfigureServices` method, add the following lines:\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n\n  var entities = new[] { typeof(DependentModel) }; //list all your entities\n  services.AddAnyService(entities);\n  ...\n}\n```\n\n#### 4. Configure caching and persistency functions\n\n**Persistency** is achieved by `IRepository` implementation. Below is `EntityFramework` (`InMemory` provider) example.\n\n1. Add reference to `AnyService.EntityFramework` nuget package (see [here](https://www.nuget.org/packages/anyservice.entityframework))\n2. Add reference to `Microsoft.EntityFrameworkCore.InMemory` nuget packages (see [here](https://docs.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli))\n3. Create `DbContext` (it must have `public DbSet\u003cUserPermissions\u003e UserPermissions { get; set; }` in order to manage user permissions on an entity)\n\n```\npublic class SampleAppDbContext : DbContext\n    {\n        public SampleAppDbContext(DbContextOptions\u003cSampleAppDbContext\u003e options) : base(options)\n        { }\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            modelBuilder.Entity\u003cUserPermissions\u003e(b =\u003e b.Property(u =\u003e u.Id).ValueGeneratedOnAdd());\n            modelBuilder.Entity\u003cDependentModel\u003e(b =\u003e b.Property(u =\u003e u.Id).ValueGeneratedOnAdd());\n            modelBuilder.Entity\u003cDependent2\u003e(b =\u003e b.Property(u =\u003e u.Id).ValueGeneratedOnAdd());\n            modelBuilder.Entity\u003cMultipartSampleModel\u003e(b =\u003e b.Property(u =\u003e u.Id).ValueGeneratedOnAdd());\n        }\n    }\n```\n\n4. Add the following lines to `ConfigureServices` method.\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var dbName = \"anyservice-testsapp-db\";\n    \n  var options = new DbContextOptionsBuilder\u003cSampleAppDbContext\u003e()\n      .UseInMemoryDatabase(databaseName: dbName).Options;\n  services.AddTransient\u003cDbContext\u003e(sp  =\u003e new SampleAppDbContext\u003e(options)); //inject dbContext instance\n  services.AddTransient(typeof(IRepository\u003cModel\u003e), typeof(EfRepository\u003c\u003e); //inject Generic repository\n  services.AddTransient\u003cIFileStoreManager, EfFileStoreManager\u003e(); //injects file storage\n  ...\n}\n```\n\n**Caching** is achieved by `ICacheManager` implementation. Here is `EasyCaching.InMemory` example: : add reference to `AnyService.EasyCaching` nuget (see [here](https://www.nuget.org/packages/anyservice.easycaching)) and to `EasyCaching.InMemory` (see[here](https://www.nuget.org/packages/EasyCaching.InMemory/)) and adding the following lines to `ConfigureServices` method.\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var easycachingconfig = new EasyCachingConfig();\n  services.AddSingleton(easycachingconfig);\n  services.AddEasyCaching(options =\u003e options.UseInMemory(\"default\"));\n  services.AddSingleton\u003cICacheManager, EasyCachingCacheManager\u003e();\n  ...\n}\n```\n\n#### 5. Add Authentication\n\n`AnyService` must have authentication configured (otherwise 401 Unauthorized response is always returned). The easiest way to develop with authentication is to inject mocked instance of `AuthenticationHandler\u003cAuthenticationSchemeOptions\u003e`. Please copy the code below and configure (feel free to add  your own required claims).\n```\npublic class IntegrationAuthenticationHandler : AuthenticationHandler\u003cAuthenticationSchemeOptions\u003e\n    {\n        public const string Schema = \"int-auth-schema\";\n\n        public const string SystemAdmin = \"system-admin-user-id\";\n        public const string User1 = \"user-id-1\";\n        public const string User2 = \"user-id-2\";\n        \n        public IntegrationAuthenticationHandler(IOptionsMonitor\u003cAuthenticationSchemeOptions\u003e options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)\n        {\n        }\n        protected override Task\u003cAuthenticateResult\u003e HandleAuthenticateAsync()\n        {\n            Request.Headers.TryGetValue(\"Authorization\", out StringValues authHeader);\n            var token = authHeader[0].Split(' ', StringSplitOptions.RemoveEmptyEntries)[1];\n            var identity = new ClaimsIdentity(Tokens[token], Schema);\n            var principal = new ClaimsPrincipal(identity);\n            var ticket = new AuthenticationTicket(principal, Schema);\n\n            var result = AuthenticateResult.Success(ticket);\n\n            return Task.FromResult(result);\n        }\n        private static readonly IReadOnlyDictionary\u003cstring, IEnumerable\u003cClaim\u003e\u003e Tokens = new Dictionary\u003cstring, IEnumerable\u003cClaim\u003e\u003e\n        {\n            {\n                SystemAdmin,\n                new[]\n                {\n                     //list all you claims here\n                    new Claim(ClaimTypes.NameIdentifier, SystemAdmin), \n                    new Claim(ClaimTypes.Role, \"system-manager\"),\n                    new Claim(ClaimTypes.Role, \"system-user\")\n                }\n            },\n             {\n               User1,\n                new[]\n                {\n                     //list all you claims here\n                    new Claim(ClaimTypes.NameIdentifier, User1), \n                    new Claim(ClaimTypes.Role, \"catalog-manager\"),\n                    new Claim(ClaimTypes.Role, \"system-user\"),\n                    new Claim(ClaimTypes.Role, \"product-create\"),\n                    new Claim(ClaimTypes.Role, \"product-read\"),\n                    new Claim(ClaimTypes.Role, \"product-update\"),\n                    new Claim(ClaimTypes.Role, \"product-delete\"),\n                }\n            },\n            {\n                User2,\n                new[]\n                {\n                    //list all you claims here\n                    new Claim(ClaimTypes.NameIdentifier, User2), \n                    new Claim(ClaimTypes.Role, \"registered-user\"),\n                    new Claim(ClaimTypes.Role, \"system-user\"),\n                    new Claim(ClaimTypes.Role, \"product-read\"),\n                }\n            },\n        };\n    }\n```\nNow configure the handler in your `Startup.cs`\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  services.AddAuthentication(o =\u003e\n    {\n        o.DefaultScheme = IntegrationAuthenticationHandler.Schema;\n        o.DefaultAuthenticateScheme = IntegrationAuthenticationHandler.Schema;\n    }).AddScheme\u003cAuthenticationSchemeOptions, IntegrationAuthenticationHandler\u003e(IntegrationAuthenticationHandler.Schema, options =\u003e { });\n  ...\n}\n```\nThis method helps you to focus on developing without the need to setup full authentication mechanism. \nTo direct your server to the schema you just added, imcoming `HTTP` request should contain the header `Authorization` with the value `int-auth-schema \u003cuser_name\u003e`.\nSo the `authorization` header with the value `int-auth-schema user-id-1` injects all the claims assosicate with `User1`, and basically authenticate it.\nAnother example of this method can be found in the class `ManagedAuthenticationHandler` of `AnyService`'s `SampleApp` project\n\n#### 6. The final step is to add `AnyService` middlewares to pipeline\n\nAdd the following line to `Configure` method of `Startup.cs`\n\n```\npublic void Configure(IApplicationBuilder app, IHostingEnvironment env)\n{\n  //other middleware before\n  app.UseAnyService();\n  //other middleware after\n}\n```\n\n#### 7. Start your app\n\nNow hit F5 to start your application and perform _CRUD_ operations on `dependent` URI.\n\n## CRUD Flow\n\n### Create\n\nTBD\n\n### Read By Id\n1. Validate user is Authenticated and has permission to read entity\n2. Entity Details is fetched more database\n3. Event with `entity-read` key is fired\n4. Entity details id returned to user \n\n### Read All (with filter)\nRead all api has 2 scenarios to be used. The first is when a user fetches all entities under his account and the second is when a user fetches ALL public entities of the endpoint.\n#### User fetches entities under his account\n1. Validate user is Authenticated\n2. Entity Details are queried from database, based on user permissions and filter\n3. Event with `entity-read` key is fired\n4. Entities details returned to user \n#### User fetches entities under his account (`__public` route)\n1. Validate user is Authenticated and that the entity has public API capability.\n2. All endpoint's entities are queried from database based on filter\n3. Event with `entity-read` key is fired\n4. Entities details returned to user \n\n### Update\n\nTBD\n\n### Delete\n\nTDB\n\n## Authentication\n\nAuthenticating a user is mandatory in `AnyService`. Some of the main reasons are:\n\n- `UserId` is required to manage permissions access over entities\n- A user-based-event is raised whenever `CRUD` operation is performed\n- `Audity` feature data management heavily relies on user's info\n- etc.\n\nAuthentication is added and configured using `asp.net core`'s default authentication configuration mechanism.\n**important:** user's id claim type must be \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\" (defined by `System.Security.Claims.ClaimTypes.NameIdentifier` constant).\n\nFor development purposes, you may configure `AlwaysPassAuthenticationHandler` which, as implied, always approves incoming request's user as authenticated one, with pre-configured id value and claims.\nTo use `AlwaysPassAuthenticationHandler` simply use `AddAlwaysPassAuthentication` extension method.\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var userId = \"some-user-id\";\n  var claims = new []\n  {\n    new KeyValuePair\u003cstring, string\u003e(\"claim1-key\", \"claim1-value\"),\n    new KeyValuePair\u003cstring, string\u003e(\"claim1-key\", \"claim1-value\"),\n    new KeyValuePair\u003cstring, string\u003e(\"claim1-key\", \"claim1-value\"),\n  };\n  services.AddAlwaysPassAuthentication(userId, claims); // This\n  ...\n}\n```\n\n## Response Mapping\n\nBy Default `ServiceResponse` is returned from business logic layer (`CrudService\u003c\u003e`) to the entity controller(`GenericController`). The controller maps the returned `ServiceResponse` to `IActionResult` object and return to caller.\n2 types of response mappers exist in `AnyService`:\n 1. `DataOnlyServiceResponseMapper` (the default mapper) that returns `ServiceResponse`'s data only (recomended for production)\n 2. `DefaultServiceResponseMapper` which returns all the `ServiceResponse`'s details (recomended for debugging and response monitoring)\n \n\nTo modify the `IServiceResponseMapper` used for an entity, set the value of `AnyService.EntityConfigRecord.ResponseMapperType` to the required type.\n\n## Authorization (Permission Management)\n\nAuthorization has 2 aspects addressed by `AnyService`:\n### 1. Configuring user's access to specific resource (URI)\n#### _Can user perform CRUD operation on a given URI?_\n\nThis snippet shows how to set role based authorization on controller and its methods\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var config = new AnyServiceConfig\n              {\n                EntityConfigRecords = new[]{\n                    new EntityConfigRecord\n                    {\n                        Type = typeof(Product),\n                        Authorization = new AuthorizationInfo\n                        {\n                            ControllerAuthorizationNode = new AuthorizationNode //only users with role \"admin\" can access the controller\n                            {\n                                Roles = new []{\"admin\"},\n                            },\n                            PostAuthorizeNode = new AuthorizationNode //only users with role \"creator\" can access the controller\n                            {\n                                Roles = new []{\"creator\"},\n                            },\n                            GetAuthorizeNode = new AuthorizationNode //only users with role \"reader\" can access the controller\n                            {\n                                Roles = new []{\"reader\"},\n                            },\n                            PutAuthorizeNode = new AuthorizationNode //only users with role \"updater\" can access the controller\n                            {\n                                Roles = new []{\"updater\"},\n                            },\n                            DeleteAuthorizeNode = new AuthorizationNode //only users with role \"deleter\" can access the controller\n                            {\n                                Roles = new []{\"deleter\"},\n                            }\n                        }\n                    }\n\n                }\n            };\n    services.AddAnyService(config);\n    ...\n}          \n```\n### 2. Manage CRUD Permissions on an authorized resource (URI)\n#### _Given that a user has permission to perform CRUD operation on given URI (entity), can the user perform read/update/delete operations on specific URI's entity?_\n\nOnce user has permission to create an entity on a resource, `AnyService` takes care of managing permissions over the created entity.\n`AnyService` supports 2 permission management patterns:\n\n1. All CRUD operations can be performed by the creator and only by the creator.\n   This means the entities created by a priliged user are fully private and are **managed and accessed** by this user (entity's creator) and only by this user.\n\n2. Create, Update and Delete operations can be performed by the creator and only by the creator while Read operation can be performed by all users.\n   This means the entities created by a priliged user are **managed** privatly by this user (entity's creator) and only by this user, but are publicly **accessed** by all users.\n\n\n### Disable/Modify Default Authorization Behavior\n#### Disable Default Authorization \n`AnyService` uses the `DefaultAuthorizationMiddleware` to verify user has the required `Claim`s to access entity's `URI` (controller and/or controller's method).\nTo disable Permission Management, set the value of `AnyServiceOptions.UseAuthorizationMiddleware` to `false`.\n\n#### Permission Management\nAnother aspect of authrization management is managing user **permissions** to perform `CRUD`'s on `URI` or an entity. \nThere are several options to modify permission management:\n- To disable Permission Management, set the value of `ManageEntityPermissions` of `AnyServiceOptions` class to `false`.\n- To modify default permission logic you may:\n - Create your own implementation of `IPermissionManager`\n - Create your own permission middleware\n\n## Model Validation\n\nWhe `POST` and `PUT` Http methods are used, the data within request's body is called model.\nBy default `AnyService` does not perform any model validation, and leaves it to the client side.\nIf you want to perform model validation, you should implement `ICrudValidator\u003cT\u003e` generic interface.\n\nTBD - add example here\n\n## Audity\n\nTBD\n\n## CRUD Events\n\n`AnyService` raises event whenever CRUD operation is preformed.\n\nTBD - show how to consume event\nTBD - show how to modify event key\n\n## `AnyServiceConfig` - Customize default values\n\nYou are able to customize all default values of `AnyService`.\nMost of `AnyService` properties can be modified by creating instance of `AnyServiceConfig` and set relevant properties. Then send it to `AddAnyService` extension method.\n\nIn the example below we modify entity route.\nBy default route is set to entity's name (using `Type.Name`).\nWe use `HeatMapInfo` entity which by default gets the route `/heatmapinfo` for its `CRUD` operations. By setting the `EntityConfigRecord.Route` property to `/heatmap`, `CRUD` operations are performed in `/heatmap` route.\n\n```\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var anyServiceConfig = new AnyServiceConfig\n  {\n    EntityConfigRecords = new[]\n    {\n      new EntityConfigRecord\n      {\n        Type =typeof(HeatMapInfo),\n        Route = \"/heatmap\"\n      }\n    },\n  };\n  ...\n}\n```\n\n## Combine Custom Controllers with `AnyEntity` Middleware\n\nIf you want to utilize `AnyService` automatic setup and/or require custom controller, it is required to configure your entity in `AnyServiceConfig` (simply by adding it to the `entityConfigRecords` collection).\nTo add custom controller (in case you want to override the configured route, for instance), set the property `ControllerType` with the custom controller type you want to use.\n\n```\n[Route(\"api/my-great-route\")]\npublic class MyCustomController : ControllerBase\n{\n  ... all HTTP method handlers\n}\n\npublic void ConfigureServices(IServiceCollection services)\n{\n  ...\n  var anyServiceConfig = new AnyServiceConfig\n  {\n    EntityConfigRecords = new[]\n    {\n      new EntityConfigRecord\n      {\n        Type =typeof(Value),\n        Route = \"/api/my-great-route\",\n        ControllerType = typeof(MyCustomController),\n      }\n    },\n  };\n  ...\n}\n```\n\n## Reserve Paths\n\nAny resource that starts with `__`\n\n## Contact Me\n\nFeel free to write me directly to roi@saturn72.com 📧\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaturn72%2FAnyService","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaturn72%2FAnyService","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaturn72%2FAnyService/lists"}