From dded3f18b6f1e9dd3a8c55c375a6d1c2db03932b Mon Sep 17 00:00:00 2001 From: chullybun Date: Sun, 10 Mar 2024 16:45:31 -0700 Subject: [PATCH 1/5] tags and endpoints --- CHANGELOG.md | 4 + Common.targets | 2 +- docs/Entity-CodeGeneration-Config.md | 1 + docs/Entity-Entity-Config.md | 1 + docs/Entity-Operation-Config.md | 1 + .../Cdr.Banking.Api/Cdr.Banking.Api.csproj | 2 +- .../Generated/AccountController.cs | 2 + .../Cdr.Banking.Business.csproj | 6 +- .../Properties/launchSettings.json | 2 +- .../Cdr.Banking.CodeGen/entity.beef-5.yaml | 4 +- .../Cdr.Banking.Common.csproj | 2 +- .../Cdr.Banking.Test/Cdr.Banking.Test.csproj | 2 +- .../Demo/Beef.Demo.Api/Beef.Demo.Api.csproj | 2 +- .../Controllers/Generated/PersonController.cs | 2 + .../Beef.Demo.Business.csproj | 16 +- .../Beef.Demo.Business/Data/ZippoAgent.cs | 5 +- .../Demo/Beef.Demo.CodeGen/entity.beef-5.yaml | 4 +- .../Beef.Demo.Common/Beef.Demo.Common.csproj | 2 +- .../Demo/Beef.Demo.Test/Beef.Demo.Test.csproj | 4 +- samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj | 2 +- .../My.Hr.Business/My.Hr.Business.csproj | 8 +- .../My.Hr/My.Hr.Common/My.Hr.Common.csproj | 2 +- samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj | 4 +- .../MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj | 4 +- .../MyEf.Hr.Business/MyEf.Hr.Business.csproj | 8 +- .../MyEf.Hr.Common/MyEf.Hr.Common.csproj | 2 +- .../GlobalUsings.cs | 1 + .../MyEf.Hr.Security.Subscriptions.csproj | 7 +- .../OktaHttpClient.cs | 2 +- .../MyEf.Hr.Security.Subscriptions/Startup.cs | 2 +- .../appsettings.json | 4 +- .../MyEf.Hr.Security.Test.csproj | 2 +- .../EmployeeTerminatedSubscriberTest.cs | 3 +- .../MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj | 4 +- .../MyEf.Hr/docs/10-Service-Bus-Subscribe.md | 10 +- .../content/.template.config/template.json | 4 +- .../Beef.CodeGen.Core.csproj | 2 +- tools/Beef.CodeGen.Core/CodeGenConsole.cs | 194 ++++------------- tools/Beef.CodeGen.Core/CodeGenerator.cs | 56 ++--- tools/Beef.CodeGen.Core/CommandType.cs | 7 +- .../Config/Entity/CodeGenConfig.cs | 9 + .../Config/Entity/EntityConfig.cs | 24 +++ .../Config/Entity/OperationConfig.cs | 30 +++ .../DirectoryCountStatistics.cs | 195 ++++++++++++++++++ tools/Beef.CodeGen.Core/EndPointStatistics.cs | 161 +++++++++++++++ tools/Beef.CodeGen.Core/ExtendedHelp.txt | 10 +- tools/Beef.CodeGen.Core/README.md | 12 +- .../Schema/entity.beef-5.json | 23 +++ .../Templates/EntityWebApiController_cs.hbs | 6 + .../ReferenceDataWebApiController_cs.hbs | 6 + .../Beef.Database.Core.csproj | 2 +- .../Beef.Database.MySql.csproj | 2 +- .../Beef.Database.Postgres.csproj | 2 +- .../Beef.Database.SqlServer.csproj | 2 +- tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj | 2 +- 55 files changed, 602 insertions(+), 276 deletions(-) create mode 100644 tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs create mode 100644 tools/Beef.CodeGen.Core/EndPointStatistics.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb52065d..c7b8a7987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Represents the **NuGet** versions. +## v5.12.0 +- *Enhancement:* Added `WebApiTags` code-generation property to enable the specification of `Tags` for the Web API Controller class. +- *Enhancement:* Added `dotnet run endpoints` option to report all configured endpoints providing a means to audit the generated API surface. + ## v5.11.0 - *Enhancement:* Added `dotnet new beef ... --services AzFunction` to enable the templating of a corresponding `Company.AppName.Services` project as an Azure Functions project. This will provide an example of leveraging the shared `Company.AppName.Business` logic and consuming the published events using an `EventSubscriberOrchestrator`. - *Enhancement:* The `DatabaseMapper` (stored procedures) code-generation logic has been updated to leverage the new extended `DatabaseMapperEx`. This avoids the existing reflection and expression compilation, using explicit code to perform the mapping. Can offer up to 40%+ improvement in some scenarios. Where existing behavior is required then set YAML `databaseMapperEx: false` in the `entity.beef-5.yaml` file (root and/or entity within hierarchy). diff --git a/Common.targets b/Common.targets index 68f8b9822..557526c07 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 5.11.0 + 5.12.0 preview Avanade Avanade diff --git a/docs/Entity-CodeGeneration-Config.md b/docs/Entity-CodeGeneration-Config.md index 8cd06cbbd..8cfdcd56b 100644 --- a/docs/Entity-CodeGeneration-Config.md +++ b/docs/Entity-CodeGeneration-Config.md @@ -104,6 +104,7 @@ Property | Description `webApiAuthorize` | The authorize attribute value to be used for the corresponding entity Web API controller; generally either `Authorize` or `AllowAnonymous`.
† This can be overridden within the `Entity`(s) and/or their corresponding `Operation`(s). `webApiAutoLocation` | Indicates whether the HTTP Response Location Header route (`Operation.WebApiLocation`) is automatically inferred.
† This will automatically set the `Operation.WebApiLocation` for an `Operation` named `Create` where there is a corresponding named `Get`. This can be overridden within the `Entity`(s). **`webApiRoutePrefix`** | The base (prefix) `URI` prepended to all `Operation.WebApiRoute` values. +`webApiTags` | The list of tags to add for the generated `WebApi`.
† This can be overridden within the `Entity`(s) and/or their corresponding `Operation`(s).
diff --git a/docs/Entity-Entity-Config.md b/docs/Entity-Entity-Config.md index a56879ec5..074e783f9 100644 --- a/docs/Entity-Entity-Config.md +++ b/docs/Entity-Entity-Config.md @@ -164,6 +164,7 @@ Property | Description `webApiAutoLocation` | Indicates whether the HTTP Response Location Header route (`Operation.WebApiLocation`) is automatically inferred.
† This will automatically set the `Operation.WebApiLocation` for an `Operation` named `Create` where there is a corresponding named `Get`. This is defaulted from the `CodeGen.WebApiAutoLocation`. `webApiConcurrency` | Indicates whether the Web API is responsible for managing (simulating) concurrency via auto-generated ETag.
† This provides an alternative where the underlying data source does not natively support optimistic concurrency (native support should always be leveraged as a priority). Where the `Operation.Type` is `Update` or `Patch`, the request ETag will be matched against the response for a corresponding `Get` operation to verify no changes have been made prior to updating. For this to function correctly the .NET response Type for the `Get` must be the same as that returned from the corresponding `Create`, `Update` and `Patch` (where applicable) as the generated ETag is a SHA256 hash of the resulting JSON. This defaults the `Operation.WebApiConcurrency`. `webApiGetOperation` | The corresponding `Get` method name (in the `XxxManager`) where the `Operation.Type` is `Update` and `SimulateConcurrency` is `true`.
† Defaults to `Get`. Specify either just the method name (e.g. `OperationName`) or, interface and method name (e.g. `IXxxManager.OperationName`) to be invoked where in a different `YyyManager.OperationName`. +`webApiTags` | The list of tags to add for the generated `WebApi` controller.
diff --git a/docs/Entity-Operation-Config.md b/docs/Entity-Operation-Config.md index eb7444884..9c4d979c1 100644 --- a/docs/Entity-Operation-Config.md +++ b/docs/Entity-Operation-Config.md @@ -115,6 +115,7 @@ Property | Description `webApiUpdateOperation` | The corresponding `Update` method name (in the `XxxManager`) where the `Operation.Type` is `Patch`.
† Defaults to `Update`. Specify either just the method name (e.g. `OperationName`) or, interface and method name (e.g. `IXxxManager.OperationName`) to be invoked where in a different `YyyManager.OperationName`. `webApiProduces` | The value(s) for the optional `[Produces()]` attribute for the operation within the Web Api Controller for the Swagger/OpenAPI documentation. `webApiProducesResponseType` | The `[ProducesResponseType()]` attribute `typeof` for the operation within the Web Api Controller for the Swagger/OpenAPI documentation.
† Defaults to the _Common_ type. A value of `None`, `none` or `` will ensure no type is emitted. +`webApiTags` | The list of tags to add for the generated `WebApi` operation.
† Overrides the `Entity.WebApiTags`; unless, if the first tag value is a `^` then this indicates that the `Entity.WebApiTags` are to be included (inherited) as a replacement. Otherwise, defaults to `Entity.WebApiTags`.
diff --git a/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj b/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj index 03fbb87b4..b65375374 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj @@ -5,7 +5,7 @@ true
- + diff --git a/samples/Cdr.Banking/Cdr.Banking.Api/Controllers/Generated/AccountController.cs b/samples/Cdr.Banking/Cdr.Banking.Api/Controllers/Generated/AccountController.cs index ad46fa9da..26ba28383 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Api/Controllers/Generated/AccountController.cs +++ b/samples/Cdr.Banking/Cdr.Banking.Api/Controllers/Generated/AccountController.cs @@ -7,6 +7,7 @@ namespace Cdr.Banking.Api.Controllers; /// /// Provides the Web API functionality. /// +[Tags("Banking")] [Produces(System.Net.Mime.MediaTypeNames.Application.Json)] public partial class AccountController : ControllerBase { @@ -30,6 +31,7 @@ public AccountController(WebApi webApi, IAccountManager manager) /// The Open Status (see ). /// Indicates whether Is Owned. /// The + [Tags("Banking", "Accounts")] [HttpGet("api/v1/banking/accounts")] [Paging] [ProducesResponseType(typeof(Common.Entities.AccountCollection), (int)HttpStatusCode.OK)] diff --git a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj index 46063119d..13e0ba51e 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj @@ -11,8 +11,8 @@ - - - + + +
\ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.CodeGen/Properties/launchSettings.json b/samples/Cdr.Banking/Cdr.Banking.CodeGen/Properties/launchSettings.json index 8e0fc5e64..2338319c9 100644 --- a/samples/Cdr.Banking/Cdr.Banking.CodeGen/Properties/launchSettings.json +++ b/samples/Cdr.Banking/Cdr.Banking.CodeGen/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Cdr.Banking.CodeGen": { "commandName": "Project", - "commandLineArgs": "all" + "commandLineArgs": "endpoints" } } } \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.CodeGen/entity.beef-5.yaml b/samples/Cdr.Banking/Cdr.Banking.CodeGen/entity.beef-5.yaml index 8d21e1c4c..6d3bc1c33 100644 --- a/samples/Cdr.Banking/Cdr.Banking.CodeGen/entity.beef-5.yaml +++ b/samples/Cdr.Banking/Cdr.Banking.CodeGen/entity.beef-5.yaml @@ -4,7 +4,7 @@ entities: # API route prefixed defined. # Auto-implementing data access using Cosmos with Container 'Account', leveraging auto-mapping to a 'Model.Account'. # -- { name: Account, text: Account, collection: true, collectionResult: true, webApiRoutePrefix: api/v1/banking/accounts, autoImplement: Cosmos, cosmosModel: Model.Account, cosmosContainerId: Accounts, +- { name: Account, text: Account, collection: true, collectionResult: true, webApiRoutePrefix: api/v1/banking/accounts, autoImplement: Cosmos, cosmosModel: Model.Account, cosmosContainerId: Accounts, webApiTags: [ Banking ], properties: [ # Convention of Id property within entity. # Reference as accountId otherwise (ArgumentName and JsonName). @@ -32,7 +32,7 @@ entities: # Supports paging. # Data access will be auto-implemented for Cosmos as defined for the entity. # - { name: GetAccounts, text: Get all accounts, type: GetColl, paging: true, + { name: GetAccounts, text: Get all accounts, type: GetColl, paging: true, webApiTags: [ ^, Accounts ], parameters: [ { name: Args, type: AccountArgs, validator: AccountArgsValidator } ] diff --git a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj index 77cd7d9c3..94262139c 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj index ce66ab116..e3b2dfd5f 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj @@ -39,7 +39,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj b/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj index f3f3a9b68..37ecb0a26 100644 --- a/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj +++ b/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj @@ -12,7 +12,7 @@ - + diff --git a/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs b/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs index d38627b5d..9c3853a7c 100644 --- a/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs +++ b/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs @@ -10,6 +10,7 @@ namespace Beef.Demo.Api.Controllers; /// /// Provides the Web API functionality. /// +[Tags("person", "bananas")] [AllowAnonymous] [Produces(System.Net.Mime.MediaTypeNames.Application.Json)] public partial class PersonController : ControllerBase @@ -107,6 +108,7 @@ public Task Patch(Guid id) /// Gets the that contains the items that match the selection criteria. /// /// The + [Tags("apples", "oranges")] [HttpGet("api/v1/persons/all")] [Paging] [ProducesResponseType(typeof(Common.Entities.PersonCollection), (int)HttpStatusCode.OK)] diff --git a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj index 0daa5a514..4c9da22d0 100644 --- a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj +++ b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + diff --git a/samples/Demo/Beef.Demo.Business/Data/ZippoAgent.cs b/samples/Demo/Beef.Demo.Business/Data/ZippoAgent.cs index 40794050c..f8190d5fe 100644 --- a/samples/Demo/Beef.Demo.Business/Data/ZippoAgent.cs +++ b/samples/Demo/Beef.Demo.Business/Data/ZippoAgent.cs @@ -8,9 +8,6 @@ namespace Beef.Demo.Business.Data public class ZippoAgent : TypedMappedHttpClientCore { public ZippoAgent(HttpClient client, IMapper mapper, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, SettingsBase settings, ILogger logger) - : base(client, mapper, jsonSerializer, executionContext, settings, logger) - { - DefaultOptions.WithRetry(); - } + : base(client, mapper, jsonSerializer, executionContext, settings, logger) { } } } \ No newline at end of file diff --git a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml index 0aa709513..6d565a26a 100644 --- a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml +++ b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml @@ -11,7 +11,7 @@ managerCleanUp: true withResult: false preprocessorDirectives: true entities: -- { name: Person, text: Person, collection: true, collectionResult: true, grpc: true, validator: PersonValidator, identifierGenerator: true, webApiAuthorize: AllowAnonymous, webApiRoutePrefix: api/v1/persons, webApiAutoLocation: true, create: true, delete: true, autoImplement: Database, databaseSchema: Demo, entityFrameworkModel: EfModel.Person, dataCtorParams: [ 'Microsoft.Extensions.Logging.ILogger^Logger', Common.Agents.IPersonAgent ], managerExtensions: true, dataSvcExtensions: true, dataExtensions: true, eventTransaction: true, TestCodeGen: true, TestExtra: 'Unknown-Config', +- { name: Person, text: Person, collection: true, collectionResult: true, grpc: true, validator: PersonValidator, identifierGenerator: true, webApiAuthorize: AllowAnonymous, webApiRoutePrefix: api/v1/persons, webApiAutoLocation: true, create: true, delete: true, autoImplement: Database, databaseSchema: Demo, entityFrameworkModel: EfModel.Person, dataCtorParams: [ 'Microsoft.Extensions.Logging.ILogger^Logger', Common.Agents.IPersonAgent ], managerExtensions: true, dataSvcExtensions: true, dataExtensions: true, eventTransaction: true, TestCodeGen: true, TestExtra: 'Unknown-Config', webApiTags: [ person, bananas ], properties: [ { name: Id, text: '{{Person}} identifier', type: Guid, grpcFieldNo: 1, primaryKey: true, dataName: PersonId, annotation1: '[System.Xml.Serialization.XmlElement("Id")]' }, { name: FirstName, type: string, grpcFieldNo: 2 }, @@ -31,7 +31,7 @@ entities: { name: Update, type: Update, validator: PersonValidator, primaryKey: true, webApiRoute: '{id}', dataSvcTransaction: true, managerExtensions: true, dataSvcExtensions: false, dataExtensions: false }, { name: UpdateWithRollback, type: Update, validator: PersonValidator, primaryKey: true, webApiRoute: 'withRollback/{id}', dataSvcTransaction: true, databaseStoredProc: spPersonUpdate }, { name: Patch, type: Patch, primaryKey: true, webApiRoute: '{id}' }, - { name: GetAll, type: GetColl, paging: true, webApiRoute: all }, + { name: GetAll, type: GetColl, paging: true, webApiRoute: all, webApiTags: [ apples, oranges ] }, { name: GetAll2, type: GetColl, paging: false, webApiRoute: allnopaging, databaseStoredProc: spPersonGetAll }, { name: GetByArgs, type: GetColl, paging: true, parameters: [ diff --git a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj index 4615579aa..69d15aa71 100644 --- a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj +++ b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj @@ -7,7 +7,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj index 04163c240..b7fcf6828 100644 --- a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj +++ b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj @@ -52,7 +52,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -62,7 +62,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj index 5c0493b18..6a08258ee 100644 --- a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj +++ b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj @@ -5,7 +5,7 @@ true - + diff --git a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj index 05a1dc085..869840215 100644 --- a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj +++ b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj index 7f6875b0a..fbdc74ba1 100644 --- a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj +++ b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj index 3154a74ac..83f3b5a53 100644 --- a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj +++ b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj @@ -32,7 +32,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -42,7 +42,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj index 4a080ecf1..99563145c 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj @@ -6,8 +6,8 @@ True - - + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj index 0d8d6dcf1..63019ffb5 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj index 7f6875b0a..fbdc74ba1 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/GlobalUsings.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/GlobalUsings.cs index 3e6764345..329856fc4 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/GlobalUsings.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/GlobalUsings.cs @@ -15,6 +15,7 @@ global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; +global using Polly; global using System; global using System.Collections.Generic; global using System.Linq; diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj index 567489937..f6be60e76 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj @@ -15,13 +15,14 @@ - - + + - + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs index 81af8d2c2..9fe74f48c 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/OktaHttpClient.cs @@ -6,7 +6,7 @@ public OktaHttpClient(HttpClient client, SecuritySettings settings, IJsonSeriali : base(client, jsonSerializer, executionContext, settings, logger) { Client.BaseAddress = new Uri(settings.OktaHttpClientBaseUri); - DefaultOptions.WithRetry().EnsureSuccess().ThrowKnownException(); + DefaultOptions.EnsureSuccess().ThrowKnownException(); } /// diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Startup.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Startup.cs index b25ad840b..a9baa38b9 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Startup.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/Startup.cs @@ -30,6 +30,6 @@ public override void ConfigureServices(IServiceCollection services) { o.EventDataDeserializationErrorHandling = ErrorHandling.HandleBySubscriber; }) - .AddTypedHttpClient("OktaApi"); + .AddTypedHttpClient("OktaApi").AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(retryCount: 3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))); ; } } \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json index e3f01ec0e..edf7ef943 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/appsettings.json @@ -1,8 +1,6 @@ { "OktaHttpClient": { - "BaseUri": "https://dev-1234.okta.com", - "HttpRetryCount": 2, - "HttpTimeoutSeconds": 120 + "BaseUri": "https://dev-1234.okta.com" }, "ServiceBusOrchestratedSubscriber": { "AbandonOnTransient": true, diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj index de08bd7c3..3f25b7447 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj @@ -27,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs b/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs index 9ded8855a..e89531f44 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/Subscribers/EmployeeTerminatedSubscriberTest.cs @@ -102,12 +102,13 @@ public void OktaForbidden_Retry() public void OktaServiceUnavailable_Retry() { var mcf = MockHttpClientFactory.Create(); - var mc = mcf.CreateClient("OktaApi", "https://test-okta/"); + var mc = mcf.CreateClient("OktaApi", "https://test-okta/").WithConfigurations(); mc.Request(HttpMethod.Get, "/api/v1/users?search=profile.email eq \"bob@email.com\"").Respond.WithSequence(x => { x.Respond().With(HttpStatusCode.ServiceUnavailable); x.Respond().With(HttpStatusCode.ServiceUnavailable); x.Respond().With(HttpStatusCode.ServiceUnavailable); + x.Respond().With(HttpStatusCode.ServiceUnavailable); }); using var test = FunctionTester.Create(); diff --git a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj index c32de368a..6d9462c5d 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj @@ -32,7 +32,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -42,7 +42,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/docs/10-Service-Bus-Subscribe.md b/samples/MyEf.Hr/docs/10-Service-Bus-Subscribe.md index 9724b293c..17ec4dbaf 100644 --- a/samples/MyEf.Hr/docs/10-Service-Bus-Subscribe.md +++ b/samples/MyEf.Hr/docs/10-Service-Bus-Subscribe.md @@ -81,7 +81,7 @@ Then complete the following house cleaning tasks within the newly created projec Update project dependencies as follows. -1. Add the `CoreEx.Azure` and `CoreEx.Validation` NuGet packages as dependencies. +1. Add the `CoreEx.Azure`, `CoreEx.Validation` and `Microsoft.Extensions.Http.Polly` NuGet packages as dependencies. 2. Add `MyHr.Ef.Common` as a project reference dependency (within a real implemenation the `*.Common` assemblies should be published as internal packages for reuse across domains; that is largely their purpose).
@@ -216,17 +216,13 @@ Create a new corresponding `appsettings.json` file, then copy in the following c Setting | Description -|- `OktaHttpClient:BaseUri` | The base [`Uri`](https://learn.microsoft.com/en-us/dotnet/api/system.uri) for the external OKTA API. -`OktaHttpClient:HttpRetryCount`* | Specifies the number of times the HTTP request should be retried when a transient error occurs. -`OktaHttpClient:HttpTimeoutSeconds`* | Specifies the maximum number of seconds for the HTTP request to complete before timing out. `ServiceBusOrchestratedSubscriber.AbandonOnTransient`* | Indicates that the message should be explicitly abandoned where transient error occurs. `ServiceBusOrchestratedSubscriber.RetryDelay`* | The timespan to delay (multiplied by delivery count) after each transient error is encountered; continues to lock message. ``` json { "OktaHttpClient": { - "BaseUri": "https://dev-1234.okta.com", - "HttpRetryCount": 2, - "HttpTimeoutSeconds": 120 + "BaseUri": "https://dev-1234.okta.com" }, "ServiceBusOrchestratedSubscriber": { "AbandonOnTransient": true, @@ -260,7 +256,7 @@ public class OktaHttpClient : TypedHttpClientBase : base(client, jsonSerializer, executionContext, settings, logger) { Client.BaseAddress = new Uri(settings.OktaHttpClientBaseUri); - DefaultOptions.WithRetry().EnsureSuccess().ThrowKnownException(); + DefaultOptions.EnsureSuccess().ThrowKnownException(); } /// diff --git a/templates/Beef.Template.Solution/content/.template.config/template.json b/templates/Beef.Template.Solution/content/.template.config/template.json index e06eaf517..719a1bc29 100644 --- a/templates/Beef.Template.Solution/content/.template.config/template.json +++ b/templates/Beef.Template.Solution/content/.template.config/template.json @@ -85,7 +85,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "3.13.0" + "value": "3.14.0" }, "replaces": "CoreExVersion" }, @@ -93,7 +93,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "5.10.0" + "value": "5.12.0" }, "replaces": "BeefVersion" }, diff --git a/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj b/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj index 30b489310..8758a8bb6 100644 --- a/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj +++ b/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj @@ -31,7 +31,7 @@ - + diff --git a/tools/Beef.CodeGen.Core/CodeGenConsole.cs b/tools/Beef.CodeGen.Core/CodeGenConsole.cs index 2d7940951..a0e76414b 100644 --- a/tools/Beef.CodeGen.Core/CodeGenConsole.cs +++ b/tools/Beef.CodeGen.Core/CodeGenConsole.cs @@ -267,6 +267,9 @@ protected override async Task OnCodeGenerationAsync() if (cmd.HasFlag(CommandType.Count)) ExecuteCount(); + if (cmd.HasFlag(CommandType.EndPoints)) + await ExecuteEndpointsAsync(exedir, company, appName).ConfigureAwait(false); + if (count > 1) { Args.Logger?.LogInformation("{Content}", new string('-', 80)); @@ -364,7 +367,7 @@ private void ExecuteCount() CountDirectoryAndItsChildren(dcs); var columnLength = Math.Max(dcs.TotalLineCount.ToString().Length, 5); - dcs.Write(Args.Logger!, columnLength, 0); + dcs.Write(Args.Logger!, columnLength, 0, dcs.Directory.Parent is null ? 0 : dcs.Directory.Parent.FullName.Length + 1); Args.Logger?.LogInformation("{Content}", string.Empty); Args.Logger?.LogInformation("{Content}", $"{AppName} Complete. [{sw.Elapsed.TotalMilliseconds}ms]"); @@ -403,171 +406,56 @@ private static void CountDirectoryAndItsChildren(DirectoryCountStatistics dcs) } /// - /// Provides count statistics. + /// Executes the endpoints. /// - internal class DirectoryCountStatistics + private async Task ExecuteEndpointsAsync(string? exedir, string company, string appName) { - /// - /// Initializes a new instance of the class. - /// - public DirectoryCountStatistics(DirectoryInfo directory, string[] exclude) - { - Directory = directory; - if (directory.Name == "Generated") - IsGenerated = true; - - Exclude = exclude ?? []; - } + var sw = Stopwatch.StartNew(); - /// - /// Gets the . - /// - public DirectoryInfo Directory { get; } - - /// - /// Gets the directory/path names to exclude. - /// - public string[] Exclude { get; private set; } - - /// - /// Gets the file count. - /// - public int FileCount { get; private set; } - - /// - /// Gets the total file count including children. - /// - public int TotalFileCount => FileCount + Children.Sum(x => x.TotalFileCount); - - /// - /// Gets the generated file count. - /// - public int GeneratedFileCount { get; private set; } - - /// - /// Gets the total generated file count including children. - /// - public int GeneratedTotalFileCount => GeneratedFileCount + Children.Sum(x => x.GeneratedTotalFileCount); - - /// - /// Gets the line count; - /// - public int LineCount { get; private set; } - - /// - /// Gets the total line count including children. - /// - public int TotalLineCount => LineCount + Children.Sum(x => x.TotalLineCount); - - /// - /// Gets the generated line count. - /// - public int GeneratedLineCount { get; private set; } - - /// - /// Gets the total line count including children. - /// - public int GeneratedTotalLineCount => GeneratedLineCount + Children.Sum(x => x.GeneratedTotalLineCount); - - /// - /// Indicates whether the contents of the directory are generated. - /// - public bool IsGenerated { get; private set; } - - /// - /// Gets the child instances. - /// - public List Children { get; } = []; - - /// - /// Increments the file count. - /// - public void IncrementFileCount() + EndPointStatistics endpoints; + if (IsEntitySupported) { - FileCount++; - if (IsGenerated) - GeneratedFileCount++; - } + var root = await LoadConfigAsync(CodeGenFileManager.GetConfigFilename(exedir!, CommandType.Entity, company, appName)).ConfigureAwait(false); + endpoints = new EndPointStatistics(); + endpoints.AddEntityEndPoints(root); + endpoints.WriteTabulated(Args.Logger!, root.CodeGenArgs!.ConfigFileName!); - /// - /// Increments the line count. - /// - public void IncrementLineCount() - { - LineCount++; - if (IsGenerated) - GeneratedLineCount++; + if (IsRefDataSupported) + { + Args.Logger!.LogInformation("{Content}", new string('-', 80)); + Args.Logger!.LogInformation("{Content}", string.Empty); + } } - /// - /// Adds a child instance. - /// - public DirectoryCountStatistics AddChildDirectory(DirectoryInfo di) + if (IsRefDataSupported) { - var dcs = new DirectoryCountStatistics(di, Exclude); - if (IsGenerated) - dcs.IsGenerated = true; - - Children.Add(dcs); - return dcs; + var root = await LoadConfigAsync(CodeGenFileManager.GetConfigFilename(exedir!, CommandType.RefData, company, appName)).ConfigureAwait(false); + endpoints = new EndPointStatistics(); + endpoints.AddRefDataEndPoints(root); + endpoints.WriteTabulated(Args.Logger!, root.CodeGenArgs!.ConfigFileName!); } - /// - /// Write the count statistics. - /// - /// The . - /// The maximum column length. - /// The indent size to show hierarchy. - public void Write(ILogger logger, int columnLength, int indent = 0) - { - if (indent == 0) - { - var hdrAll = string.Format("{0, " + columnLength + "}", "All"); - var hdrGen = string.Format("{0, " + (columnLength + 5) + "}", "Generated"); - var hdrfiles = string.Format("{0, " + columnLength + "}", "Files"); - var hdrlines = string.Format("{0, " + columnLength + "}", "Lines"); - - logger.LogInformation("{Content}", $"{hdrAll} | {hdrAll} | {hdrGen} | {hdrGen} | Path/"); - logger.LogInformation("{Content}", $"{hdrfiles} | {hdrlines} | {hdrfiles} Perc | {hdrlines} Perc | Directory"); - logger.LogInformation("{Content}", new string('-', 75)); - } - - var totfiles = string.Format("{0, " + columnLength + "}", TotalFileCount); - var totlines = string.Format("{0, " + columnLength + "}", TotalLineCount); - var totgenFiles = string.Format("{0, " + columnLength + "}", GeneratedTotalFileCount); - var totgenFilesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalFileCount == 0 ? 0 : Math.Round((double)GeneratedTotalFileCount / (double)TotalFileCount * 100.0, 0)); - var totgenLines = string.Format("{0, " + columnLength + "}", GeneratedTotalLineCount); - var totgenLinesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalLineCount == 0 ? 0 : Math.Round((double)GeneratedTotalLineCount / (double)TotalLineCount * 100.0, 0)); + if (!IsEntitySupported && !IsRefDataSupported) + Args.Logger?.LogInformation("{Content}", "No EndPoints have been configured."); - logger.LogInformation("{Content}", $"{totfiles} {totlines} {totgenFiles} {totgenFilesPerc}% {totgenLines} {totgenLinesPerc}% {new string(' ', indent * 2)}{Directory.FullName}"); - - foreach (var dcs in Children) - { - if (dcs.TotalFileCount > 0) - dcs.Write(logger, columnLength, indent + 1); - } - } + Args.Logger?.LogInformation("{Content}", string.Empty); + Args.Logger?.LogInformation("{Content}", $"{AppName} Complete. [{sw.Elapsed.TotalMilliseconds}ms]"); + Args.Logger?.LogInformation("{Content}", string.Empty); + } - /// - /// Cleans (deletes) all directories. - /// - /// The - public void Clean(ILogger logger) - { - // Where generated then delete. - if (IsGenerated) - { - logger.LogWarning(" Deleted: {Directory} [{FileCount} files]", Directory.FullName, TotalFileCount); - Directory.Delete(true); - return; - } + /// + /// Load the configuration. + /// + private async Task LoadConfigAsync(string filename) + { + // Load the configuration. + var args = new CodeGeneratorArgs(); + args.CopyFrom(Args); + args.ScriptFileName ??= _entityScript; + args.ConfigFileName ??= filename; - // Where not generated then clean children. - foreach (var dcs in Children) - { - dcs.Clean(logger); - } - } + var cg = await OnRamp.CodeGenerator.CreateAsync(args).ConfigureAwait(false); + return (Config.Entity.CodeGenConfig)await cg.LoadConfigAsync().ConfigureAwait(false); } } } \ No newline at end of file diff --git a/tools/Beef.CodeGen.Core/CodeGenerator.cs b/tools/Beef.CodeGen.Core/CodeGenerator.cs index a55155343..6f6c4d5cc 100644 --- a/tools/Beef.CodeGen.Core/CodeGenerator.cs +++ b/tools/Beef.CodeGen.Core/CodeGenerator.cs @@ -1,69 +1,39 @@ -using OnRamp.Scripts; -using OnRamp; -using Microsoft.Extensions.Logging; +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + using Beef.CodeGen.Config.Entity; -using System.Linq; -using System.Collections.Generic; +using OnRamp; +using OnRamp.Scripts; namespace Beef.CodeGen { /// /// Code-generation orchestrator. /// - internal class CodeGenerator : OnRamp.CodeGenerator + /// The . + /// The . + internal class CodeGenerator(ICodeGeneratorArgs args, CodeGenScript scripts) : OnRamp.CodeGenerator(args, scripts) { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - public CodeGenerator(ICodeGeneratorArgs args, CodeGenScript scripts) : base(args, scripts) { } - /// protected override void OnAfterScript(CodeGenScriptItem script, CodeGenStatistics statistics) { if (statistics.RootConfig is CodeGenConfig cgc) { - var list = new List<(string Name, string Route, string Method, string Status, string? Auth)>(); - if (script.Template!.StartsWith("EntityWebApiController_cs")) { - var entities = cgc.Root!.Entities!.Where(x => (!x.ExcludeWebApi.HasValue || !x.ExcludeWebApi.Value) && x.Operations!.Count > 0).AsEnumerable(); - foreach (var e in entities) - { - foreach (var o in e.Operations!.Where(x => !x.ExcludeWebApi.HasValue || !x.ExcludeWebApi.Value)) - { - list.Add(($"{e.Name}.{o.Name}", o.AgentWebApiRoute, o.WebApiMethod![4..], o.WebApiStatus!, o.WebApiAuthorize ?? e.WebApiAuthorize ?? "")); - } - } + var endpoints = new EndPointStatistics(); + endpoints.AddEntityEndPoints(cgc); + endpoints.WriteInline(cgc.CodeGenArgs!.Logger!); - LogEndpoints(script, list); } else if (script.Template!.StartsWith("ReferenceDataWebApiController_cs")) { - list.Add(("ReferenceData.GetNamed", cgc.RefDataWebApiRoute!, "GET", "OK", cgc.WebApiAuthorize)); - foreach (var e in cgc.Root!.RefDataEntities!) - { - list.Add(($"ReferenceData.{e.Name}GetAll", e.WebApiRoutePrefix!, "GET", "OK", e.WebApiAuthorize ?? "")); - } - - LogEndpoints(script, list); + var endpoints = new EndPointStatistics(); + endpoints.AddRefDataEndPoints(cgc); + endpoints.WriteInline(cgc.CodeGenArgs!.Logger!); } } base.OnAfterScript(script, statistics); } - - /// - /// Log endpoints. - /// - private static void LogEndpoints(CodeGenScriptItem script, List<(string Name, string Route, string Method, string Status, string? Auth)> list) - { - script.Root?.CodeGenArgs?.Logger?.LogInformation(" {Content}", $"Endpoints:{(list.Count == 0 ? " none" : $" ({list.Count})")}"); - foreach (var (Name, Route, Method, Status, Auth) in list.OrderBy(x => x.Route).ThenBy(x => x.Method)) - { - script.Root?.CodeGenArgs?.Logger?.LogInformation(" {Content}", $"{Route} {Method.ToUpperInvariant()} [{Status}] {(string.IsNullOrEmpty(Auth) ? "" : $"({Auth}) ")}-> {Name}"); - } - } } } \ No newline at end of file diff --git a/tools/Beef.CodeGen.Core/CommandType.cs b/tools/Beef.CodeGen.Core/CommandType.cs index 1277de9e5..1fd9671bb 100644 --- a/tools/Beef.CodeGen.Core/CommandType.cs +++ b/tools/Beef.CodeGen.Core/CommandType.cs @@ -43,6 +43,11 @@ public enum CommandType /// /// Counts the files and lines of code for child directories distinguising between 'Generated' and non-generated. /// - Count = 4096 + Count = 4096, + + /// + /// Reports all the endpoints. + /// + EndPoints = 8192 } } \ No newline at end of file diff --git a/tools/Beef.CodeGen.Core/Config/Entity/CodeGenConfig.cs b/tools/Beef.CodeGen.Core/Config/Entity/CodeGenConfig.cs index 5fee0500c..b40ce25b8 100644 --- a/tools/Beef.CodeGen.Core/Config/Entity/CodeGenConfig.cs +++ b/tools/Beef.CodeGen.Core/Config/Entity/CodeGenConfig.cs @@ -183,6 +183,14 @@ public class CodeGenConfig : ConfigRootBase [CodeGenProperty("WebApi", Title = "The base (prefix) `URI` prepended to all `Operation.WebApiRoute` values.", IsImportant = true)] public string? WebApiRoutePrefix { get; set; } + /// + /// The list of tags to add for the generated `WebApi`. + /// + [JsonPropertyName("webApiTags")] + [CodeGenPropertyCollection("WebApi", Title = "The list of tags to add for the generated `WebApi`.", + Description = "This can be overridden within the `Entity`(s) and/or their corresponding `Operation`(s).")] + public List? WebApiTags { get; set; } + #endregion #region Manager @@ -615,6 +623,7 @@ protected override async Task PrepareAsync() RefDataTextDataName = DefaultWhereNull(RefDataTextDataName, () => "Text"); RefDataIsActiveDataName = DefaultWhereNull(RefDataIsActiveDataName, () => "IsActive"); RefDataSortOrderDataName = DefaultWhereNull(RefDataSortOrderDataName, () => "SortOrder"); + WebApiTags ??= []; if (!string.IsNullOrEmpty(WebApiRoutePrefix)) RefDataWebApiRoute = string.IsNullOrEmpty(RefDataWebApiRoute) ? WebApiRoutePrefix : diff --git a/tools/Beef.CodeGen.Core/Config/Entity/EntityConfig.cs b/tools/Beef.CodeGen.Core/Config/Entity/EntityConfig.cs index 347a1734e..f2d6c6183 100644 --- a/tools/Beef.CodeGen.Core/Config/Entity/EntityConfig.cs +++ b/tools/Beef.CodeGen.Core/Config/Entity/EntityConfig.cs @@ -844,6 +844,13 @@ public class EntityConfig : ConfigBase Description = "Defaults to `Get`. Specify either just the method name (e.g. `OperationName`) or, interface and method name (e.g. `IXxxManager.OperationName`) to be invoked where in a different `YyyManager.OperationName`.")] public string? WebApiGetOperation { get; set; } + /// + /// The list of tags to add for the generated `WebApi`. + /// + [JsonPropertyName("webApiTags")] + [CodeGenPropertyCollection("WebApi", Title = "The list of tags to add for the generated `WebApi` controller.")] + public List? WebApiTags { get; set; } + #endregion #region Model @@ -1465,6 +1472,23 @@ protected override async Task PrepareAsync() HttpAgentReturnModel = DefaultWhereNull(HttpAgentReturnModel, () => HttpAgentModel); + // Ensure the WebApiTags are set correctly. + if (WebApiTags is not null && WebApiTags.Any()) + { + var list = new List(); + foreach (var tag in WebApiTags) + { + if (tag == "^") + list.AddRange(Parent!.WebApiTags!); + else + list.Add(tag); + } + + WebApiTags = list; + } + else + WebApiTags ??= []; + InferInherits(); Consts = await PrepareCollectionAsync(Consts).ConfigureAwait(false); await PreparePropertiesAsync().ConfigureAwait(false); diff --git a/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs b/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs index 6d6db1193..6553aacf2 100644 --- a/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs +++ b/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs @@ -525,6 +525,14 @@ public class OperationConfig : ConfigBase Description = "Defaults to the _Common_ type. A value of `None`, `none` or `` will ensure no type is emitted.")] public string? WebApiProducesResponseType { get; set; } + /// + /// The list of tags to add for the generated `WebApi`. + /// + [JsonPropertyName("webApiTags")] + [CodeGenPropertyCollection("WebApi", Title = "The list of tags to add for the generated `WebApi` operation.", + Description = "Overrides the `Entity.WebApiTags`; unless, if the first tag value is a `^` then this indicates that the `Entity.WebApiTags` are to be included (inherited) as a replacement. Otherwise, defaults to `Entity.WebApiTags`.")] + public List? WebApiTags { get; set; } + #endregion #region Auth @@ -991,6 +999,11 @@ public class OperationEvent /// public string? WebApiProducesContentType => WebApiProduces is not null && WebApiProduces.Count > 0 ? string.Join(", ", WebApiProduces.Select(x => $"\"{x}\"")) : null; + /// + /// Gets the Tags for reporting. + /// + public List ReportTags => WebApiTags!.Count >= 1 ? WebApiTags : Parent!.WebApiTags!; + /// /// /// @@ -1185,6 +1198,23 @@ protected override async Task PrepareAsync() ExcludeWebApiAgent = DefaultWhereNull(ExcludeWebApiAgent, () => CompareValue(ExcludeAll, true)); ExcludeGrpcAgent = DefaultWhereNull(ExcludeGrpcAgent, () => CompareValue(ExcludeAll, true)); + // Ensure the WebApiTags are set correctly. + if (WebApiTags is not null && WebApiTags.Any()) + { + var list = new List(); + foreach (var tag in WebApiTags) + { + if (tag == "^") + list.AddRange(Parent!.WebApiTags!); + else + list.Add(tag); + } + + WebApiTags = list; + } + else + WebApiTags ??= []; + if (Type == "Patch") ExcludeIData = ExcludeData = ExcludeIDataSvc = ExcludeDataSvc = ExcludeIManager = ExcludeManager = true; diff --git a/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs b/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs new file mode 100644 index 000000000..ee74180f1 --- /dev/null +++ b/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs @@ -0,0 +1,195 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Beef.CodeGen +{ + /// + /// Provides count statistics. + /// + internal class DirectoryCountStatistics + { + /// + /// Initializes a new instance of the class. + /// + public DirectoryCountStatistics(DirectoryInfo directory, string[] exclude) + { + Directory = directory; + if (directory.Name == "Generated") + IsGenerated = true; + + Exclude = exclude ?? []; + } + + /// + /// Gets the . + /// + public DirectoryInfo Directory { get; } + + /// + /// Gets the directory/path names to exclude. + /// + public string[] Exclude { get; private set; } + + /// + /// Gets the file count. + /// + public int FileCount { get; private set; } + + /// + /// Gets the total file count including children. + /// + public int TotalFileCount => FileCount + Children.Sum(x => x.TotalFileCount); + + /// + /// Gets the generated file count. + /// + public int GeneratedFileCount { get; private set; } + + /// + /// Gets the total generated file count including children. + /// + public int GeneratedTotalFileCount => GeneratedFileCount + Children.Sum(x => x.GeneratedTotalFileCount); + + /// + /// Gets the line count; + /// + public int LineCount { get; private set; } + + /// + /// Gets the total line count including children. + /// + public int TotalLineCount => LineCount + Children.Sum(x => x.TotalLineCount); + + /// + /// Gets the generated line count. + /// + public int GeneratedLineCount { get; private set; } + + /// + /// Gets the total line count including children. + /// + public int GeneratedTotalLineCount => GeneratedLineCount + Children.Sum(x => x.GeneratedTotalLineCount); + + /// + /// Indicates whether the contents of the directory are generated. + /// + public bool IsGenerated { get; private set; } + + /// + /// Gets the child instances. + /// + public List Children { get; } = []; + + /// + /// Increments the file count. + /// + public void IncrementFileCount() + { + FileCount++; + if (IsGenerated) + GeneratedFileCount++; + } + + /// + /// Increments the line count. + /// + public void IncrementLineCount() + { + LineCount++; + if (IsGenerated) + GeneratedLineCount++; + } + + /// + /// Adds a child instance. + /// + public DirectoryCountStatistics AddChildDirectory(DirectoryInfo di) + { + var dcs = new DirectoryCountStatistics(di, Exclude); + if (IsGenerated) + dcs.IsGenerated = true; + + Children.Add(dcs); + return dcs; + } + + /// + /// Write the count statistics. + /// + /// The . + /// The maximum column length. + /// The indent size to show hierarchy. + /// The number of characters to remove from directory + public void Write(ILogger logger, int columnLength, int indent, int remove) + { + if (indent == 0) + { + var hdrAll = string.Format("{0, " + columnLength + "}", "All"); + var hdrGen = string.Format("{0, " + (columnLength + 5) + "}", "Generated"); + var hdrfiles = string.Format("{0, " + columnLength + "}", "Files"); + var hdrlines = string.Format("{0, " + columnLength + "}", "Lines"); + + logger.LogInformation("{Content}", $"{hdrAll} | {hdrAll} | {hdrGen} | {hdrGen} | Path/"); + logger.LogInformation("{Content}", $"{hdrfiles} | {hdrlines} | {hdrfiles} Perc | {hdrlines} Perc | Directory"); + + int maxLength = 0; + DirectoryDepthAnalysis(this, 0, ref maxLength); + logger.LogInformation("{Content}", new string('-', (columnLength * 4) + 22 + maxLength - remove)); + } + + var totfiles = string.Format("{0, " + columnLength + "}", TotalFileCount); + var totlines = string.Format("{0, " + columnLength + "}", TotalLineCount); + var totgenFiles = string.Format("{0, " + columnLength + "}", GeneratedTotalFileCount); + var totgenFilesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalFileCount == 0 ? 0 : Math.Round((double)GeneratedTotalFileCount / (double)TotalFileCount * 100.0, 0)); + var totgenLines = string.Format("{0, " + columnLength + "}", GeneratedTotalLineCount); + var totgenLinesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalLineCount == 0 ? 0 : Math.Round((double)GeneratedTotalLineCount / (double)TotalLineCount * 100.0, 0)); + + logger.LogInformation("{Content}", $"{totfiles} {totlines} {totgenFiles} {totgenFilesPerc}% {totgenLines} {totgenLinesPerc}% {new string(' ', indent * 2)}{Directory.FullName[remove..]}"); + + foreach (var dcs in Children) + { + if (dcs.TotalFileCount > 0) + dcs.Write(logger, columnLength, indent + 1, remove); + } + } + + /// + /// Performs directory depth and length analysis. + /// + private static void DirectoryDepthAnalysis(DirectoryCountStatistics dcs, int depth, ref int maxLength) + { + maxLength = Math.Max(maxLength, dcs.Directory.FullName.Length + (depth * 2)); + + foreach (var d in dcs.Children) + { + DirectoryDepthAnalysis(d, depth + 1, ref maxLength); + } + } + + /// + /// Cleans (deletes) all directories. + /// + /// The + public void Clean(ILogger logger) + { + // Where generated then delete. + if (IsGenerated) + { + logger.LogWarning(" Deleted: {Directory} [{FileCount} files]", Directory.FullName, TotalFileCount); + Directory.Delete(true); + return; + } + + // Where not generated then clean children. + foreach (var dcs in Children) + { + dcs.Clean(logger); + } + } + } +} diff --git a/tools/Beef.CodeGen.Core/EndPointStatistics.cs b/tools/Beef.CodeGen.Core/EndPointStatistics.cs new file mode 100644 index 000000000..ebafd5bb5 --- /dev/null +++ b/tools/Beef.CodeGen.Core/EndPointStatistics.cs @@ -0,0 +1,161 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef + +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Beef.CodeGen +{ + /// + /// Provides the ability to capture and report endpoint statistics. + /// + internal class EndPointStatistics() + { + private readonly List _endPoints = []; + + /// + /// Adds the endpoints. + /// + public void AddEntityEndPoints(Config.Entity.CodeGenConfig root) + { + var entities = root.Entities!.Where(x => (!x.ExcludeWebApi.HasValue || !x.ExcludeWebApi.Value) && x.Operations!.Count > 0).AsEnumerable(); + foreach (var e in entities) + { + foreach (var o in e.Operations!.Where(x => !x.ExcludeWebApi.HasValue || !x.ExcludeWebApi.Value)) + { + Add(new EndPointData + { + Route = o.AgentWebApiRoute, + Method = o.WebApiMethod![4..], + Status = o.WebApiStatus!, + Auth = o.WebApiAuthorize ?? e.WebApiAuthorize, + Tags = string.Join(", ", o.WebApiTags!.Count > 0 ? o.WebApiTags : e.WebApiTags!), + Name = $"{e.Name}.{o.Name}" + }); + } + } + } + + /// + /// Adds the endpoints. + /// + public void AddRefDataEndPoints(Config.Entity.CodeGenConfig root) + { + // Add the 'GetNamed' global endpoint. + Add(new EndPointData + { + Route = root.RefDataWebApiRoute, + Method = "GET", + Status = "OK", + Auth = root.WebApiAuthorize, + Tags = null, + Name = "ReferenceData.GetNamed" + }); + + // Add the configured refdata entities. + var entities = root.RefDataEntities!; + foreach (var e in entities) + { + Add(new EndPointData + { + Route = e.WebApiRoutePrefix!, + Method = "GET", + Status = "OK", + Auth = e.WebApiAuthorize, + Tags = string.Join(", ", e.WebApiTags!), + Name = $"ReferenceData.{e.Name}GetAll" + }); + } + } + + /// + /// Add the to the collection. + /// + /// The . + public void Add(EndPointData data) => _endPoints.Add(data); + + /// + /// Write as tabulated data to the . + /// + /// The . + /// The title. + public void WriteTabulated(ILogger logger, string title) + { + // Writer header. + logger.LogInformation("{Content}", $"Config: {title}"); + logger.LogInformation("{Content}", $"Count: {(_endPoints.Count == 0 ? "none" : _endPoints.Count)}"); + logger.LogInformation("{Content}", string.Empty); + + if (_endPoints.Count == 0) + return; + + WriteTabulated(logger, false); + + logger.LogInformation("{Content}", string.Empty); + } + + /// + /// Writes the tabulated data. + /// + private void WriteTabulated(ILogger logger, bool indent) + { + // Write table header. + var routeLength = Math.Max(5, _endPoints.Max(x => x.Route?.Length ?? 0)); + var methodLength = Math.Max(6, _endPoints.Max(x => x.Method?.Length ?? 0)); + var statusLength = Math.Max(6, _endPoints.Max(x => x.Status?.Length ?? 0)); + var authLength = Math.Max(4, _endPoints.Max(x => x.Auth?.Length ?? 0)); + var tagsLength = Math.Max(4, _endPoints.Max(x => x.Tags?.Length ?? 0)); + + var hdrRoute = string.Format("{0, -" + routeLength + "}", "Route"); + var hdrMethod = string.Format("{0, -" + methodLength + "}", "Method"); + var hdrStatus = string.Format("{0, -" + statusLength + "}", "Status"); + var hdrAuth = string.Format("{0, -" + authLength + "}", "Auth"); + var hdrTags = string.Format("{0, -" + tagsLength + "}", "Tags"); + + if (_endPoints.Count == 0) + return; + + var prefix = indent ? new string(' ', 6) : string.Empty; + + logger.LogInformation("{Content}", $"{prefix}{hdrRoute} | {hdrMethod} | {hdrStatus} | {hdrAuth} | {hdrTags} | Name"); + logger.LogInformation("{Content}", $"{prefix}{new string('-', routeLength + methodLength + statusLength + authLength + tagsLength + 15 + Math.Max(4, _endPoints.Max(x => x.Name?.Length ?? 0)))}"); + + // Write the data. + foreach (var ep in _endPoints.OrderBy(x => x.Route).ThenBy(x => x.Method).ThenBy(x => x.Name)) + { + var route = string.Format("{0, -" + routeLength + "}", ep.Route); + var method = string.Format("{0, -" + methodLength + "}", ep.Method); + var status = string.Format("{0, -" + statusLength + "}", ep.Status); + var auth = string.Format("{0, -" + authLength + "}", ep.Auth); + var tags = string.Format("{0, -" + tagsLength + "}", ep.Tags); + + logger.LogInformation("{Content}", $"{prefix}{route} | {method.ToUpperInvariant()} | {status} | {auth} | {tags} | {ep.Name}"); + } + } + + /// + /// Writes as inline tabulated to the . + /// + /// The . + public void WriteInline(ILogger logger) + { + logger.LogInformation("{Content}", $" Endpoints:{(_endPoints.Count == 0 ? " none" : $" ({_endPoints.Count})")}"); + if (_endPoints.Count > 0) + WriteTabulated(logger, true); + } + } + + /// + /// Represents the endpoint data for reporting. + /// + internal class EndPointData + { + public string? Route { get; set; } + public string? Method { get; set; } + public string? Status { get; set; } + public string? Auth { get; set; } + public string? Tags { get; set; } + public string? Name { get; set; } + } +} diff --git a/tools/Beef.CodeGen.Core/ExtendedHelp.txt b/tools/Beef.CodeGen.Core/ExtendedHelp.txt index 955941be4..f1b259da4 100644 --- a/tools/Beef.CodeGen.Core/ExtendedHelp.txt +++ b/tools/Beef.CodeGen.Core/ExtendedHelp.txt @@ -1,7 +1,9 @@ Extended commands and argument(s): - clean Cleans (removes) all related directories named 'Generated'. - - Use --param exclude=name[,name] to exclude named directory(s) from the clean. + clean Cleans (removes) all related directories named 'Generated'. + - Use --param exclude=name[,name] to exclude named directory(s) from the clean. - count Counts and reports the number of files and lines (All and Generated) within all related directories. - - Use --param exclude=name[,name] to exclude named directory(s) from the count. + count Counts and reports the number of files and lines (All and Generated) within all related directories. + - Use --param exclude=name[,name] to exclude named directory(s) from the count. + + endpoints Lists (audits) the code-generated endpoints and related configuration. diff --git a/tools/Beef.CodeGen.Core/README.md b/tools/Beef.CodeGen.Core/README.md index 12e327db7..951dd93f2 100644 --- a/tools/Beef.CodeGen.Core/README.md +++ b/tools/Beef.CodeGen.Core/README.md @@ -166,7 +166,7 @@ Usage: Beef.CodeGen.Core [options] Arguments: command Execution command type. - Allowed values are: Entity, Database, RefData, DataModel, All, Clean, Count. + Allowed values are: Entity, Database, RefData, DataModel, All, Clean, Count, EndPoints. Options: -?|-h|--help Show help information. @@ -181,11 +181,13 @@ Options: -sim|--simulation Indicates whether the code-generation is a simulation (i.e. does not create/update any artefacts). Extended commands and argument(s): - clean Cleans (removes) all related directories named 'Generated'. - - Use --param exclude=name[,name] to exclude named directory(s) from the clean. + clean Cleans (removes) all related directories named 'Generated'. + - Use --param exclude=name[,name] to exclude named directory(s) from the clean. - count Counts and reports the number of files and lines (All and Generated) within all related directories. - - Use --param exclude=name[,name] to exclude named directory(s) from the count. + count Counts and reports the number of files and lines (All and Generated) within all related directories. + - Use --param exclude=name[,name] to exclude named directory(s) from the count. + + endpoints Lists (audits) the code-generated endpoints and related configuration. ```
diff --git a/tools/Beef.CodeGen.Core/Schema/entity.beef-5.json b/tools/Beef.CodeGen.Core/Schema/entity.beef-5.json index eb9e7a253..f8cff828c 100644 --- a/tools/Beef.CodeGen.Core/Schema/entity.beef-5.json +++ b/tools/Beef.CodeGen.Core/Schema/entity.beef-5.json @@ -105,6 +105,14 @@ "type": "string", "title": "The base (prefix) \u0060URI\u0060 prepended to all \u0060Operation.WebApiRoute\u0060 values." }, + "webApiTags": { + "type": "array", + "title": "The list of tags to add for the generated \u0060WebApi\u0060.", + "description": "This can be overridden within the \u0060Entity\u0060(s) and/or their corresponding \u0060Operation\u0060(s).", + "items": { + "type": "string" + } + }, "managerCleanUp": { "type": "boolean", "title": "Indicates whether a \u0060Cleaner.Cleanup\u0060 is performed for the operation parameters within the Manager-layer.", @@ -868,6 +876,13 @@ "title": "The corresponding \u0060Get\u0060 method name (in the \u0060XxxManager\u0060) where the \u0060Operation.Type\u0060 is \u0060Update\u0060 and \u0060SimulateConcurrency\u0060 is \u0060true\u0060.", "description": "Defaults to \u0060Get\u0060. Specify either just the method name (e.g. \u0060OperationName\u0060) or, interface and method name (e.g. \u0060IXxxManager.OperationName\u0060) to be invoked where in a different \u0060YyyManager.OperationName\u0060." }, + "webApiTags": { + "type": "array", + "title": "The list of tags to add for the generated \u0060WebApi\u0060 controller.", + "items": { + "type": "string" + } + }, "dataModel": { "type": "boolean", "title": "Indicates whether a data \u0060model\u0060 version of the entity should also be generated (output to \u0060.\\Business\\Data\\Model\u0060).", @@ -1589,6 +1604,14 @@ "title": "The \u0060[ProducesResponseType()]\u0060 attribute \u0060typeof\u0060 for the operation within the Web Api Controller for the Swagger/OpenAPI documentation.", "description": "Defaults to the _Common_ type. A value of \u0060None\u0060, \u0060none\u0060 or \u0060\u0060 will ensure no type is emitted." }, + "webApiTags": { + "type": "array", + "title": "The list of tags to add for the generated \u0060WebApi\u0060 operation.", + "description": "Overrides the \u0060Entity.WebApiTags\u0060; unless, if the first tag value is a \u0060^\u0060 then this indicates that the \u0060Entity.WebApiTags\u0060 are to be included (inherited) as a replacement. Otherwise, defaults to \u0060Entity.WebApiTags\u0060.", + "items": { + "type": "string" + } + }, "authPermission": { "type": "string", "title": "The permission used by the \u0060ExecutionContext.IsAuthorized(AuthPermission)\u0060 to determine whether the user is authorized." diff --git a/tools/Beef.CodeGen.Core/Templates/EntityWebApiController_cs.hbs b/tools/Beef.CodeGen.Core/Templates/EntityWebApiController_cs.hbs index 65990a69a..fb8db59dc 100644 --- a/tools/Beef.CodeGen.Core/Templates/EntityWebApiController_cs.hbs +++ b/tools/Beef.CodeGen.Core/Templates/EntityWebApiController_cs.hbs @@ -13,6 +13,9 @@ namespace {{Root.NamespaceApi}}.Controllers; /// /// Provides the {{{EntityNameSeeComments}}} Web API functionality. /// +{{#ifge WebApiTags.Count 1}} +[Tags({{#each WebApiTags}}"{{.}}"{{#unless @last}}, {{/unless}}{{/each}})] +{{/ifge}} {{#ifval WebApiAuthorize}} [{{{WebApiAuthorize}}}] {{/ifval}} @@ -64,6 +67,9 @@ public partial class {{Name}}Controller : ControllerBase {{#if HasReturnValue}} /// {{{WebApiReturnText}}} {{/if}} + {{#ifge WebApiTags.Count 1}} + [Tags({{#each WebApiTags}}"{{.}}"{{#unless @last}}, {{/unless}}{{/each}})] + {{/ifge}} {{#ifval WebApiAuthorize}} [{{{WebApiAuthorize}}}] {{/ifval}} diff --git a/tools/Beef.CodeGen.Core/Templates/ReferenceDataWebApiController_cs.hbs b/tools/Beef.CodeGen.Core/Templates/ReferenceDataWebApiController_cs.hbs index 8a89c6b08..f22c5a2d4 100644 --- a/tools/Beef.CodeGen.Core/Templates/ReferenceDataWebApiController_cs.hbs +++ b/tools/Beef.CodeGen.Core/Templates/ReferenceDataWebApiController_cs.hbs @@ -14,6 +14,9 @@ namespace {{Root.NamespaceApi}}.Controllers; /// /// Provides the ReferenceData Web API functionality. /// +{{#ifge WebApiTags.Count 1}} +[Tags({{#each WebApiTags}}"{{.}}"{{#unless @last}}, {{/unless}}{{/each}})] +{{/ifge}} {{#ifval WebApiAuthorize}} [{{{WebApiAuthorize}}}] {{/ifval}} @@ -37,6 +40,9 @@ public partial class ReferenceDataController : ControllerBase /// The reference data code list. /// The reference data text (including wildcards). /// A {{RefDataQualifiedEntityName}} collection. + {{#ifge WebApiTags.Count 1}} + [Tags({{#each WebApiTags}}"{{.}}"{{#unless @last}}, {{/unless}}{{/each}})] + {{/ifge}} {{#ifval WebApiAuthorize}} [{{{WebApiAuthorize}}}] {{/ifval}} diff --git a/tools/Beef.Database.Core/Beef.Database.Core.csproj b/tools/Beef.Database.Core/Beef.Database.Core.csproj index d7c6bb578..0ef187afb 100644 --- a/tools/Beef.Database.Core/Beef.Database.Core.csproj +++ b/tools/Beef.Database.Core/Beef.Database.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj index daa3124d8..f325d5f9e 100644 --- a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj +++ b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj b/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj index 4eefdf41e..2ef0bf7eb 100644 --- a/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj +++ b/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj index bd4463ae2..346d7c69c 100644 --- a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj +++ b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj index 4837873bd..6259bba52 100644 --- a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj +++ b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj @@ -8,7 +8,7 @@ - + From 458df67e6ef21cf3fcce40984a06b884777ddb86 Mon Sep 17 00:00:00 2001 From: chullybun Date: Mon, 11 Mar 2024 10:00:28 -0700 Subject: [PATCH 2/5] Dir Count output tweak. --- tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs b/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs index ee74180f1..141f21298 100644 --- a/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs +++ b/tools/Beef.CodeGen.Core/DirectoryCountStatistics.cs @@ -149,7 +149,13 @@ public void Write(ILogger logger, int columnLength, int indent, int remove) var totgenLines = string.Format("{0, " + columnLength + "}", GeneratedTotalLineCount); var totgenLinesPerc = string.Format("{0, " + 3 + "}", GeneratedTotalLineCount == 0 ? 0 : Math.Round((double)GeneratedTotalLineCount / (double)TotalLineCount * 100.0, 0)); - logger.LogInformation("{Content}", $"{totfiles} {totlines} {totgenFiles} {totgenFilesPerc}% {totgenLines} {totgenLinesPerc}% {new string(' ', indent * 2)}{Directory.FullName[remove..]}"); + string spacer; + if (indent == 0) + spacer = string.Empty; + else + spacer = new string(' ', (indent - 1) * 2) + "- "; + + logger.LogInformation("{Content}", $"{totfiles} {totlines} {totgenFiles} {totgenFilesPerc}% {totgenLines} {totgenLinesPerc}% {spacer}{Directory.FullName[remove..]}"); foreach (var dcs in Children) { From 9c654790aaa205f1a29c21a7b3cfcc12c38c83de Mon Sep 17 00:00:00 2001 From: chullybun Date: Tue, 19 Mar 2024 08:42:40 -0700 Subject: [PATCH 3/5] beef-5.yaml merge --- CHANGELOG.md | 1 + .../Cdr.Banking.Api/Cdr.Banking.Api.csproj | 2 +- .../Cdr.Banking.Business.csproj | 6 +- .../Cdr.Banking.Common.csproj | 2 +- .../Cdr.Banking.Test/Cdr.Banking.Test.csproj | 4 +- .../Demo/Beef.Demo.Api/Beef.Demo.Api.csproj | 2 +- .../Beef.Demo.Business.csproj | 16 +-- .../Generated/ServiceCollectionExtensions.cs | 4 +- .../Generated/ServiceCollectionExtensions.cs | 4 +- .../Generated/ServiceCollectionExtensions.cs | 4 +- .../contact.entity.beef-5.yaml | 25 +++++ .../Demo/Beef.Demo.CodeGen/entity.beef-5.yaml | 25 ----- .../Beef.Demo.Common/Beef.Demo.Common.csproj | 2 +- .../Demo/Beef.Demo.Test/Beef.Demo.Test.csproj | 6 +- samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj | 2 +- .../My.Hr.Business/My.Hr.Business.csproj | 8 +- .../My.Hr/My.Hr.Common/My.Hr.Common.csproj | 2 +- samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj | 6 +- .../MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj | 4 +- .../MyEf.Hr.Business/MyEf.Hr.Business.csproj | 8 +- .../MyEf.Hr.Common/MyEf.Hr.Common.csproj | 2 +- .../MyEf.Hr.Security.Subscriptions.csproj | 4 +- .../MyEf.Hr.Security.Test.csproj | 6 +- .../MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj | 6 +- .../content/.template.config/template.json | 2 +- .../Beef.CodeGen.Core.csproj | 2 +- tools/Beef.CodeGen.Core/CodeGenConsole.cs | 21 +++- tools/Beef.CodeGen.Core/CodeGenerator.cs | 103 ++++++++++++++++++ .../Config/Database/CodeGenConfig.cs | 6 +- tools/Beef.CodeGen.Core/EndPointStatistics.cs | 13 +-- tools/Beef.CodeGen.Core/README.md | 17 ++- .../Beef.Database.Core.csproj | 2 +- .../Beef.Database.MySql.csproj | 2 +- .../Beef.Database.Postgres.csproj | 2 +- .../Beef.Database.SqlServer.csproj | 2 +- tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj | 2 +- 36 files changed, 225 insertions(+), 100 deletions(-) create mode 100644 samples/Demo/Beef.Demo.CodeGen/contact.entity.beef-5.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b8a7987..aed653879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Represents the **NuGet** versions. ## v5.12.0 - *Enhancement:* Added `WebApiTags` code-generation property to enable the specification of `Tags` for the Web API Controller class. - *Enhancement:* Added `dotnet run endpoints` option to report all configured endpoints providing a means to audit the generated API surface. +- *Enhancement:* Secondary (additional) configuration files `*.entity.beef-5.yaml`, `*.refdata.beef-5.yaml`, `*.datamodel.beef-5.yaml` can be added to the project (including within subfolders) that will be automatically merged into the corresponding primary `entity.beef-5.yaml`, `refdata.beef-5.yaml`, `datamodel.beef-5.yaml` files respectively. The secondary files only support a single root `entities` property/node that merges into the primary's equivalent. This allows the configuration to be broken up logically to minimize challenges related to overall file size and complexity, and minimize potential developer merge conflicts, etc. ## v5.11.0 - *Enhancement:* Added `dotnet new beef ... --services AzFunction` to enable the templating of a corresponding `Company.AppName.Services` project as an Azure Functions project. This will provide an example of leveraging the shared `Company.AppName.Business` logic and consuming the published events using an `EventSubscriberOrchestrator`. diff --git a/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj b/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj index b65375374..4cdfd033f 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Api/Cdr.Banking.Api.csproj @@ -5,7 +5,7 @@ true - + diff --git a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj index 13e0ba51e..51d9bfd9c 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Business/Cdr.Banking.Business.csproj @@ -11,8 +11,8 @@ - - - + + + \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj index 94262139c..ed2170c3b 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Common/Cdr.Banking.Common.csproj @@ -8,6 +8,6 @@
- + \ No newline at end of file diff --git a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj index e3b2dfd5f..96459306a 100644 --- a/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj +++ b/samples/Cdr.Banking/Cdr.Banking.Test/Cdr.Banking.Test.csproj @@ -34,12 +34,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive
- + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj b/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj index 37ecb0a26..db58eed67 100644 --- a/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj +++ b/samples/Demo/Beef.Demo.Api/Beef.Demo.Api.csproj @@ -12,7 +12,7 @@ - + diff --git a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj index 4c9da22d0..e7c4964a9 100644 --- a/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj +++ b/samples/Demo/Beef.Demo.Business/Beef.Demo.Business.csproj @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + diff --git a/samples/Demo/Beef.Demo.Business/Data/Generated/ServiceCollectionExtensions.cs b/samples/Demo/Beef.Demo.Business/Data/Generated/ServiceCollectionExtensions.cs index 9a3970fa2..f38c8f58b 100644 --- a/samples/Demo/Beef.Demo.Business/Data/Generated/ServiceCollectionExtensions.cs +++ b/samples/Demo/Beef.Demo.Business/Data/Generated/ServiceCollectionExtensions.cs @@ -21,8 +21,8 @@ public static IServiceCollection AddGeneratedDataServices(this IServiceCollectio { return services.AddScoped() .AddScoped() - .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); } } diff --git a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/ServiceCollectionExtensions.cs b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/ServiceCollectionExtensions.cs index 985b9dcd2..86f767cc9 100644 --- a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/ServiceCollectionExtensions.cs +++ b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/ServiceCollectionExtensions.cs @@ -21,8 +21,8 @@ public static IServiceCollection AddGeneratedDataSvcServices(this IServiceCollec { return services.AddScoped() .AddScoped() - .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); } } diff --git a/samples/Demo/Beef.Demo.Business/Generated/ServiceCollectionExtensions.cs b/samples/Demo/Beef.Demo.Business/Generated/ServiceCollectionExtensions.cs index 06c83e9ea..a0333febf 100644 --- a/samples/Demo/Beef.Demo.Business/Generated/ServiceCollectionExtensions.cs +++ b/samples/Demo/Beef.Demo.Business/Generated/ServiceCollectionExtensions.cs @@ -21,9 +21,9 @@ public static IServiceCollection AddGeneratedManagerServices(this IServiceCollec { return services.AddScoped() .AddScoped() - .AddScoped() .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); } } diff --git a/samples/Demo/Beef.Demo.CodeGen/contact.entity.beef-5.yaml b/samples/Demo/Beef.Demo.CodeGen/contact.entity.beef-5.yaml new file mode 100644 index 000000000..7f8421630 --- /dev/null +++ b/samples/Demo/Beef.Demo.CodeGen/contact.entity.beef-5.yaml @@ -0,0 +1,25 @@ +entities: + # Entity with no etag or changelog +- { name: Contact, collection: true, collectionResult: true, isInitialOverride: false, validator: ContactValidator, webApiRoutePrefix: api/v1/contacts, webApiAuthorize: AllowAnonymous, webApiCtorParams: [ Microsoft.Extensions.Configuration.IConfiguration^Config ], eventPublish: Data, eventOutbox: Database, get: true, create: true, update: true, patch: true, delete: true, getAll: true, managerCleanUp: false, autoImplement: EntityFramework, databaseSchema: Demo, entityFrameworkModel: EfModel.Contact, + properties: [ + { name: Id, text: '{{Contact}} identifier', type: Guid, primaryKey: true, dataAutoGenerated: true, dataName: ContactId }, + { name: FirstName, type: string }, + { name: LastName, type: string }, + { name: Status, type: ^Status, dataName: StatusCode, refDataText: Always, refDataTextName: StatusDescription }, + { name: InternalCode, type: string, internalOnly: true, entityFrameworkMapper: Ignore }, + { name: Communications, type: ContactCommCollection, dataName: Comms, dataConverter: 'ObjectToJsonConverter{T}', entityFrameworkMapper: Set } + ], + operations: [ + { name: RaiseEvent, type: Custom, webApiRoute: raise, eventPublish: Data, eventOutbox: Database, + parameters: [ + { name: throwError, text: throw a DivideByZero exception, type: bool } + ] + } + ] + } + +- { name: ContactComm, collection: true, collectionType: Dictionary, + properties: [ + { name: Value }, + { name: IsPreferred, type: bool, text: Communication is the preferred (only one) } + ]} diff --git a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml index 6d565a26a..f808d3839 100644 --- a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml +++ b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml @@ -203,31 +203,6 @@ entities: ] } - # Entity with no etag or changelog -- { name: Contact, collection: true, collectionResult: true, isInitialOverride: false, validator: ContactValidator, webApiRoutePrefix: api/v1/contacts, webApiAuthorize: AllowAnonymous, webApiCtorParams: [ Microsoft.Extensions.Configuration.IConfiguration^Config ], eventPublish: Data, eventOutbox: Database, get: true, create: true, update: true, patch: true, delete: true, getAll: true, managerCleanUp: false, autoImplement: EntityFramework, databaseSchema: Demo, entityFrameworkModel: EfModel.Contact, - properties: [ - { name: Id, text: '{{Contact}} identifier', type: Guid, primaryKey: true, dataAutoGenerated: true, dataName: ContactId }, - { name: FirstName, type: string }, - { name: LastName, type: string }, - { name: Status, type: ^Status, dataName: StatusCode, refDataText: Always, refDataTextName: StatusDescription }, - { name: InternalCode, type: string, internalOnly: true, entityFrameworkMapper: Ignore }, - { name: Communications, type: ContactCommCollection, dataName: Comms, dataConverter: 'ObjectToJsonConverter{T}', entityFrameworkMapper: Set } - ], - operations: [ - { name: RaiseEvent, type: Custom, webApiRoute: raise, eventPublish: Data, eventOutbox: Database, - parameters: [ - { name: throwError, text: throw a DivideByZero exception, type: bool } - ] - } - ] - } - -- { name: ContactComm, collection: true, collectionType: Dictionary, - properties: [ - { name: Value }, - { name: IsPreferred, type: bool, text: Communication is the preferred (only one) } - ]} - - { name: Config, webApiRoutePrefix: api/v1/envvars, excludeEntity: true, excludeDataSvc: true, excludeIDataSvc: true, excludeData: Exclude, excludeIData: true, operations: [ { name: GetEnvVars, type: Custom, managerCustom: true, returnType: System.Collections.IDictionary } diff --git a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj index 69d15aa71..2b92f63da 100644 --- a/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj +++ b/samples/Demo/Beef.Demo.Common/Beef.Demo.Common.csproj @@ -7,7 +7,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj index b7fcf6828..a88be813b 100644 --- a/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj +++ b/samples/Demo/Beef.Demo.Test/Beef.Demo.Test.csproj @@ -52,8 +52,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -62,7 +62,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj index 6a08258ee..a6eb845a5 100644 --- a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj +++ b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj @@ -5,7 +5,7 @@ true - + diff --git a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj index 869840215..d7ca8b397 100644 --- a/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj +++ b/samples/My.Hr/My.Hr.Business/My.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj index fbdc74ba1..656bac16a 100644 --- a/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj +++ b/samples/My.Hr/My.Hr.Common/My.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj index 83f3b5a53..bc2870f7a 100644 --- a/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj +++ b/samples/My.Hr/My.Hr.Test/My.Hr.Test.csproj @@ -32,17 +32,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj index 99563145c..e7125cbb4 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Api/MyEf.Hr.Api.csproj @@ -6,8 +6,8 @@ True - - + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj index 63019ffb5..b69abb32b 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Business/MyEf.Hr.Business.csproj @@ -5,10 +5,10 @@ true - - - - + + + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj index fbdc74ba1..656bac16a 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Common/MyEf.Hr.Common.csproj @@ -4,6 +4,6 @@ enable - + \ No newline at end of file diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj index f6be60e76..6ffc2ccf6 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Subscriptions/MyEf.Hr.Security.Subscriptions.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj index 3f25b7447..e4840f1f0 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Security.Test/MyEf.Hr.Security.Test.csproj @@ -19,15 +19,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj index 6d9462c5d..eef8cc9a9 100644 --- a/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj +++ b/samples/MyEf.Hr/MyEf.Hr.Test/MyEf.Hr.Test.csproj @@ -32,17 +32,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/templates/Beef.Template.Solution/content/.template.config/template.json b/templates/Beef.Template.Solution/content/.template.config/template.json index 719a1bc29..31a63f866 100644 --- a/templates/Beef.Template.Solution/content/.template.config/template.json +++ b/templates/Beef.Template.Solution/content/.template.config/template.json @@ -85,7 +85,7 @@ "type": "generated", "generator": "constant", "parameters": { - "value": "3.14.0" + "value": "3.14.1" }, "replaces": "CoreExVersion" }, diff --git a/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj b/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj index 8758a8bb6..01298388f 100644 --- a/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj +++ b/tools/Beef.CodeGen.Core/Beef.CodeGen.Core.csproj @@ -31,7 +31,7 @@ - + diff --git a/tools/Beef.CodeGen.Core/CodeGenConsole.cs b/tools/Beef.CodeGen.Core/CodeGenConsole.cs index a0e76414b..46a496cd0 100644 --- a/tools/Beef.CodeGen.Core/CodeGenConsole.cs +++ b/tools/Beef.CodeGen.Core/CodeGenConsole.cs @@ -5,11 +5,11 @@ using Microsoft.Extensions.Logging; using OnRamp; using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Threading.Tasks; @@ -415,10 +415,18 @@ private async Task ExecuteEndpointsAsync(string? exedir, string company, string EndPointStatistics endpoints; if (IsEntitySupported) { + // Writer header. + var filename = CodeGenFileManager.GetConfigFilename(exedir!, CommandType.Entity, company, appName); + Logger?.LogInformation("{Content}", $"Config: {filename}"); + var root = await LoadConfigAsync(CodeGenFileManager.GetConfigFilename(exedir!, CommandType.Entity, company, appName)).ConfigureAwait(false); endpoints = new EndPointStatistics(); endpoints.AddEntityEndPoints(root); - endpoints.WriteTabulated(Args.Logger!, root.CodeGenArgs!.ConfigFileName!); + + Logger?.LogInformation("{Content}", $"Endpoints: {(endpoints.Count == 0 ? "none" : endpoints.Count)}"); + Logger?.LogInformation("{Content}", string.Empty); + + endpoints.WriteTabulated(Args.Logger!); if (IsRefDataSupported) { @@ -429,10 +437,17 @@ private async Task ExecuteEndpointsAsync(string? exedir, string company, string if (IsRefDataSupported) { + var filename = CodeGenFileManager.GetConfigFilename(exedir!, CommandType.RefData, company, appName); + Logger?.LogInformation("{Content}", $"Config: {filename}"); + var root = await LoadConfigAsync(CodeGenFileManager.GetConfigFilename(exedir!, CommandType.RefData, company, appName)).ConfigureAwait(false); endpoints = new EndPointStatistics(); endpoints.AddRefDataEndPoints(root); - endpoints.WriteTabulated(Args.Logger!, root.CodeGenArgs!.ConfigFileName!); + + Logger?.LogInformation("{Content}", $"Endpoints: {(endpoints.Count == 0 ? "none" : endpoints.Count)}"); + Logger?.LogInformation("{Content}", string.Empty); + + endpoints.WriteTabulated(Args.Logger!); } if (!IsEntitySupported && !IsRefDataSupported) diff --git a/tools/Beef.CodeGen.Core/CodeGenerator.cs b/tools/Beef.CodeGen.Core/CodeGenerator.cs index 6f6c4d5cc..d8b476af2 100644 --- a/tools/Beef.CodeGen.Core/CodeGenerator.cs +++ b/tools/Beef.CodeGen.Core/CodeGenerator.cs @@ -1,8 +1,15 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/Beef using Beef.CodeGen.Config.Entity; +using Microsoft.Extensions.Logging; using OnRamp; using OnRamp.Scripts; +using OnRamp.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Text.Json.Nodes; namespace Beef.CodeGen { @@ -13,6 +20,102 @@ namespace Beef.CodeGen /// The . internal class CodeGenerator(ICodeGeneratorArgs args, CodeGenScript scripts) : OnRamp.CodeGenerator(args, scripts) { + private const string _entitiesName = "entities"; + + /// + /// Will search for secondary (related) configuration files and will dynamically include (merge) the configured entities into the primary configuration. + protected override void OnConfigurationLoad(CodeGenScript script, string fileName, JsonNode json) + { + if (fileName == StreamFileName) + return; + + var fi = new FileInfo(fileName); + if (!fi.Exists) + return; + + var secondaryFiles = new List(); + JsonArray? rootEntities = null; + var root = Path.GetFileNameWithoutExtension(fi.Name); + foreach (var secondaryFile in fi.Directory!.EnumerateFiles($"*.{root}.*", SearchOption.AllDirectories)) + { + if (!Path.GetFileNameWithoutExtension(secondaryFile.FullName).EndsWith(root, StringComparison.OrdinalIgnoreCase)) + continue; + + try + { + using var configReader = secondaryFile.OpenText(); + + var secondaryJson = StreamLocator.GetContentType(secondaryFile.FullName) switch + { + StreamContentType.Yaml => configReader.YamlToJsonNode(), + StreamContentType.Json => configReader.JsonToJsonNode(), + _ => null + }; + + if (secondaryJson is null) + continue; + + var entities = GetSecondaryEntitiesArray(secondaryJson.Root) ?? throw new CodeGenException($"The secondary root node must be an object, with a single '{_entitiesName}' property that is an array. [{secondaryFile.FullName}]"); + if (entities is null || entities.Count == 0) + continue; + + rootEntities ??= GetPrimaryEntitiesArray(json); + if (rootEntities is null) + return; + + foreach (var ji in entities) + { + rootEntities.Add(ji!.DeepClone()); + } + + secondaryFiles.Add(secondaryFile); + } + catch (CodeGenException) { throw; } + catch (Exception ex) { throw new CodeGenException($"{ex.Message} [{secondaryFile.FullName}]"); } + } + + for (int i = 0; i < secondaryFiles.Count; i++) + { + if (i == 0) + CodeGenArgs.Logger?.LogInformation("{Content}", "Merged:"); + + CodeGenArgs.Logger?.LogInformation("{Content}", $" {secondaryFiles[i].FullName}"); + } + + } + + /// + /// Get the entities array for the secondary configuration. + /// + private static JsonArray? GetSecondaryEntitiesArray(JsonNode root) + { + if (root.GetValueKind() != JsonValueKind.Object) + return null; + + var jo = root.AsObject(); + if (jo.Count == 1 && jo.TryGetPropertyValue(_entitiesName, out var je)) + return je!.GetValueKind() != JsonValueKind.Array ? null : je.AsArray(); + else + return null; + } + + /// + /// Gets or creates the entities array for the primary configuration. + /// + private static JsonArray? GetPrimaryEntitiesArray(JsonNode root) + { + if (root.GetValueKind() != JsonValueKind.Object) + return null; + + var jo = root.AsObject(); + if (jo.TryGetPropertyValue(_entitiesName, out var je)) + return je!.GetValueKind() != JsonValueKind.Array ? null : je.AsArray(); + + je = new JsonArray(); + jo.Add(_entitiesName, je); + return (JsonArray)je; + } + /// protected override void OnAfterScript(CodeGenScriptItem script, CodeGenStatistics statistics) { diff --git a/tools/Beef.CodeGen.Core/Config/Database/CodeGenConfig.cs b/tools/Beef.CodeGen.Core/Config/Database/CodeGenConfig.cs index e489b0452..cb3e3200c 100644 --- a/tools/Beef.CodeGen.Core/Config/Database/CodeGenConfig.cs +++ b/tools/Beef.CodeGen.Core/Config/Database/CodeGenConfig.cs @@ -456,7 +456,8 @@ protected override async Task PrepareAsync() ///
private async Task LoadDbTablesConfigAsync() { - CodeGenArgs!.Logger?.Log(LogLevel.Information, "{Content}", $" Querying database to infer table(s)/column(s) schema configuration..."); + CodeGenArgs!.Logger?.Log(LogLevel.Information, "{Content}", string.Empty); + CodeGenArgs.Logger?.Log(LogLevel.Information, "{Content}", $"Querying database to infer table(s)/column(s) schema configuration..."); _ = CodeGenArgs.ConnectionString ?? throw new CodeGenException("Connection string must be specified via an environment variable or as a command-line option."); @@ -466,8 +467,7 @@ private async Task LoadDbTablesConfigAsync() DbTables = await db.SelectSchemaAsync(Migrator).ConfigureAwait(false); sw.Stop(); - CodeGenArgs.Logger?.Log(LogLevel.Information, "{Content}", $" Database schema query complete [{sw.ElapsedMilliseconds}ms]"); - CodeGenArgs.Logger?.Log(LogLevel.Information, "{Content}", string.Empty); + CodeGenArgs.Logger?.Log(LogLevel.Information, "{Content}", $" Database schema query complete [{sw.ElapsedMilliseconds}ms]"); } /// diff --git a/tools/Beef.CodeGen.Core/EndPointStatistics.cs b/tools/Beef.CodeGen.Core/EndPointStatistics.cs index ebafd5bb5..f966c3ea3 100644 --- a/tools/Beef.CodeGen.Core/EndPointStatistics.cs +++ b/tools/Beef.CodeGen.Core/EndPointStatistics.cs @@ -14,6 +14,11 @@ internal class EndPointStatistics() { private readonly List _endPoints = []; + /// + /// Gets the endpoint count. + /// + public int Count => _endPoints.Count; + /// /// Adds the endpoints. /// @@ -79,14 +84,8 @@ public void AddRefDataEndPoints(Config.Entity.CodeGenConfig root) /// Write as tabulated data to the . /// /// The . - /// The title. - public void WriteTabulated(ILogger logger, string title) + public void WriteTabulated(ILogger logger) { - // Writer header. - logger.LogInformation("{Content}", $"Config: {title}"); - logger.LogInformation("{Content}", $"Count: {(_endPoints.Count == 0 ? "none" : _endPoints.Count)}"); - logger.LogInformation("{Content}", string.Empty); - if (_endPoints.Count == 0) return; diff --git a/tools/Beef.CodeGen.Core/README.md b/tools/Beef.CodeGen.Core/README.md index 951dd93f2..7d5cb65a2 100644 --- a/tools/Beef.CodeGen.Core/README.md +++ b/tools/Beef.CodeGen.Core/README.md @@ -83,13 +83,19 @@ Configuration details for each of the above are as follows: - `Operation` - [YAML/JSON](../../docs/Entity-Operation-Config.md) - `Parameter` - [YAML/JSON](../../docs/Entity-Parameter-Config.md) -The Entity configuration supported filenames are, in the order in which they are searched by the code generator: `entity.beef-5.yaml` and `entity.beef-5.json`. +The Entity configuration supported filenames are, in the order in which they are searched by the code generator: +- `entity.beef-5.yaml` and `entity.beef-5.json`. The Entity configuration is defined by a schema, YAML/JSON-based [entity.beef.json](../../tools/Beef.CodeGen.Core/Schema/entity.beef.json). This schema should be used within the likes of Visual Studio when editing to enable real-time validation and basic intellisense capabilities. There are two additional configuration files that share the same schema: -- [Reference Data](./../../docs/Reference-Data.md) - used to define (configure) the _Beef_-specific Reference Data. Supported filenames are in the order in which they are searched by the code generator: `refdata.beef-5.yaml` and `refdata.beef-5.json`. -- Data Model - used to define (configure) basic data model .NET classes typically used to represent internal/backend contracts that do not require the full funcionality of a _Beef_ entity. Supported filenames are in the order in which they are searched by the code generator: `datamodel.beef-5.yaml` and `datamodel.beef-5.json`. +- [Reference Data](./../../docs/Reference-Data.md) - used to define (configure) the _Beef_-specific Reference Data. Supported filenames are in the order in which they are searched by the code generator: + - `refdata.beef-5.yaml` and `refdata.beef-5.json`. + +- Data Model - used to define (configure) basic data model .NET classes typically used to represent internal/backend contracts that do not require the full funcionality of a _Beef_ entity. Supported filenames are in the order in which they are searched by the code generator: + - `datamodel.beef-5.yaml` and `datamodel.beef-5.json`. + +Additionally, secondary configuration files `*.entity.beef-5.yaml`, `*.refdata.beef-5.yaml`, `*.datamodel.beef-5.yaml` can be added to the project (including within subfolders) that will be automatically merged into the corresponding primary `entity.beef-5.yaml`, `refdata.beef-5.yaml`, `datamodel.beef-5.yaml` files respectively. The secondary files only support a single root `entities` property/node that merges into the primary's equivalent. This allows the configuration to be broken up logically to minimize challenges related to overall file size and complexity, and minimize potential developer merge conflicts, etc.
@@ -129,7 +135,8 @@ Configuration details for each of the above are as follows: - Relationship (EF) - [YAML/JSON](../../docs/Database-Relationship-Config.md) -The Database configuration supported filenames are, in the order in which they are searched by the code generator: `database.beef-5.yaml` and `database.beef-5.json`. +The Database configuration supported filenames are, in the order in which they are searched by the code generator: +- `database.beef-5.yaml` and `database.beef-5.json`. The Database configuration is defined by a schema, YAML/JSON-based [database.beef-5.json](../../tools/Beef.CodeGen.Core/Schema/database.beef-5.json). The schema should be used within the likes of Visual Studio when editing to enable real-time validation and basic intellisense capabilities. @@ -153,8 +160,8 @@ Command | Description -|- `Entity` | Performs code generation using the _entity_ configuration and [`EntityWebApiCoreAgent.yaml`](./Scripts/EntityWebApiCoreAgent.yaml) script. `RefData` | Performs code generation using the _refdata_ configuration and [`RefDataCoreCrud.yaml`](./Scripts/RefDataCoreCrud.yaml) script. -`Database` | Performs code generation using the _database_ configuration and [`Database.yaml`](../Beef.Database.SqlServer/Scripts/Database.yaml) script. `DataModel` | Performs code generation using the _data model_ configuration and [`DataModelOnly.yaml`](./Scripts/DataModelOnly.yaml) script. +`Database` | Performs code generation using the _database_ configuration and [`Database.yaml`](../Beef.Database.SqlServer/Scripts/Database.yaml) script. `All` | Performs all of the above (where each is supported as per set up). Additionally, there are a number of command line options that can be used. diff --git a/tools/Beef.Database.Core/Beef.Database.Core.csproj b/tools/Beef.Database.Core/Beef.Database.Core.csproj index 0ef187afb..cc8b53fbb 100644 --- a/tools/Beef.Database.Core/Beef.Database.Core.csproj +++ b/tools/Beef.Database.Core/Beef.Database.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj index f325d5f9e..692814d02 100644 --- a/tools/Beef.Database.MySql/Beef.Database.MySql.csproj +++ b/tools/Beef.Database.MySql/Beef.Database.MySql.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj b/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj index 2ef0bf7eb..bdadf7c74 100644 --- a/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj +++ b/tools/Beef.Database.Postgres/Beef.Database.Postgres.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj index 346d7c69c..2f0c5441c 100644 --- a/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj +++ b/tools/Beef.Database.SqlServer/Beef.Database.SqlServer.csproj @@ -14,7 +14,7 @@ - + diff --git a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj index 6259bba52..dae67ad6a 100644 --- a/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj +++ b/tools/Beef.Test.NUnit/Beef.Test.NUnit.csproj @@ -8,7 +8,7 @@ - + From 277d87ddb496873b1b2be604e04a64fef5700abf Mon Sep 17 00:00:00 2001 From: chullybun Date: Tue, 19 Mar 2024 09:05:27 -0700 Subject: [PATCH 4/5] Template ref fix. --- .../Company.AppName.Services/Company.AppName.Services.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/Beef.Template.Solution/content/Company.AppName.Services/Company.AppName.Services.csproj b/templates/Beef.Template.Solution/content/Company.AppName.Services/Company.AppName.Services.csproj index e9fa6bc93..b6f174ee9 100644 --- a/templates/Beef.Template.Solution/content/Company.AppName.Services/Company.AppName.Services.csproj +++ b/templates/Beef.Template.Solution/content/Company.AppName.Services/Company.AppName.Services.csproj @@ -18,7 +18,6 @@ - From 7fe2942a5eaaa3b2b2e0236b7105fca9911b1317 Mon Sep 17 00:00:00 2001 From: chullybun Date: Tue, 19 Mar 2024 14:14:16 -0700 Subject: [PATCH 5/5] Operation.ReturnType nullability fix. --- CHANGELOG.md | 1 + .../Controllers/Generated/PersonController.cs | 2 +- .../Data/Generated/IPersonData.cs | 2 +- .../Data/Generated/PersonData.cs | 2 +- .../DataSvc/Generated/IPersonDataSvc.cs | 2 +- .../DataSvc/Generated/PersonDataSvc.cs | 4 +-- .../Generated/IPersonManager.cs | 2 +- .../Generated/PersonManager.cs | 4 +-- .../Demo/Beef.Demo.CodeGen/entity.beef-5.yaml | 2 +- .../Agents/Generated/IPersonAgent.cs | 2 +- .../Agents/Generated/PersonAgent.cs | 4 +-- .../Config/Entity/OperationConfig.cs | 31 ++++++++++++------- 12 files changed, 34 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aed653879..e96c31906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Represents the **NuGet** versions. - *Enhancement:* Added `WebApiTags` code-generation property to enable the specification of `Tags` for the Web API Controller class. - *Enhancement:* Added `dotnet run endpoints` option to report all configured endpoints providing a means to audit the generated API surface. - *Enhancement:* Secondary (additional) configuration files `*.entity.beef-5.yaml`, `*.refdata.beef-5.yaml`, `*.datamodel.beef-5.yaml` can be added to the project (including within subfolders) that will be automatically merged into the corresponding primary `entity.beef-5.yaml`, `refdata.beef-5.yaml`, `datamodel.beef-5.yaml` files respectively. The secondary files only support a single root `entities` property/node that merges into the primary's equivalent. This allows the configuration to be broken up logically to minimize challenges related to overall file size and complexity, and minimize potential developer merge conflicts, etc. +- *Fixed:* The `Operation.ReturnType` was incorrectly determining and overridding nullability (`Operation.ReturnTypeNullability`) which has been corrected. ## v5.11.0 - *Enhancement:* Added `dotnet new beef ... --services AzFunction` to enable the templating of a corresponding `Company.AppName.Services` project as an Azure Functions project. This will provide an example of leveraging the shared `Company.AppName.Business` logic and consuming the published events using an `EventSubscriberOrchestrator`. diff --git a/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs b/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs index 9c3853a7c..e4321cb94 100644 --- a/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs +++ b/samples/Demo/Beef.Demo.Api/Controllers/Generated/PersonController.cs @@ -313,7 +313,7 @@ public Task ThrowError() [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] public Task InvokeApiViaAgent(Guid id) - => _webApi.PostAsync(Request, p => _manager.InvokeApiViaAgentAsync(id), alternateStatusCode: HttpStatusCode.NoContent, operationType: CoreEx.OperationType.Unspecified); + => _webApi.PostAsync(Request, p => _manager.InvokeApiViaAgentAsync(id), alternateStatusCode: HttpStatusCode.NoContent, operationType: CoreEx.OperationType.Unspecified); /// /// Param Coll. diff --git a/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs b/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs index 8f094c0e4..7b4fab6d4 100644 --- a/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs +++ b/samples/Demo/Beef.Demo.Business/Data/Generated/IPersonData.cs @@ -148,7 +148,7 @@ public partial interface IPersonData /// /// The identifier. /// A resultant . - Task InvokeApiViaAgentAsync(Guid id); + Task InvokeApiViaAgentAsync(Guid id); /// /// Gets the specified . diff --git a/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs b/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs index e74e955d8..3922dc0ef 100644 --- a/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs +++ b/samples/Demo/Beef.Demo.Business/Data/Generated/PersonData.cs @@ -181,7 +181,7 @@ public Task GetByArgsWithEfAsync(PersonArgs? args, Pagin public Task ThrowErrorAsync() => ThrowErrorOnImplementationAsync(); /// - public Task InvokeApiViaAgentAsync(Guid id) => InvokeApiViaAgentOnImplementationAsync(id); + public Task InvokeApiViaAgentAsync(Guid id) => InvokeApiViaAgentOnImplementationAsync(id); /// public Task GetWithEfAsync(Guid id) => DataInvoker.Current.InvokeAsync(this, async (_, __) => diff --git a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs index 367e6ceba..b456c6993 100644 --- a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs +++ b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/IPersonDataSvc.cs @@ -161,7 +161,7 @@ public partial interface IPersonDataSvc /// /// The identifier. /// A resultant . - Task InvokeApiViaAgentAsync(Guid id); + Task InvokeApiViaAgentAsync(Guid id); /// /// Param Coll. diff --git a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs index b81a00b77..86ce54c11 100644 --- a/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs +++ b/samples/Demo/Beef.Demo.Business/DataSvc/Generated/PersonDataSvc.cs @@ -35,7 +35,7 @@ public partial class PersonDataSvc : IPersonDataSvc private Func?, Task>? _getNullOnAfterAsync; private Func? _getByArgsWithEfOnAfterAsync; private Func? _throwErrorOnAfterAsync; - private Func? _invokeApiViaAgentOnAfterAsync; + private Func? _invokeApiViaAgentOnAfterAsync; private Func? _getWithEfOnAfterAsync; private Func? _createWithEfOnAfterAsync; private Func? _updateWithEfOnAfterAsync; @@ -225,7 +225,7 @@ public async Task ThrowErrorAsync() } /// - public async Task InvokeApiViaAgentAsync(Guid id) + public async Task InvokeApiViaAgentAsync(Guid id) { var r = await _data.InvokeApiViaAgentAsync(id).ConfigureAwait(false); await Invoker.InvokeAsync(_invokeApiViaAgentOnAfterAsync?.Invoke(r, id)).ConfigureAwait(false); diff --git a/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs b/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs index 5edd99480..ee9de4e2d 100644 --- a/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs +++ b/samples/Demo/Beef.Demo.Business/Generated/IPersonManager.cs @@ -181,7 +181,7 @@ public partial interface IPersonManager /// /// The identifier. /// A resultant . - Task InvokeApiViaAgentAsync(Guid id); + Task InvokeApiViaAgentAsync(Guid id); /// /// Param Coll. diff --git a/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs b/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs index 0b6aa1ab0..dde0d98b0 100644 --- a/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs +++ b/samples/Demo/Beef.Demo.Business/Generated/PersonManager.cs @@ -120,7 +120,7 @@ public partial class PersonManager : IPersonManager private Func? _invokeApiViaAgentOnPreValidateAsync; private Action? _invokeApiViaAgentOnValidate; private Func? _invokeApiViaAgentOnBeforeAsync; - private Func? _invokeApiViaAgentOnAfterAsync; + private Func? _invokeApiViaAgentOnAfterAsync; private Func? _paramCollOnPreValidateAsync; private Action? _paramCollOnValidate; @@ -510,7 +510,7 @@ await MultiValidator.Create() }, new InvokerArgs { IncludeTransactionScope = true, OperationType = OperationType.Unspecified }); /// - public Task InvokeApiViaAgentAsync(Guid id) => ManagerInvoker.Current.InvokeAsync(this, async (_, ct) => + public Task InvokeApiViaAgentAsync(Guid id) => ManagerInvoker.Current.InvokeAsync(this, async (_, ct) => { Cleaner.CleanUp(id); await Invoker.InvokeAsync(_invokeApiViaAgentOnPreValidateAsync?.Invoke(id)).ConfigureAwait(false); diff --git a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml index f808d3839..d1de60dc3 100644 --- a/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml +++ b/samples/Demo/Beef.Demo.CodeGen/entity.beef-5.yaml @@ -99,7 +99,7 @@ entities: parameters: [ { name: Id, text: '{{Person}} identifier', type: Guid, isMandatory: true } ]}, - { name: SimulateWork, type: Custom, withResult: true, returnType: string, webApiRoute: simulate, webApiMethod: HttpGet, webApiStatus: OK, primaryKey: true } + { name: SimulateWork, type: Custom, withResult: true, returnType: string?, webApiRoute: simulate, webApiMethod: HttpGet, webApiStatus: OK, primaryKey: true } ] } diff --git a/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs b/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs index c1e67930c..85bb71a3f 100644 --- a/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs +++ b/samples/Demo/Beef.Demo.Common/Agents/Generated/IPersonAgent.cs @@ -255,7 +255,7 @@ public partial interface IPersonAgent /// The optional . /// The . /// A . - Task> InvokeApiViaAgentAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); + Task> InvokeApiViaAgentAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default); /// /// Param Coll. diff --git a/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs b/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs index 068073148..57e497b9f 100644 --- a/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs +++ b/samples/Demo/Beef.Demo.Common/Agents/Generated/PersonAgent.cs @@ -133,8 +133,8 @@ public Task ThrowErrorAsync(HttpRequestOptions? requestOptions = nul => PostAsync("api/v1/persons/error", requestOptions: requestOptions, cancellationToken: cancellationToken); /// - public Task> InvokeApiViaAgentAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) - => PostAsync("api/v1/persons/invokeApi", requestOptions: requestOptions, args: HttpArgs.Create(new HttpArg("id", id)), cancellationToken: cancellationToken); + public Task> InvokeApiViaAgentAsync(Guid id, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => PostAsync("api/v1/persons/invokeApi", requestOptions: requestOptions, args: HttpArgs.Create(new HttpArg("id", id)), cancellationToken: cancellationToken); /// public Task ParamCollAsync(AddressCollection? addresses, HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) diff --git a/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs b/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs index 6553aacf2..69628ee1a 100644 --- a/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs +++ b/tools/Beef.CodeGen.Core/Config/Entity/OperationConfig.cs @@ -1038,17 +1038,6 @@ protected override async Task PrepareAsync() ReturnTypeNullable = true; } - ReturnTypeNullable = DefaultWhereNull(ReturnTypeNullable, () => false); - if (ReturnType == "void") - ReturnTypeNullable = false; - else if (Type == "Get") - ReturnTypeNullable = true; - else if (Type != "Custom") - ReturnTypeNullable = false; - - if (ReturnType == "string") - ReturnTypeNullable = true; - if (ReturnType != null && Type == "GetColl") ReturnType += "CollectionResult"; @@ -1063,6 +1052,26 @@ protected override async Task PrepareAsync() _ => "void" }); + if (ReturnType != null && ReturnType!.EndsWith("?", StringComparison.InvariantCulture)) + { + ReturnType = ReturnType[0..^1]; + ReturnTypeNullable = true; + } + + ReturnTypeNullable = DefaultWhereNull(ReturnTypeNullable, () => + { + if (Type != "Custom") + ReturnTypeNullable = false; + + return false; + }); + + if (ReturnType == "void") + ReturnTypeNullable = false; + + if (Type == "Get") + ReturnTypeNullable = true; + ValueType = DefaultWhereNull(ValueType, () => Type switch { "Create" => Parent!.EntityName,