Repository: Avanade/Liquid-Application-Framework Branch: main Commit: 6476e1704477 Files: 536 Total size: 887.5 KB Directory structure: gitextract_r5h2xvzw/ ├── .editorconfig ├── .github/ │ ├── dco.yml │ └── workflows/ │ ├── base-liquid-ci-and-cd.yml │ ├── liquid-ci-cd-cache-memory.yml │ ├── liquid-ci-cd-cache-ncache.yml │ ├── liquid-ci-cd-cache-redis.yml │ ├── liquid-ci-cd-cache-sqlserver.yml │ ├── liquid-ci-cd-core-telemetry-elasticapm.yaml │ ├── liquid-ci-cd-core.yml │ ├── liquid-ci-cd-dataverse.yml │ ├── liquid-ci-cd-genai-openai.yml │ ├── liquid-ci-cd-http.yml │ ├── liquid-ci-cd-messaging-kafka.yaml │ ├── liquid-ci-cd-messaging-rabbitmq.yaml │ ├── liquid-ci-cd-messaging-servicebus.yaml │ ├── liquid-ci-cd-repository-entityframework.yml │ ├── liquid-ci-cd-repository-mongodb.yml │ ├── liquid-ci-cd-repository-odata.yml │ ├── liquid-ci-cd-storage-azurestorage.yml │ └── liquid-ci-cd-templates.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Liquid.Adapters.sln ├── Liquid.Application.Framework.sln ├── Liquid.WebApi.sln ├── README.md ├── docs/ │ ├── About-Lightweight-Architectures.md │ ├── About-Liquid-Applications.md │ ├── About-Liquid.md │ ├── Business-logic-seggregation.md │ ├── Introduction.md │ ├── Key-Concepts.md │ ├── Platform-Abstraction-Layer.md │ └── Using-Liquid-for-building-your-application.md ├── nuget.config ├── samples/ │ ├── Liquid.Sample.Dataverse.sln │ ├── Liquid.Sample.MessagingConsumer.sln │ ├── Liquid.Sample.WebApi.sln │ └── src/ │ ├── Liquid.Sample.Api/ │ │ ├── Controllers/ │ │ │ └── SampleController.cs │ │ ├── Liquid.Sample.Api.csproj │ │ ├── Program.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── Liquid.Sample.Dataverse.Domain/ │ │ ├── Liquid.Sample.Dataverse.Domain.csproj │ │ └── PostSampleService.cs │ ├── Liquid.Sample.Dataverse.Function/ │ │ ├── .gitignore │ │ ├── DataverseIntegration.cs │ │ ├── Liquid.Sample.Dataverse.Function.csproj │ │ ├── Properties/ │ │ │ ├── serviceDependencies.json │ │ │ └── serviceDependencies.local.json │ │ ├── Startup.cs │ │ ├── host.json │ │ └── local.settings.json │ ├── Liquid.Sample.Domain/ │ │ ├── Entities/ │ │ │ ├── SampleEntity.cs │ │ │ └── SampleMessageEntity.cs │ │ ├── Handlers/ │ │ │ ├── SampleGet/ │ │ │ │ ├── SampleCommandHandler.cs │ │ │ │ ├── SampleRequest.cs │ │ │ │ ├── SampleRequestValidator.cs │ │ │ │ └── SampleResponse.cs │ │ │ ├── SamplePost/ │ │ │ │ ├── SampleEventCommandHandler.cs │ │ │ │ ├── SampleEventRequest.cs │ │ │ │ └── SampleEventRequestValidator.cs │ │ │ └── SamplePut/ │ │ │ ├── PutCommandHandler.cs │ │ │ ├── PutCommandRequest.cs │ │ │ └── PutCommandRequestValidator.cs │ │ └── Liquid.Sample.Domain.csproj │ └── Liquid.Sample.MessagingConsumer/ │ ├── Liquid.Sample.MessagingConsumer.csproj │ ├── Program.cs │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── src/ │ ├── Liquid.Cache.Memory/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtension.cs │ │ └── Liquid.Cache.Memory.csproj │ ├── Liquid.Cache.NCache/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtension.cs │ │ ├── Liquid.Cache.NCache.csproj │ │ ├── client.ncconf │ │ ├── config.ncconf │ │ └── tls.ncconf │ ├── Liquid.Cache.Redis/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtension.cs │ │ └── Liquid.Cache.Redis.csproj │ ├── Liquid.Cache.SqlServer/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtension.cs │ │ └── Liquid.Cache.SqlServer.csproj │ ├── Liquid.Core/ │ │ ├── AbstractMappers/ │ │ │ ├── LiquidMapper.cs │ │ │ └── OcrResultMapper.cs │ │ ├── Attributes/ │ │ │ └── LiquidSectionNameAttribute.cs │ │ ├── Base/ │ │ │ ├── Enumeration.cs │ │ │ └── LiquidInterceptorBase.cs │ │ ├── Decorators/ │ │ │ ├── LiquidContextDecorator.cs │ │ │ ├── LiquidCultureDecorator.cs │ │ │ └── LiquidScopedLoggingDecorator.cs │ │ ├── Entities/ │ │ │ ├── ChatCompletionResult.cs │ │ │ ├── ClientDictionary.cs │ │ │ ├── ConsumerErrorEventArgs.cs │ │ │ ├── ConsumerMessageEventArgs.cs │ │ │ ├── FunctionBody.cs │ │ │ ├── LiquidBlob.cs │ │ │ ├── LiquidEntity.cs │ │ │ └── OcrResult.cs │ │ ├── Exceptions/ │ │ │ ├── DataMappingException.cs │ │ │ ├── DatabaseContextException.cs │ │ │ ├── ExceptionCustomCodes.cs │ │ │ ├── LiquidCustomException.cs │ │ │ ├── LiquidDatabaseSettingsDoesNotExistException.cs │ │ │ ├── LiquidException.cs │ │ │ ├── MessagingConsumerException.cs │ │ │ ├── MessagingMissingConfigurationException.cs │ │ │ ├── MessagingMissingContextKeysException.cs │ │ │ ├── MessagingMissingScopedKeysException.cs │ │ │ ├── MessagingMissingSettingsException.cs │ │ │ ├── MessagingProducerException.cs │ │ │ ├── SerializerFailException.cs │ │ │ ├── UnitOfWorkTransactionNotStartedException.cs │ │ │ └── UnitOfWorkTransactionWithoutRepositoryException.cs │ │ ├── Extensions/ │ │ │ ├── ByteExtension.cs │ │ │ ├── CommonExtensions.cs │ │ │ ├── DateTimeExtension.cs │ │ │ ├── DependencyInjection/ │ │ │ │ ├── IServiceCollectionAutoMapperExtensions.cs │ │ │ │ ├── IServiceCollectionCoreExtensions.cs │ │ │ │ ├── IServiceCollectionLiquidExtension.cs │ │ │ │ ├── IServiceCollectionTypeExtensions.cs │ │ │ │ └── IServiceProviderExtensions.cs │ │ │ ├── EnumExtension.cs │ │ │ ├── IEnumerableExtension.cs │ │ │ ├── IntExtension.cs │ │ │ ├── ObjectExtension.cs │ │ │ ├── StreamExtension.cs │ │ │ ├── StringExtension.cs │ │ │ └── TypeExtensions.cs │ │ ├── GenAi/ │ │ │ ├── Entities/ │ │ │ │ ├── LiquidChatContent.cs │ │ │ │ ├── LiquidChatMessage.cs │ │ │ │ └── LiquidChatMessages.cs │ │ │ ├── Enums/ │ │ │ │ ├── LiquidContentKind.cs │ │ │ │ └── LiquidMessageRole.cs │ │ │ ├── Interfaces/ │ │ │ │ ├── ILiquidGenAi.cs │ │ │ │ └── ILiquidGenAiHandler.cs │ │ │ └── Settings/ │ │ │ └── CompletionsOptions.cs │ │ ├── Implementations/ │ │ │ ├── LiquidBackgroundService.cs │ │ │ ├── LiquidCache.cs │ │ │ ├── LiquidContext.cs │ │ │ ├── LiquidContextNotifications.cs │ │ │ ├── LiquidJsonSerializer.cs │ │ │ ├── LiquidSerializerProvider.cs │ │ │ ├── LiquidTelemetryInterceptor.cs │ │ │ ├── LiquidUnitOfWork.cs │ │ │ └── LiquidXmlSerializer.cs │ │ ├── Interfaces/ │ │ │ ├── ILiquidCache.cs │ │ │ ├── ILiquidConsumer.cs │ │ │ ├── ILiquidContext.cs │ │ │ ├── ILiquidContextNotifications.cs │ │ │ ├── ILiquidDataContext.cs │ │ │ ├── ILiquidMapper.cs │ │ │ ├── ILiquidOcr.cs │ │ │ ├── ILiquidProducer.cs │ │ │ ├── ILiquidRepository.cs │ │ │ ├── ILiquidSerializer.cs │ │ │ ├── ILiquidSerializerProvider.cs │ │ │ ├── ILiquidStorage.cs │ │ │ ├── ILiquidUnitOfWork.cs │ │ │ └── ILiquidWorker.cs │ │ ├── Liquid.Core.csproj │ │ ├── Localization/ │ │ │ ├── Entities/ │ │ │ │ ├── LocalizationCollection.cs │ │ │ │ ├── LocalizationItem.cs │ │ │ │ └── LocalizationValue.cs │ │ │ ├── ILocalization.cs │ │ │ ├── JsonFileLocalization.cs │ │ │ ├── LocalizationException.cs │ │ │ ├── LocalizationExtensions.cs │ │ │ └── LocalizationReaderException.cs │ │ ├── PipelineBehaviors/ │ │ │ ├── LiquidTelemetryBehavior.cs │ │ │ └── LiquidValidationBehavior.cs │ │ ├── Settings/ │ │ │ ├── CultureSettings.cs │ │ │ ├── DatabaseSettings.cs │ │ │ ├── GenAiOptions.cs │ │ │ ├── OcrOptions.cs │ │ │ ├── ScopedContextSettings.cs │ │ │ ├── ScopedKey.cs │ │ │ └── ScopedLoggingSettings.cs │ │ └── Utils/ │ │ ├── TypeUtils.cs │ │ └── ZipUtils.cs │ ├── Liquid.Core.Telemetry.ElasticApm/ │ │ ├── Extensions/ │ │ │ ├── DependencyInjection/ │ │ │ │ ├── IApplicationBuilderExtensions.cs │ │ │ │ └── IServiceCollectionExtensions.cs │ │ │ └── IConfigurationExtension.cs │ │ ├── Implementations/ │ │ │ ├── LiquidElasticApmInterceptor.cs │ │ │ └── LiquidElasticApmTelemetryBehavior.cs │ │ └── Liquid.Core.Telemetry.ElasticApm.csproj │ ├── Liquid.Dataverse/ │ │ ├── DataverseClientFactory.cs │ │ ├── DataverseEntityMapper.cs │ │ ├── DataverseSettings.cs │ │ ├── Extensions/ │ │ │ ├── DependencyInjection/ │ │ │ │ └── IServiceCollectionExtensions.cs │ │ │ └── EntityExtensions.cs │ │ ├── IDataverseClientFactory.cs │ │ ├── ILiquidDataverse.cs │ │ ├── Liquid.Dataverse.csproj │ │ └── LiquidDataverse.cs │ ├── Liquid.GenAi.OpenAi/ │ │ ├── Extensions/ │ │ │ └── IServiceCollectionExtension.cs │ │ ├── IOpenAiClientFactory.cs │ │ ├── Liquid.GenAi.OpenAi.csproj │ │ ├── OpenAiAdapter.cs │ │ ├── OpenAiClientFactory.cs │ │ └── Settings/ │ │ └── OpenAiOptions.cs │ ├── Liquid.Messaging.Kafka/ │ │ ├── Extensions/ │ │ │ ├── DependencyInjection/ │ │ │ │ └── IServiceCollectionExtension.cs │ │ │ └── HeadersExtension.cs │ │ ├── IKafkaFactory.cs │ │ ├── KafkaConsumer.cs │ │ ├── KafkaFactory.cs │ │ ├── KafkaProducer.cs │ │ ├── Liquid.Messaging.Kafka.csproj │ │ └── Settings/ │ │ └── KafkaSettings.cs │ ├── Liquid.Messaging.RabbitMq/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtension.cs │ │ ├── IRabbitMqFactory.cs │ │ ├── Liquid.Messaging.RabbitMq.csproj │ │ ├── RabbitMqConsumer.cs │ │ ├── RabbitMqFactory.cs │ │ ├── RabbitMqProducer.cs │ │ └── Settings/ │ │ ├── AdvancedSettings.cs │ │ ├── QueueAckModeEnum.cs │ │ ├── QueueAckModeSettings.cs │ │ ├── RabbitMqConsumerSettings.cs │ │ ├── RabbitMqProducerSettings.cs │ │ └── RabbitMqSettings.cs │ ├── Liquid.Messaging.ServiceBus/ │ │ ├── Extensions/ │ │ │ └── DependencyInjection/ │ │ │ └── IServiceCollectionExtensions.cs │ │ ├── IServiceBusFactory.cs │ │ ├── Liquid.Messaging.ServiceBus.csproj │ │ ├── ServiceBusConsumer.cs │ │ ├── ServiceBusFactory.cs │ │ ├── ServiceBusProducer.cs │ │ └── Settings/ │ │ └── ServiceBusSettings.cs │ ├── Liquid.Repository.EntityFramework/ │ │ ├── EntityFrameworkDataContext.cs │ │ ├── EntityFrameworkRepository.cs │ │ ├── Exceptions/ │ │ │ └── DatabaseDoesNotExistException.cs │ │ ├── Extensions/ │ │ │ ├── DbContextExtensions.cs │ │ │ ├── ILiquidRepositoryExtensions.cs │ │ │ └── IServiceCollectionExtensions.cs │ │ ├── IEntityFrameworkDataContext.cs │ │ └── Liquid.Repository.EntityFramework.csproj │ ├── Liquid.Repository.Mongo/ │ │ ├── Exceptions/ │ │ │ ├── MongoEntitySettingsDoesNotExistException.cs │ │ │ └── MongoException.cs │ │ ├── Extensions/ │ │ │ ├── IMongoCollectionExtensions.cs │ │ │ └── IServiceCollectionExtensions.cs │ │ ├── IMongoClientFactory.cs │ │ ├── IMongoDataContext.cs │ │ ├── Liquid.Repository.Mongo.csproj │ │ ├── MongoClientFactory.cs │ │ ├── MongoDataContext.cs │ │ ├── MongoRepository.cs │ │ └── Settings/ │ │ └── MongoEntitySettings.cs │ ├── Liquid.Repository.OData/ │ │ ├── Extensions/ │ │ │ └── IServiceCollectionExtension.cs │ │ ├── IODataClientFactory.cs │ │ ├── Liquid.Repository.OData.csproj │ │ ├── ODataClientFactory.cs │ │ ├── ODataRepository.cs │ │ └── ODataSettings.cs │ ├── Liquid.Storage.AzureStorage/ │ │ ├── BlobClientFactory.cs │ │ ├── Extensions/ │ │ │ └── IServiceCollectionExtensions.cs │ │ ├── IBlobClientFactory.cs │ │ ├── Liquid.Storage.AzureStorage.csproj │ │ ├── LiquidStorageAzure.cs │ │ └── StorageSettings.cs │ └── Liquid.WebApi.Http/ │ ├── Attributes/ │ │ ├── SwaggerAuthorizationHeaderAttribute.cs │ │ ├── SwaggerBaseHeaderAttribute.cs │ │ ├── SwaggerChannelHeaderAttribute.cs │ │ ├── SwaggerCultureHeaderAttribute.cs │ │ └── SwaggerCustomHeaderAttribute.cs │ ├── Entities/ │ │ └── LiquidErrorResponse.cs │ ├── Exceptions/ │ │ ├── LiquidContextKeysException.cs │ │ └── LiquidScopedKeysException.cs │ ├── Extensions/ │ │ ├── DependencyInjection/ │ │ │ ├── IApplicationBuilderExtensions.cs │ │ │ └── IServiceCollectionExtensions.cs │ │ └── HttpContextExtensions.cs │ ├── Filters/ │ │ └── Swagger/ │ │ ├── AddHeaderParameterFilter.cs │ │ ├── DefaultResponseFilter.cs │ │ ├── DocumentSortFilter.cs │ │ └── OverloadMethodsSameVerb.cs │ ├── Implementations/ │ │ ├── LiquidControllerBase.cs │ │ └── LiquidNotificationHelper.cs │ ├── Interfaces/ │ │ └── ILiquidNotificationHelper.cs │ ├── Liquid.WebApi.Http.csproj │ ├── Middlewares/ │ │ ├── LiquidContextMiddleware.cs │ │ ├── LiquidCultureMiddleware.cs │ │ ├── LiquidExceptionMiddleware.cs │ │ └── LiquidScopedLoggingMiddleware.cs │ └── Settings/ │ └── SwaggerSettings.cs ├── templates/ │ ├── Liquid.Templates.sln │ └── src/ │ └── Liquid.Templates/ │ ├── Liquid.Templates.csproj │ └── Templates/ │ ├── Liquid.Crud.AddEntity/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ ├── PROJECTNAME.Domain/ │ │ │ ├── Entities/ │ │ │ │ └── ENTITYNAME.cs │ │ │ └── Handlers/ │ │ │ ├── CreateENTITYNAME/ │ │ │ │ ├── CreateENTITYNAMEHandler.cs │ │ │ │ ├── CreateENTITYNAMERequest.cs │ │ │ │ └── CreateENTITYNAMEValidator.cs │ │ │ ├── ListENTITYNAME/ │ │ │ │ ├── ListENTITYNAMEHandler.cs │ │ │ │ ├── ListENTITYNAMERequest.cs │ │ │ │ └── ListENTITYNAMEResponse.cs │ │ │ ├── ReadENTITYNAME/ │ │ │ │ ├── ReadENTITYNAMEHandler.cs │ │ │ │ ├── ReadENTITYNAMERequest.cs │ │ │ │ └── ReadENTITYNAMEResponse.cs │ │ │ ├── RemoveENTITYNAME/ │ │ │ │ ├── RemoveENTITYNAMEHandler.cs │ │ │ │ ├── RemoveENTITYNAMERequest.cs │ │ │ │ └── RemoveENTITYNAMEResponse.cs │ │ │ └── UpdateENTITYNAME/ │ │ │ ├── UpdateENTITYNAMEHandler.cs │ │ │ ├── UpdateENTITYNAMERequest.cs │ │ │ ├── UpdateENTITYNAMEResponse.cs │ │ │ └── UpdateENTITYNAMEValidator.cs │ │ └── PROJECTNAME.WebApi/ │ │ └── Controllers/ │ │ └── ENTITYNAMEController.cs │ ├── Liquid.Crud.Solution/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── src/ │ │ ├── PROJECTNAME.Domain/ │ │ │ ├── Entities/ │ │ │ │ └── ENTITYNAME.cs │ │ │ ├── Handlers/ │ │ │ │ ├── CreateENTITYNAME/ │ │ │ │ │ ├── CreateENTITYNAMEHandler.cs │ │ │ │ │ ├── CreateENTITYNAMERequest.cs │ │ │ │ │ └── CreateENTITYNAMEValidator.cs │ │ │ │ ├── ListENTITYNAME/ │ │ │ │ │ ├── ListENTITYNAMEHandler.cs │ │ │ │ │ ├── ListENTITYNAMERequest.cs │ │ │ │ │ └── ListENTITYNAMEResponse.cs │ │ │ │ ├── ReadENTITYNAME/ │ │ │ │ │ ├── ReadENTITYNAMEHandler.cs │ │ │ │ │ ├── ReadENTITYNAMERequest.cs │ │ │ │ │ └── ReadENTITYNAMEResponse.cs │ │ │ │ ├── RemoveENTITYNAME/ │ │ │ │ │ ├── RemoveENTITYNAMEHandler.cs │ │ │ │ │ ├── RemoveENTITYNAMERequest.cs │ │ │ │ │ └── RemoveENTITYNAMEResponse.cs │ │ │ │ └── UpdateENTITYNAME/ │ │ │ │ ├── UpdateENTITYNAMEHandler.cs │ │ │ │ ├── UpdateENTITYNAMERequest.cs │ │ │ │ ├── UpdateENTITYNAMEResponse.cs │ │ │ │ └── UpdateENTITYNAMEValidator.cs │ │ │ ├── IDomainInjection.cs │ │ │ └── PROJECTNAME.Domain.csproj │ │ ├── PROJECTNAME.Microservice.sln │ │ └── PROJECTNAME.WebApi/ │ │ ├── Controllers/ │ │ │ └── ENTITYNAMEController.cs │ │ ├── PROJECTNAME.WebApi.csproj │ │ ├── PROJECTNAME.WebApi.http │ │ ├── Program.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── Liquid.DbContext.AddEntity/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.Repository/ │ │ └── Configurations/ │ │ └── ENTITYNAMEConfiguration.cs │ ├── Liquid.DbContext.Project/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.Repository/ │ │ ├── Configurations/ │ │ │ └── ENTITYNAMEConfiguration.cs │ │ ├── LiquidDbContext.cs │ │ └── PROJECTNAME.Repository.csproj │ ├── Liquid.Domain.AddHandler/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.Domain/ │ │ └── Handlers/ │ │ └── COMMANDNAMEENTITYNAME/ │ │ ├── COMMANDNAMEENTITYNAMEHandler.cs │ │ ├── COMMANDNAMEENTITYNAMERequest.cs │ │ ├── COMMANDNAMEENTITYNAMEResponse.cs │ │ └── COMMANDNAMEENTITYNAMEValidator.cs │ ├── Liquid.Domain.Project/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.Domain/ │ │ ├── Entities/ │ │ │ └── ENTITYNAME.cs │ │ ├── Handlers/ │ │ │ └── COMMANDNAMEENTITYNAME/ │ │ │ ├── COMMANDNAMEENTITYNAMEHandler.cs │ │ │ ├── COMMANDNAMEENTITYNAMERequest.cs │ │ │ ├── COMMANDNAMEENTITYNAMEResponse.cs │ │ │ └── COMMANDNAMEENTITYNAMEValidator.cs │ │ └── PROJECTNAME.Domain.csproj │ ├── Liquid.WebApi.AddEntity/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ ├── PROJECTNAME.Domain/ │ │ │ ├── Entities/ │ │ │ │ └── ENTITYNAME.cs │ │ │ └── Handlers/ │ │ │ └── COMMANDNAMEENTITYNAME/ │ │ │ ├── COMMANDNAMEENTITYNAMEHandler.cs │ │ │ ├── COMMANDNAMEENTITYNAMERequest.cs │ │ │ ├── COMMANDNAMEENTITYNAMEResponse.cs │ │ │ └── COMMANDNAMEENTITYNAMEValidator.cs │ │ └── PROJECTNAME.WebApi/ │ │ └── Controllers/ │ │ └── ENTITYNAMEController.cs │ ├── Liquid.WebApi.Project/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.WebApi/ │ │ ├── Controllers/ │ │ │ └── ENTITYNAMEController.cs │ │ ├── PROJECTNAME.WebApi.csproj │ │ ├── PROJECTNAME.WebApi.http │ │ ├── Program.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── Liquid.WebApi.Solution/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── src/ │ │ ├── PROJECTNAME.Domain/ │ │ │ ├── Entities/ │ │ │ │ └── ENTITYNAME.cs │ │ │ ├── Handlers/ │ │ │ │ └── COMMANDNAMEENTITYNAME/ │ │ │ │ ├── COMMANDNAMEENTITYNAMEHandler.cs │ │ │ │ ├── COMMANDNAMEENTITYNAMERequest.cs │ │ │ │ ├── COMMANDNAMEENTITYNAMEResponse.cs │ │ │ │ └── COMMANDNAMEENTITYNAMEValidator.cs │ │ │ └── PROJECTNAME.Domain.csproj │ │ ├── PROJECTNAME.Microservice.sln │ │ └── PROJECTNAME.WebApi/ │ │ ├── Controllers/ │ │ │ └── ENTITYNAMEController.cs │ │ ├── PROJECTNAME.WebApi.csproj │ │ ├── PROJECTNAME.WebApi.http │ │ ├── Program.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ ├── Liquid.WorkerService.Project/ │ │ ├── .template.config/ │ │ │ └── template.json │ │ └── PROJECTNAME.WorkerService/ │ │ ├── PROJECTNAME.WorkerService.csproj │ │ ├── Program.cs │ │ ├── Worker.cs │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ └── Liquid.WorkerService.Solution/ │ ├── .template.config/ │ │ └── template.json │ └── src/ │ ├── PROJECTNAME.Domain/ │ │ ├── Entities/ │ │ │ └── ENTITYNAME.cs │ │ ├── Handlers/ │ │ │ └── COMMANDNAMEENTITYNAME/ │ │ │ ├── COMMANDNAMEENTITYNAMEHandler.cs │ │ │ ├── COMMANDNAMEENTITYNAMERequest.cs │ │ │ ├── COMMANDNAMEENTITYNAMEResponse.cs │ │ │ └── COMMANDNAMEENTITYNAMEValidator.cs │ │ └── PROJECTNAME.Domain.csproj │ ├── PROJECTNAME.Microservice.sln │ └── PROJECTNAME.WorkerService/ │ ├── PROJECTNAME.WorkerService.csproj │ ├── Program.cs │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json └── test/ ├── CodeCoverage.runsettings ├── Liquid.Cache.Memory.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ └── Liquid.Cache.Memory.Tests.csproj ├── Liquid.Cache.NCache.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ ├── Liquid.Cache.NCache.Tests.csproj │ ├── client.ncconf │ ├── config.ncconf │ └── tls.ncconf ├── Liquid.Cache.Redis.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ └── Liquid.Cache.Redis.Tests.csproj ├── Liquid.Cache.SqlServer.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ └── Liquid.Cache.SqlServer.Tests.csproj ├── Liquid.Core.Telemetry.ElasticApm.Tests/ │ ├── Extensions/ │ │ └── IServiceCollectionExtension.cs │ ├── IApplicationBuilderExtensionsTests.cs │ ├── IServiceCollectionExtensionsTests.cs │ ├── Liquid.Core.Telemetry.ElasticApm.Tests.csproj │ ├── LiquidElasticApmInterceptorTests.cs │ ├── LiquidElasticApmTelemetryBehaviorTests.cs │ ├── Mocks/ │ │ ├── CommandHandlerMock.cs │ │ ├── IMockService.cs │ │ ├── MockService.cs │ │ ├── RequestMock.cs │ │ └── ResponseMock.cs │ └── Settings/ │ ├── ConfigurationSettings.cs │ ├── ElasticApmSettings.cs │ └── IElasticApmSettings.cs ├── Liquid.Core.Tests/ │ ├── Cache/ │ │ ├── IServiceCollectionExtensionTests.cs │ │ └── LiquidCacheTests.cs │ ├── CommandHandlers/ │ │ ├── Test1/ │ │ │ ├── Test1Command.cs │ │ │ ├── Test1CommandHandler.cs │ │ │ └── Test1Response.cs │ │ └── Test2/ │ │ ├── Test2Command.cs │ │ ├── Test2CommandHandler.cs │ │ ├── Test2CommandValidator.cs │ │ └── Test2Response.cs │ ├── Core/ │ │ ├── IServiceCollectionLiquidExtensionTest.cs │ │ ├── LiquidContextNotificationsTest.cs │ │ ├── LiquidContextTest.cs │ │ ├── LiquidJsonSerializerTest.cs │ │ ├── LiquidSerializerProviderTest.cs │ │ ├── LiquidTelemetryInterceptorTest.cs │ │ ├── LiquidXmlSerializerTest.cs │ │ └── LocalizationTest.cs │ ├── Domain/ │ │ └── RequestHandlerTest.cs │ ├── Liquid.Core.Tests.csproj │ ├── Messaging/ │ │ ├── IServiceCollectionExtensionTest.cs │ │ ├── LiquidBackgroundServiceTest.cs │ │ ├── LiquidContextDecoratorTest.cs │ │ ├── LiquidCultureDecoratorTest.cs │ │ └── LiquidScopedLoggingDecoratorTest.cs │ ├── Mocks/ │ │ ├── AnotherTestEntity.cs │ │ ├── CommandHandlerMock.cs │ │ ├── CommandRequestMock.cs │ │ ├── EntityMock.cs │ │ ├── IMockService.cs │ │ ├── InMemoryRepository.cs │ │ ├── MockInterceptService.cs │ │ ├── MockSerializeObject.cs │ │ ├── MockService.cs │ │ ├── MockSettings.cs │ │ ├── MockType.cs │ │ ├── TestEntity.cs │ │ └── WorkerMock.cs │ ├── Repository/ │ │ └── LiquidUnitOfWorkTests.cs │ ├── appsettings.json │ ├── client.ncconf │ ├── config.ncconf │ ├── localization.en-US.json │ ├── localization.es-ES.json │ ├── localization.json │ └── tls.ncconf ├── Liquid.Dataverse.Tests/ │ ├── DataverseClientFactoryTests.cs │ ├── Liquid.Dataverse.Tests.csproj │ ├── LiquidDataverseTests.cs │ └── Usings.cs ├── Liquid.GenAi.OpenAi.Tests/ │ ├── Liquid.GenAi.OpenAi.Tests.csproj │ └── OpenAiClientFactoryTests.cs ├── Liquid.Messaging.Kafka.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ ├── KafkaFactoryTest.cs │ ├── KafkaProducerTest.cs │ ├── Liquid.Messaging.Kafka.Tests.csproj │ ├── Mock/ │ │ ├── HandlerMock/ │ │ │ ├── MockCommandHandler.cs │ │ │ ├── MockRequest.cs │ │ │ └── MockValidator.cs │ │ ├── MessageMock.cs │ │ ├── WorkerMediatorMock.cs │ │ └── WorkerMock .cs │ └── kafkaConsumerTest.cs ├── Liquid.Messaging.RabbitMq.Tests/ │ ├── IServiceCollectionExtensionTest.cs │ ├── Liquid.Messaging.RabbitMq.Tests.csproj │ ├── Mock/ │ │ ├── HandlerMock/ │ │ │ ├── MockCommandHandler.cs │ │ │ ├── MockRequest.cs │ │ │ └── MockValidator.cs │ │ ├── MessageMock.cs │ │ ├── WorkerMediatorMock.cs │ │ └── WorkerMock .cs │ ├── RabbitMqConsumerTest.cs │ └── RabbitMqProducerTest.cs ├── Liquid.Messaging.ServiceBus.Tests/ │ ├── Liquid.Messaging.ServiceBus.Tests.csproj │ ├── Mock/ │ │ └── EntityMock.cs │ ├── ServiceBusConsumerTest.cs │ ├── ServiceBusFactoryTest.cs │ ├── ServiceBusFactoryTests.cs │ └── ServiceBusProducerTest.cs ├── Liquid.Repository.EntityFramework.Tests/ │ ├── Configurations/ │ │ └── MockTypeConfiguration.cs │ ├── Entities/ │ │ ├── MockEntity.cs │ │ └── MockSubEntity.cs │ ├── EntityFrameworkDataContextTests.cs │ ├── EntityFrameworkRepositoryTest.cs │ ├── Liquid.Repository.EntityFramework.Tests.csproj │ └── MockDbContext.cs ├── Liquid.Repository.Mongo.Tests/ │ ├── IServiceCollectionExtensionsTests.cs │ ├── Liquid.Repository.Mongo.Tests.csproj │ ├── Mock/ │ │ ├── AnotherTestEntity.cs │ │ ├── AsyncCursorMock.cs │ │ └── TestEntity.cs │ ├── MongoClientFactoryTests.cs │ ├── MongoDataContextTests.cs │ └── MongoRepositoryTests.cs ├── Liquid.Repository.OData.Tests/ │ ├── GlobalUsings.cs │ ├── IServiceCollectionExtensionTests.cs │ ├── Liquid.Repository.OData.Tests.csproj │ ├── Mock/ │ │ ├── AnotherTestEntity.cs │ │ ├── MockPeople.cs │ │ ├── MyMockHttpMessageHandler.cs │ │ └── TestEntity.cs │ ├── ODataClientFactoryTests.cs │ └── ODataRepositoryTests.cs ├── Liquid.Storage.AzureStorage.Tests/ │ ├── BlobClientFactoryTests.cs │ ├── Liquid.Storage.AzureStorage.Tests.csproj │ ├── LiquidStorageAzureTests.cs │ └── Usings.cs └── Liquid.WebApi.Http.Tests/ ├── Liquid.WebApi.Http.Tests.csproj ├── LiquidControllerBaseTest.cs ├── LiquidNotificationHelperTest.cs └── Mocks/ ├── Handlers/ │ ├── TestCaseQueryHandler.cs │ ├── TestCaseRequest.cs │ └── TestCaseResponse.cs ├── TestController.cs └── TestNotificationController.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*.cs] # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. dotnet_diagnostic.CS8618.severity = silent ================================================ FILE: .github/dco.yml ================================================ allowRemediationCommits: individual: true ================================================ FILE: .github/workflows/base-liquid-ci-and-cd.yml ================================================ # CI & CD workflow name: CI/CD - Base Reusable CI & CD workflow used by Liquid Application Framework components on: # Allows this workflow to be called by other workflows workflow_call: inputs: component_name: description: 'The component name to build' required: true type: string secrets: sonar_token: required: true nuget_token: required: false jobs: build: runs-on: ubuntu-latest steps: - name: Identify Job Type from workflow trigger event type run: | if [ "$GITHUB_EVENT_NAME" == "push" ] then echo "Starting CD Job: Build, Test, Analyze, Pack and Publish library to Nuget.org..." else echo "Starting CI Job: Build, Test and Analyze..." fi - name: (CI on PR) Checkout repo on Pull Request if: ${{ github.event_name == 'pull_request' }} uses: actions/checkout@v3 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} fetch-depth: 0 # required to eliminate shallow clone warning in Sonarcloud analysis - name: (CI/CD) Checkout repo if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v3 with: fetch-depth: 0 # required to eliminate shallow clone warning in Sonarcloud analysis # required by sonarcloud scanner - name: (CI/CD) Setup Java JDK if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') }} uses: actions/setup-java@v3 with: # The Java version to make available on the path. Takes a whole or semver Java version, or 1.x syntax (e.g. 1.8 => Java 8.x). Early access versions can be specified in the form of e.g. 14-ea, 14.0.0-ea, or 14.0.0-ea.28 distribution: 'microsoft' java-version: 17.x - name: (CI/CD) Setup .NET Core SDK uses: actions/setup-dotnet@v2 with: # SDK version to use. Examples: 2.2.104, 3.1, 3.1.x dotnet-version: | 3.1.x 5.0.x 6.0.x 8.0.x # required by sonarcloud scanner - name: (CI/CD) Setup Sonar Scanner tool if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') }} run: dotnet tool install --global dotnet-sonarscanner - name: (CI/CD) Install Test Reporting Tool run: dotnet tool install --global dotnet-reportgenerator-globaltool - name: (CI/CD) Restore dependencies run: dotnet restore src/${{ inputs.component_name }}/${{ inputs.component_name }}.csproj - name: (CI/CD) Build and Analyze Project env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.sonar_token }} RUN_SONAR: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') }} run: | if [ "$RUN_SONAR" == "true" ] then if [[ -d test/${{ inputs.component_name }}.Tests && -f test/${{ inputs.component_name }}.Tests/${{ inputs.component_name }}.Tests.csproj ]] then dotnet sonarscanner begin /k:"${{ inputs.component_name }}" /o:"avanade-1" /d:sonar.login="${{ secrets.sonar_token }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vstest.reportsPaths=$GITHUB_WORKSPACE/test/testresults/*.trx /d:sonar.coverageReportPaths=$GITHUB_WORKSPACE/test/coverlet/reports/SonarQube.xml else dotnet sonarscanner begin /k:"${{ inputs.component_name }}" /o:"avanade-1" /d:sonar.login="${{ secrets.sonar_token }}" /d:sonar.host.url="https://sonarcloud.io" fi fi dotnet build src/${{ inputs.component_name }}/${{ inputs.component_name }}.csproj --configuration Release --no-restore if [[ -d test/${{ inputs.component_name }}.Tests && -f test/${{ inputs.component_name }}.Tests/${{ inputs.component_name }}.Tests.csproj ]] then dotnet test test/${{ inputs.component_name }}.Tests/${{ inputs.component_name }}.Tests.csproj --collect:"XPlat Code Coverage" --logger trx --results-directory $GITHUB_WORKSPACE/test/testresults reportgenerator -reports:$GITHUB_WORKSPACE/test/testresults/**/coverage.cobertura.xml -targetdir:$GITHUB_WORKSPACE/test/coverlet/reports -reporttypes:"SonarQube" fi if [ "$RUN_SONAR" == "true" ] then dotnet sonarscanner end /d:sonar.login="${{ secrets.sonar_token }}" fi - name: (CD) Nuget Pack & Push to Nuget.org if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} run: | dotnet pack --no-build --configuration Release src/${{ inputs.component_name }}/${{ inputs.component_name }}.csproj --output . dotnet nuget push *.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.nuget_token }} --skip-duplicate ================================================ FILE: .github/workflows/liquid-ci-cd-cache-memory.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Cache.Memory component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Cache.Memory/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Cache.Memory/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Cache.Memory secrets: sonar_token: ${{ secrets.SONAR_TOKEN_CACHE_MEMORY }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-cache-ncache.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Cache.NCache component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Cache.NCache/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Cache.NCache/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Cache.NCache secrets: sonar_token: ${{ secrets.SONAR_TOKEN_CACHE_NCACHE }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-cache-redis.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Cache.Redis component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Cache.Redis/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Cache.Redis/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Cache.Redis secrets: sonar_token: ${{ secrets.SONAR_TOKEN_CACHE_REDIS }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-cache-sqlserver.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Cache.SqlServer component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Cache.SqlServer/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Cache.SqlServer/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Cache.SqlServer secrets: sonar_token: ${{ secrets.SONAR_TOKEN_CACHE_SQLSERVER }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-core-telemetry-elasticapm.yaml ================================================ # CI & CD workflow name: CI/CD - Liquid.Core.Telemetry.ElasticApm component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Core.Telemetry.ElasticApm/**' pull_request: branches: [ main ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Core.Telemetry.ElasticApm/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Core.Telemetry.ElasticApm secrets: sonar_token: ${{ secrets.SONAR_TOKEN_ELASTICAPM }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-core.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Core component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Core/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Core/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Core secrets: sonar_token: ${{ secrets.SONAR_TOKEN_CORE }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-dataverse.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Dataverse component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Dataverse/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Dataverse/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Dataverse secrets: sonar_token: ${{ secrets.SONAR_TOKEN_DATAVERSE }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-genai-openai.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.GenAi.OpenAi component for Liquid Application Framework on: push: branches: [ main ] paths: - 'src/Liquid.GenAi.OpenAi/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.GenAi.OpenAi/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.GenAi.OpenAi secrets: sonar_token: ${{ secrets.SONAR_TOKEN_OPENAI }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-http.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.WebApi.Http component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.WebApi.Http/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.WebApi.Http/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.WebApi.Http secrets: sonar_token: ${{ secrets.SONAR_TOKEN_HTTP }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-messaging-kafka.yaml ================================================ # CI & CD workflow name: CI/CD - Liquid.Messaging.Kafka cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Messaging.Kafka/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Messaging.Kafka/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Messaging.Kafka secrets: sonar_token: ${{ secrets.SONAR_TOKEN_KAFKA }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-messaging-rabbitmq.yaml ================================================ # CI & CD workflow name: CI/CD - Liquid.Messaging.RabbitMq cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Messaging.RabbitMq/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Messaging.RabbitMq/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Messaging.RabbitMq secrets: sonar_token: ${{ secrets.SONAR_TOKEN_RABBIT }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-messaging-servicebus.yaml ================================================ # CI & CD workflow name: CI/CD - Liquid.Messaging.ServiceBus cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Messaging.ServiceBus/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Messaging.ServiceBus/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Messaging.ServiceBus secrets: sonar_token: ${{ secrets.SONAR_TOKEN_SB }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-repository-entityframework.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Repository.EntityFramework Cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Repository.EntityFramework/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Repository.EntityFramework/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Repository.EntityFramework secrets: sonar_token: ${{ secrets.SONAR_TOKEN_EF }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-repository-mongodb.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Repository.Mongo Cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Repository.Mongo/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Repository.Mongo/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Repository.Mongo secrets: sonar_token: ${{ secrets.SONAR_TOKEN_MONGO }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-repository-odata.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Repository.OData Cartridge for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Repository.OData/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Repository.OData/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Repository.OData secrets: sonar_token: ${{ secrets.SONAR_TOKEN_ODATA }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-storage-azurestorage.yml ================================================ # CI & CD workflow name: CI/CD - Liquid.Storage.AzureStorage component for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'src/Liquid.Storage.AzureStorage/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'src/Liquid.Storage.AzureStorage/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: call-reusable-build-workflow: uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main with: component_name: Liquid.Storage.AzureStorage secrets: sonar_token: ${{ secrets.SONAR_TOKEN_STORAGE }} nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }} ================================================ FILE: .github/workflows/liquid-ci-cd-templates.yaml ================================================ # CI & CD workflow name: CI/CD - Liquid.Templates for Liquid Application Framework on: push: branches: [ main, releases/v2.X.X, releases/v6.X.X ] paths: - 'templates/src/Liquid.Templates/**' pull_request: branches: [ main, releases/** ] types: [opened, synchronize, reopened] paths: - 'templates/src/Liquid.Templates/**' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - name: Job Type run: | if [$GITHUB_EVENT_NAME == 'push'] then echo "Starting CD Job: Build, Test, Analyze, Pack and Publish library to Nuget.org..." else echo "Starting CI Job: Build, Test and Analyze..." fi - name: (CI) Checkout repo on Pull Request if: ${{ github.event_name == 'pull_request' }} uses: actions/checkout@v2 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} fetch-depth: 0 # required to eliminate shallow clone warning in Sonarcloud analysis - name: (CI/CD) Checkout repo if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v2 with: fetch-depth: 0 # required to eliminate shallow clone warning in Sonarcloud analysis - name: (CI/CD) Setup .NET Core SDK uses: actions/setup-dotnet@v1.7.2 with: # SDK version to use. Examples: 2.2.104, 3.1, 3.1.x dotnet-version: 3.1.x - name: (CI/CD) Restore dependencies run: dotnet restore templates/src/Liquid.Templates/Liquid.Templates.csproj - name: (CI/CD) Build and Analyze Project run: | dotnet build templates/src/Liquid.Templates/Liquid.Templates.csproj --configuration Release --no-restore - name: (CD) Nuget Pack & Push to Nuget.org if: ${{ github.event_name == 'push' }} run: | dotnet pack --no-build --configuration Release templates/src/Liquid.Templates/Liquid.Templates.csproj --output . dotnet nuget push *.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{secrets.PUBLISH_TO_NUGET_ORG}} --skip-duplicate ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute This project welcomes contributions and suggestions. By contributing, you confirm that you have the right to, and actually do, grant us the rights to use your contribution. More information below. Please feel free to contribute code, ideas, improvements, and patches - we've added some general guidelines and information below, and you can propose changes to this document in a pull request. This project has adopted the [Contributor Covenant Code of Conduct](https://avanade.github.io/code-of-conduct/). One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes. ## Rights to your contributions By contributing to this project, you: - Agree that you have authored 100% of the content - Agree that you have the necessary rights to the content - Agree that you have received the necessary permissions from your employer to make the contributions (if applicable) - Agree that the content you contribute may be provided under the Project license(s) - Agree that, if you did not author 100% of the content, the appropriate licenses and copyrights have been added along with any other necessary attribution. ## Code of Conduct This project, and people participating in it, are governed by our [code of conduct](https://avanade.github.io/code-of-conduct/). By taking part, we expect you to try your best to uphold this code of conduct. If you have concerns about unacceptable behaviour, please contact the community leaders responsible for enforcement at [ospo@avanade.com](ospo@avanade.com). ## Developer Certificate of Origin (DCO) Avanade asks that all commits sign the [Developer Certificate of Origin](https://developercertificate.org/), to ensure that every developer is confirming that they have the right to upload the code they submit. Sign-offs are added to the commit. Git has a `-s` command line option to append this automatically to your commit message, and [sign offs can be added through the web interface](https://github.blog/changelog/2022-06-08-admins-can-require-sign-off-on-web-based-commits/). ## General feedback and discussions? Start a discussion on the [repository issue tracker](https://github.com/Avanade/Liquid-Application-Framework/issues). ## Bugs and feature requests? For non-security related bugs, log a new issue in the GitHub repository. ## Reporting security issues and bugs Security issues and bugs should be reported privately, via email, to a repository admin. You should receive a response within 24 hours. We also ask you to file via our [security disclosure](https://github.com/Avanade/avanade-template/blob/main/SECURITY.md) policy. ## Contributing code and content We accept fixes and features! Here are some resources to help you get started on how to contribute code or new content. * ["Help wanted" issues](https://github.com/Avanade/Liquid-Application-Framework/labels/help%20wanted) - these issues are up for grabs. Comment on an issue if you want to create a fix. * ["Good first issue" issues](https://github.com/Avanade/Liquid-Application-Framework/labels/good%20first%20issue) - we think these are a good for newcomers. ### Identifying the scale If you would like to contribute to one of our repositories, first identify the scale of what you would like to contribute. If it is small (grammar/spelling or a bug fix) feel free to start working on a fix. If you are submitting a feature or substantial code contribution, please discuss it with the team and ensure it follows the product roadmap. You might also read these two blogs posts on contributing code: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don't "Push" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik. All code submissions will be rigorously reviewed and tested by the team, and only those that meet an extremely high bar for both quality and design/roadmap appropriateness will be merged into the source. ### Submitting a pull request If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. **Make sure the repository can build and all tests pass.** Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are below on the topic [Engineering guidelines](#Engineering-guidelines). ### Feedback Your pull request will now go through **extensive checks** by the subject matter experts on our team. Please be patient. Update your pull request according to feedback until it is approved by one of the ASP.NET team members. After that, one of our team members may adjust the branch you merge into based on the expected release schedule. # Engineering Guidelines ## General We try to hold our code to the higher standards. Every pull request must and will be scrutinized in order to maintain our standard. Every pull request should improve on quality, therefore any PR that decreases the quality will not be approved. We follow coding best practices to make sure the codebase is clean and newcomers and seniors alike will understand the code. This document provides a guideline that should make most of our practices clear, but gray areas may arise and we might make a judgment call on your code, so, when in doubt, question ahead. Issues are a wonderful tool that should be used by every contributor to help us drive the project. All the rules here are mandatory; however, we do not claim to hold all the answers - you can raise a question over any rule any time (through issues) and we'll discuss it. Finally, this is a new project and we are still learning how to work on a Open Source project. Please bear with us while we learn how to do it best. ## External dependencies This refers to dependencies on projects (i.e. NuGet packages) outside of the repo. Every dependency we add enlarges the package, and might have copyright issues that we may need to address. Therefore, adding or updating any external dependency requires approval. This can be discussed preferrabily on the issue related to the PR or on the PR itself. ## Code reviews and checkins To help ensure that only the highest quality code makes its way into the project, please submit all your code changes to GitHub as PRs. This includes runtime code changes, unit test updates, and updates to official samples. For example, sending a PR for just an update to a unit test might seem like a waste of time but the unit tests are just as important as the product code and as such, reviewing changes to them is also just as important. This also helps create visibility for your changes so that others can observe what is going on. The advantages are numerous: improving code quality, more visibility on changes and their potential impact, avoiding duplication of effort, and creating general awareness of progress being made in various areas. To commit the PR to the repo either use preferably GitHub's "Squash and Merge" button on the main PR page, or do a typical push that you would use with Git (e.g. local pull, rebase, merge, push). ## Branching Strategy We are using [Trunk-Based branching strategy](https://trunkbaseddevelopment.com/). ## Assembly naming pattern The general naming pattern is `Liquid..`. ## Unit tests We use NUnit for all unit testing. Additionally, you can use NSubstitute for mocks and such, and leverage AutoFixture for anonymous instances. ## Code Style ### General The most general guideline is that we use all the VS default settings in terms of code formatting, except that we put System namespaces before other namespaces (this used to be the default in VS, but it changed in a more recent version of VS). Also, we are leveraging StyleCop to add to code standardization. 1. Use four spaces of indentation (no tabs) 1. Use `_camelCase` for private fields 1. Avoid `this`. unless absolutely necessary 1. Always specify member visibility, even if it's the default (i.e. `private string _foo;` not `string _foo;`) 1. Open-braces (`{`) go on a new line 1. Use any language features available to you (expression-bodied members, throw expressions, tuples, etc.) as long as they make for readable, manageable code. This is pretty bad: `public (int, string) GetData(string filter) => (Data.Status, Data.GetWithFilter(filter ?? throw new ArgumentNullException(nameof(filter))));` ### Usage of the var keyword The var keyword is to be used as much as the compiler will allow. For example, these are correct: ```csharp var fruit = "Lychee"; var fruits = new List(); var flavor = fruit.GetFlavor(); string fruit = null; // can't use "var" because the type isn't known (though you could do (string)null, don't!) const string expectedName = "name"; // can't use "var" with const ``` The following are incorrect: ```csharp string fruit = "Lychee"; List fruits = new List(); FruitFlavor flavor = fruit.GetFlavor(); ``` ### Use C# type keywords in favor of .NET type names When using a type that has a C# keyword the keyword is used in favor of the .NET type name. For example, these are correct: ```csharp public string TrimString(string s) { return string.IsNullOrEmpty(s) ? null : s.Trim(); } var intTypeName = nameof(Int32); // can't use C# type keywords with nameof ``` The following are incorrect: ```csharp public String TrimString(String s) { return String.IsNullOrEmpty(s) ? null : s.Trim(); } ``` ### Cross-platform coding Our frameworks should work on CoreCLR, which supports multiple operating systems. Don't assume we only run (and develop) on Windows. Code should be sensitive to the differences between OS's. Here are some specifics to consider. #### Line breaks Windows uses \r\n, OS X and Linux uses \n. When it is important, use Environment.NewLine instead of hard-coding the line break. Note: this may not always be possible or necessary. Be aware that these line-endings may cause problems in code when using @"" text blocks with line breaks. #### Environment Variables OS's use different variable names to represent similar settings. Code should consider these differences. For example, when looking for the user's home directory, on Windows the variable is USERPROFILE but on most Linux systems it is HOME. ```csharp var homeDir = Environment.GetEnvironmentVariable("USERPROFILE") ?? Environment.GetEnvironmentVariable("HOME"); ``` #### File path separators Windows uses \ and OS X and Linux use / to separate directories. Instead of hard-coding either type of slash, use Path.Combine() or Path.DirectorySeparatorChar. If this is not possible (such as in scripting), use a forward slash. Windows is more forgiving than Linux in this regard. ### When to use internals vs. public and when to use InternalsVisibleTo As a modern set of frameworks, usage of internal types and members is allowed, but discouraged. `InternalsVisibleTo` is used only to allow a unit test to test internal types and members of its runtime assembly. We do not use `InternalsVisibleTo` between two runtime assemblies. If two runtime assemblies need to share common helpers then we will use a "shared source" solution with build-time only packages. If two runtime assemblies need to call each other's APIs, the APIs must be public. If we need it, it is likely that our customers need it. ### Async method patterns By default all async methods must have the Async suffix. There are some exceptional circumstances where a method name from a previous framework will be grandfathered in. Passing cancellation tokens is done with an optional parameter with a value of `default(CancellationToken)`, which is equivalent to `CancellationToken.None` (one of the few places that we use optional parameters). The main exception to this is in web scenarios where there is already an `HttpContext` being passed around, in which case the context has its own cancellation token that can be used when needed. Sample async method: ```csharp public Task GetDataAsync( QueryParams query, int maxData, CancellationToken cancellationToken = default(CancellationToken)) { ... } ``` ### Extension method patterns The general rule is: if a regular static method would suffice, avoid extension methods. Extension methods are often useful to create chainable method calls, for example, when constructing complex objects, or creating queries. Internal extension methods are allowed, but bear in mind the previous guideline: ask yourself if an extension method is truly the most appropriate pattern. The namespace of the extension method class should generally be the namespace that represents the functionality of the extension method, as opposed to the namespace of the target type. One common exception to this is that the namespace for middleware extension methods is normally always the same is the namespace of IAppBuilder. The class name of an extension method container (also known as a "sponsor type") should generally follow the pattern of `Extensions`, `Extensions`, or `Extensions`. For example: ```csharp namespace Food { class Fruit { ... } } namespace Fruit.Eating { class FruitExtensions { public static void Eat(this Fruit fruit); } OR class FruitEatingExtensions { public static void Eat(this Fruit fruit); } OR class EatingFruitExtensions { public static void Eat(this Fruit fruit); } } ``` When writing extension methods for an interface the sponsor type name must not start with an I. ### Analyzers Code style is usually enforced by Analyzers; any change to those rules must be discussed with the team before it's made. Also, any pull request that changes analyzers rules and commits code will be reproved immediately. ### Good practices The items below point out the good practices that all code should follow. #### Zero warnings Compiler warnings should usually be dealt with by correcting the code. Only discussed warnings may be allowed to be marked as exceptions. #### Inner documentation All public members must be documented. Documentation should clarify the purpose and usage of code elements, so comments such as FooManager: "manages foo" will be rejected. Classes that implement interface may use comment inheritance `/// `, but use it sparingly. Try to use examples and such in classes to enable users to understand them more easily. If you don't believe that a class or method deserves to be documents, ask yourself if it can be marked as non-public. If should comment every non-public class or member that is complex enough. All comments should be read-proof. > **Note**: Public means callable by a customer, so it includes protected APIs. However, some public APIs might still be "for internal use only" but need to be public for technical reasons. We will still have doc comments for these APIs but they will be documented as appropriate. ## Tests - Tests need to be provided for every bug/feature that is completed. - Tests only need to be present for issues that need to be verified by QA (for example, not tasks) - Pull requests must strive to not reduce code coverage. - If there is a scenario that is far too hard to test there does not need to be a test for it. - "Too hard" is determined by the team as a whole. ### Unit tests and functional tests #### Assembly naming The unit tests for the `Liquid.Fruit` assembly live in the `Liquid.Fruit.Tests` assembly. The functional tests for the `Liquid.Fruit` assembly live in the `Liquid.Fruit.AcceptanceTests` assembly. In general there should be exactly one unit test assembly for each product runtime assembly. In general there should be one functional test assembly per repo. Exceptions can be made for both. #### Unit test class naming Test class names end with Test and live in the same namespace as the class being tested. For example, the unit tests for the Liquid.Fruit.Banana class would be in a Liquid.Fruit.BananaTest class in the test assembly. #### Unit test method naming Unit test method names must be descriptive about what is being tested, under what conditions, and what the expectations are. Pascal casing and underscores can be used to improve readability. We will try to follow [Roy Osherove's guidelines](https://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html), therefore, the following test names are correct: `GetAttachmentWhenItWasInsertedReturnsInsertedData` `GetWhenAttachmentDoesntExistsThrows` > Notice how we use When and Returns / Throws to split the name into recognizable parts. The following test names are incorrect: ```csharp Test1 Constructor FormatString GetData ``` #### Unit test structure The contents of every unit test should be split into three distinct stages, optionally separated by these comments: ```csharp // Arrange // Act // Assert ``` The crucial thing here is that the Act stage is exactly one statement. That one statement is nothing more than a call to the one method that you are trying to test. Keeping that one statement as simple as possible is also very important. For example, this is not ideal: ```csharp int result = myObj.CallSomeMethod(GetComplexParam1(), GetComplexParam2(), GetComplexParam3()); ``` This style is not recommended because way too many things can go wrong in this one statement. All the `GetComplexParamN()` calls can throw for a variety of reasons unrelated to the test itself. It is thus unclear to someone running into a problem why the failure occurred. The ideal pattern is to move the complex parameter building into the Arrange section: ```csharp // Arrange P1 p1 = GetComplexParam1(); P2 p2 = GetComplexParam2(); P3 p3 = GetComplexParam3(); // Act int result = myObj.CallSomeMethod(p1, p2, p3); // Assert Assert.AreEqual(1234, result); ``` Now the only reason the line with `CallSomeMethod()` can fail is if the method itself blew up. This is especially important when you're using helpers such as ExceptionHelper, where the delegate you pass into it must fail for exactly one reason. #### Use NUnit's plethora of built-in assertions NUnit includes many kinds of assertions – please use the most appropriate one for your test. This will make the tests a lot more readable and also allow the test runner report the best possible errors (whether it's local or the CI machine). For example, these are bad: ```csharp Assert.AreEqual(true, someBool); Assert.True("abc123" == someString); Assert.True(list1.Length == list2.Length); for (int i = 0; i < list1.Length; i++) { Assert.True( String.Equals list1[i], list2[i], StringComparison.OrdinalIgnoreCase)); } ``` These are good: ```csharp Assert.True(someBool); Assert.AreEqual("abc123", someString); // built-in collection assertions! Assert.AreEqual(list1, list2, StringComparer.OrdinalIgnoreCase); ``` It's also possible (and recommended) to use the new ContraintModel of NUnit: ```csharp Assert.That(condition, Is.True); int[] array = new int[] { 1, 2, 3 }; Assert.That(array, Has.Exactly(1).EqualTo(3)); Assert.That(array, Has.Exactly(2).GreaterThan(1)); Assert.That(array, Has.Exactly(3).LessThan(100)); ``` #### Parallel tests By default all unit test assemblies should run in parallel mode, which is the default. Unit tests shouldn't depend on any shared state, and so should generally be runnable in parallel. If the tests fail in parallel, the first thing to do is to figure out why; do not just disable parallel tests! For functional tests it is reasonable to disable parallel tests. ### Use only complete words or common/standard abbreviations in public APIs Public namespaces, type names, member names, and parameter names must use complete words or common/standard abbreviations. These are correct: ```csharp public void AddReference(AssemblyReference reference); public EcmaScriptObject SomeObject { get; } ``` These are incorrect: ```csharp public void AddRef(AssemblyReference ref); public EcmaScriptObject SomeObj { get; } ``` Note that abbreviations still follow camel/pascal casing. Use `Api` instead of `API` and `Ecma` instead of `ECMA`, to make your code more legible. Avoid abbreviations with only two characters because of the rule above. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Avanade Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Liquid.Adapters.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.34202.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse", "src\Liquid.Adapter.Dataverse\Liquid.Adapter.Dataverse.csproj", "{02191AB8-D13C-4CCC-8455-08813FB0C9C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse.Tests", "test\Liquid.Adapter.Dataverse.Tests\Liquid.Adapter.Dataverse.Tests.csproj", "{90D60966-6004-4705-907D-780503EBC141}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dataverse", "Dataverse", "{0E973865-5B87-43F2-B513-CD1DA96A2A3A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureStorage", "AzureStorage", "{81CB75D9-FC31-4533-9A2D-C9277DD4A33E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.AzureStorage", "src\Liquid.Adapter.AzureStorage\Liquid.Adapter.AzureStorage.csproj", "{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Adapter.AzureStorage.Tests", "test\Liquid.Adapter.AzureStorage.Tests\Liquid.Adapter.AzureStorage.Tests.csproj", "{A2A7E164-98DF-4953-9679-B35E109E8990}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {02191AB8-D13C-4CCC-8455-08813FB0C9C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {02191AB8-D13C-4CCC-8455-08813FB0C9C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {02191AB8-D13C-4CCC-8455-08813FB0C9C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {02191AB8-D13C-4CCC-8455-08813FB0C9C3}.Release|Any CPU.Build.0 = Release|Any CPU {90D60966-6004-4705-907D-780503EBC141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90D60966-6004-4705-907D-780503EBC141}.Debug|Any CPU.Build.0 = Debug|Any CPU {90D60966-6004-4705-907D-780503EBC141}.Release|Any CPU.ActiveCfg = Release|Any CPU {90D60966-6004-4705-907D-780503EBC141}.Release|Any CPU.Build.0 = Release|Any CPU {E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Release|Any CPU.Build.0 = Release|Any CPU {A2A7E164-98DF-4953-9679-B35E109E8990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A2A7E164-98DF-4953-9679-B35E109E8990}.Debug|Any CPU.Build.0 = Debug|Any CPU {A2A7E164-98DF-4953-9679-B35E109E8990}.Release|Any CPU.ActiveCfg = Release|Any CPU {A2A7E164-98DF-4953-9679-B35E109E8990}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {02191AB8-D13C-4CCC-8455-08813FB0C9C3} = {0E973865-5B87-43F2-B513-CD1DA96A2A3A} {90D60966-6004-4705-907D-780503EBC141} = {0E973865-5B87-43F2-B513-CD1DA96A2A3A} {E21AF05A-738E-4DA2-AEEE-9900D7534F7C} = {81CB75D9-FC31-4533-9A2D-C9277DD4A33E} {A2A7E164-98DF-4953-9679-B35E109E8990} = {81CB75D9-FC31-4533-9A2D-C9277DD4A33E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8105640B-39D2-4FFE-8BBD-D8E37F2A070D} EndGlobalSection EndGlobal ================================================ FILE: Liquid.Application.Framework.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Core", "src\Liquid.Core\Liquid.Core.csproj", "{C33A89FC-4F4D-4274-8D0F-29456BA8F76B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EE1C8DCF-EBC3-43B8-9E9C-433CDD57FB30}" ProjectSection(SolutionItems) = preProject test\CodeCoverage.runsettings = test\CodeCoverage.runsettings .github\workflows\liquid-core-ci-cd-publish.yml = .github\workflows\liquid-core-ci-cd-publish.yml logo.png = logo.png nuget.config = nuget.config EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Core.Tests", "test\Liquid.Core.Tests\Liquid.Core.Tests.csproj", "{69B34B22-198D-4BD0-AAFC-39A90F9ADAC2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Core.Telemetry.ElasticApm", "src\Liquid.Core.Telemetry.ElasticApm\Liquid.Core.Telemetry.ElasticApm.csproj", "{AD354CF6-C132-4B5D-944D-0DFE21509781}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Core.Telemetry.ElasticApm.Tests", "test\Liquid.Core.Telemetry.ElasticApm.Tests\Liquid.Core.Telemetry.ElasticApm.Tests.csproj", "{8863A92B-E1A3-4203-8C08-C017E5B3D2CC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.Memory", "src\Liquid.Cache.Memory\Liquid.Cache.Memory.csproj", "{C7DD9DB1-E287-4886-AA44-159C0AB4F041}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.NCache", "src\Liquid.Cache.NCache\Liquid.Cache.NCache.csproj", "{74D0605F-5466-423D-B5ED-E2C104BB3180}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.Redis", "src\Liquid.Cache.Redis\Liquid.Cache.Redis.csproj", "{6508D946-4B74-4629-B819-4C2C3F7AF8B5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.SqlServer", "src\Liquid.Cache.SqlServer\Liquid.Cache.SqlServer.csproj", "{F24D06BE-E608-4DFD-B8F6-B02F87062323}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.Memory.Tests", "test\Liquid.Cache.Memory.Tests\Liquid.Cache.Memory.Tests.csproj", "{28747B1E-0784-4B7B-AE48-F5AD3C937262}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.NCache.Tests", "test\Liquid.Cache.NCache.Tests\Liquid.Cache.NCache.Tests.csproj", "{08A826AA-2C2E-4E21-A3D8-60BF8E57772E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.SqlServer.Tests", "test\Liquid.Cache.SqlServer.Tests\Liquid.Cache.SqlServer.Tests.csproj", "{ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Cache.Redis.Tests", "test\Liquid.Cache.Redis.Tests\Liquid.Cache.Redis.Tests.csproj", "{2E8F3332-545A-4454-9F75-1498A9B7B9BF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.Mongo.Tests", "test\Liquid.Repository.Mongo.Tests\Liquid.Repository.Mongo.Tests.csproj", "{400BD703-3E6F-41E6-929B-5FD888EFD00A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.EntityFramework.Tests", "test\Liquid.Repository.EntityFramework.Tests\Liquid.Repository.EntityFramework.Tests.csproj", "{73A5230F-0A90-4D2C-8D5E-D95C40DEF310}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.EntityFramework", "src\Liquid.Repository.EntityFramework\Liquid.Repository.EntityFramework.csproj", "{376DEF77-CBE3-452A-890C-FF2728D90322}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.Mongo", "src\Liquid.Repository.Mongo\Liquid.Repository.Mongo.csproj", "{D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.ServiceBus.Tests", "test\Liquid.Messaging.ServiceBus.Tests\Liquid.Messaging.ServiceBus.Tests.csproj", "{A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.ServiceBus", "src\Liquid.Messaging.ServiceBus\Liquid.Messaging.ServiceBus.csproj", "{C00DA46C-9614-4C42-AA89-64A3520F4EE7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.Kafka", "src\Liquid.Messaging.Kafka\Liquid.Messaging.Kafka.csproj", "{605AB027-CB0A-4B8E-8E20-55258369C497}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.Kafka.Tests", "test\Liquid.Messaging.Kafka.Tests\Liquid.Messaging.Kafka.Tests.csproj", "{0D7F5E5F-348D-4606-9CE4-F2FB220E12BA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.RabbitMq", "src\Liquid.Messaging.RabbitMq\Liquid.Messaging.RabbitMq.csproj", "{6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Messaging.RabbitMq.Tests", "test\Liquid.Messaging.RabbitMq.Tests\Liquid.Messaging.RabbitMq.Tests.csproj", "{40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.WebApi.Http", "src\Liquid.WebApi.Http\Liquid.WebApi.Http.csproj", "{25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.WebApi.Http.Tests", "test\Liquid.WebApi.Http.Tests\Liquid.WebApi.Http.Tests.csproj", "{DED98401-EAED-4BE0-84A9-494D77B0E70D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Storage.AzureStorage", "src\Liquid.Storage.AzureStorage\Liquid.Storage.AzureStorage.csproj", "{F599C512-7224-4A27-A474-3AA9510D2A14}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse", "src\Liquid.Dataverse\Liquid.Dataverse.csproj", "{CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Dataverse.Tests", "test\Liquid.Dataverse.Tests\Liquid.Dataverse.Tests.csproj", "{5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Storage.AzureStorage.Tests", "test\Liquid.Storage.AzureStorage.Tests\Liquid.Storage.AzureStorage.Tests.csproj", "{53341B04-6D30-4137-943B-20D8706351E8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.GenAi.OpenAi", "src\Liquid.GenAi.OpenAi\Liquid.GenAi.OpenAi.csproj", "{CB199ED6-3D1D-4E12-A15F-597B6A0BA564}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Repository.OData", "src\Liquid.Repository.OData\Liquid.Repository.OData.csproj", "{60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Repository.OData.Tests", "test\Liquid.Repository.OData.Tests\Liquid.Repository.OData.Tests.csproj", "{70A43D24-4905-4A16-8CE2-165F73243B8D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.GenAi.OpenAi.Tests", "test\Liquid.GenAi.OpenAi.Tests\Liquid.GenAi.OpenAi.Tests.csproj", "{ECDF1DD3-073D-4708-A20B-1F0F2D663065}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C33A89FC-4F4D-4274-8D0F-29456BA8F76B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C33A89FC-4F4D-4274-8D0F-29456BA8F76B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C33A89FC-4F4D-4274-8D0F-29456BA8F76B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C33A89FC-4F4D-4274-8D0F-29456BA8F76B}.Release|Any CPU.Build.0 = Release|Any CPU {69B34B22-198D-4BD0-AAFC-39A90F9ADAC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {69B34B22-198D-4BD0-AAFC-39A90F9ADAC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {69B34B22-198D-4BD0-AAFC-39A90F9ADAC2}.Release|Any CPU.ActiveCfg = Release|Any CPU {69B34B22-198D-4BD0-AAFC-39A90F9ADAC2}.Release|Any CPU.Build.0 = Release|Any CPU {AD354CF6-C132-4B5D-944D-0DFE21509781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD354CF6-C132-4B5D-944D-0DFE21509781}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD354CF6-C132-4B5D-944D-0DFE21509781}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD354CF6-C132-4B5D-944D-0DFE21509781}.Release|Any CPU.Build.0 = Release|Any CPU {8863A92B-E1A3-4203-8C08-C017E5B3D2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8863A92B-E1A3-4203-8C08-C017E5B3D2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {8863A92B-E1A3-4203-8C08-C017E5B3D2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8863A92B-E1A3-4203-8C08-C017E5B3D2CC}.Release|Any CPU.Build.0 = Release|Any CPU {C7DD9DB1-E287-4886-AA44-159C0AB4F041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7DD9DB1-E287-4886-AA44-159C0AB4F041}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7DD9DB1-E287-4886-AA44-159C0AB4F041}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7DD9DB1-E287-4886-AA44-159C0AB4F041}.Release|Any CPU.Build.0 = Release|Any CPU {74D0605F-5466-423D-B5ED-E2C104BB3180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {74D0605F-5466-423D-B5ED-E2C104BB3180}.Debug|Any CPU.Build.0 = Debug|Any CPU {74D0605F-5466-423D-B5ED-E2C104BB3180}.Release|Any CPU.ActiveCfg = Release|Any CPU {74D0605F-5466-423D-B5ED-E2C104BB3180}.Release|Any CPU.Build.0 = Release|Any CPU {6508D946-4B74-4629-B819-4C2C3F7AF8B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6508D946-4B74-4629-B819-4C2C3F7AF8B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {6508D946-4B74-4629-B819-4C2C3F7AF8B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {6508D946-4B74-4629-B819-4C2C3F7AF8B5}.Release|Any CPU.Build.0 = Release|Any CPU {F24D06BE-E608-4DFD-B8F6-B02F87062323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F24D06BE-E608-4DFD-B8F6-B02F87062323}.Debug|Any CPU.Build.0 = Debug|Any CPU {F24D06BE-E608-4DFD-B8F6-B02F87062323}.Release|Any CPU.ActiveCfg = Release|Any CPU {F24D06BE-E608-4DFD-B8F6-B02F87062323}.Release|Any CPU.Build.0 = Release|Any CPU {28747B1E-0784-4B7B-AE48-F5AD3C937262}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28747B1E-0784-4B7B-AE48-F5AD3C937262}.Debug|Any CPU.Build.0 = Debug|Any CPU {28747B1E-0784-4B7B-AE48-F5AD3C937262}.Release|Any CPU.ActiveCfg = Release|Any CPU {28747B1E-0784-4B7B-AE48-F5AD3C937262}.Release|Any CPU.Build.0 = Release|Any CPU {08A826AA-2C2E-4E21-A3D8-60BF8E57772E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08A826AA-2C2E-4E21-A3D8-60BF8E57772E}.Debug|Any CPU.Build.0 = Debug|Any CPU {08A826AA-2C2E-4E21-A3D8-60BF8E57772E}.Release|Any CPU.ActiveCfg = Release|Any CPU {08A826AA-2C2E-4E21-A3D8-60BF8E57772E}.Release|Any CPU.Build.0 = Release|Any CPU {ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4}.Release|Any CPU.Build.0 = Release|Any CPU {2E8F3332-545A-4454-9F75-1498A9B7B9BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E8F3332-545A-4454-9F75-1498A9B7B9BF}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E8F3332-545A-4454-9F75-1498A9B7B9BF}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E8F3332-545A-4454-9F75-1498A9B7B9BF}.Release|Any CPU.Build.0 = Release|Any CPU {400BD703-3E6F-41E6-929B-5FD888EFD00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {400BD703-3E6F-41E6-929B-5FD888EFD00A}.Debug|Any CPU.Build.0 = Debug|Any CPU {400BD703-3E6F-41E6-929B-5FD888EFD00A}.Release|Any CPU.ActiveCfg = Release|Any CPU {400BD703-3E6F-41E6-929B-5FD888EFD00A}.Release|Any CPU.Build.0 = Release|Any CPU {73A5230F-0A90-4D2C-8D5E-D95C40DEF310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73A5230F-0A90-4D2C-8D5E-D95C40DEF310}.Debug|Any CPU.Build.0 = Debug|Any CPU {73A5230F-0A90-4D2C-8D5E-D95C40DEF310}.Release|Any CPU.ActiveCfg = Release|Any CPU {73A5230F-0A90-4D2C-8D5E-D95C40DEF310}.Release|Any CPU.Build.0 = Release|Any CPU {376DEF77-CBE3-452A-890C-FF2728D90322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {376DEF77-CBE3-452A-890C-FF2728D90322}.Debug|Any CPU.Build.0 = Debug|Any CPU {376DEF77-CBE3-452A-890C-FF2728D90322}.Release|Any CPU.ActiveCfg = Release|Any CPU {376DEF77-CBE3-452A-890C-FF2728D90322}.Release|Any CPU.Build.0 = Release|Any CPU {D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A}.Release|Any CPU.Build.0 = Release|Any CPU {A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA}.Debug|Any CPU.Build.0 = Debug|Any CPU {A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA}.Release|Any CPU.ActiveCfg = Release|Any CPU {A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA}.Release|Any CPU.Build.0 = Release|Any CPU {C00DA46C-9614-4C42-AA89-64A3520F4EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C00DA46C-9614-4C42-AA89-64A3520F4EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU {C00DA46C-9614-4C42-AA89-64A3520F4EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU {C00DA46C-9614-4C42-AA89-64A3520F4EE7}.Release|Any CPU.Build.0 = Release|Any CPU {605AB027-CB0A-4B8E-8E20-55258369C497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {605AB027-CB0A-4B8E-8E20-55258369C497}.Debug|Any CPU.Build.0 = Debug|Any CPU {605AB027-CB0A-4B8E-8E20-55258369C497}.Release|Any CPU.ActiveCfg = Release|Any CPU {605AB027-CB0A-4B8E-8E20-55258369C497}.Release|Any CPU.Build.0 = Release|Any CPU {0D7F5E5F-348D-4606-9CE4-F2FB220E12BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D7F5E5F-348D-4606-9CE4-F2FB220E12BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {0D7F5E5F-348D-4606-9CE4-F2FB220E12BA}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D7F5E5F-348D-4606-9CE4-F2FB220E12BA}.Release|Any CPU.Build.0 = Release|Any CPU {6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C}.Release|Any CPU.Build.0 = Release|Any CPU {40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C}.Release|Any CPU.Build.0 = Release|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Release|Any CPU.Build.0 = Release|Any CPU {DED98401-EAED-4BE0-84A9-494D77B0E70D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DED98401-EAED-4BE0-84A9-494D77B0E70D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DED98401-EAED-4BE0-84A9-494D77B0E70D}.Release|Any CPU.ActiveCfg = Release|Any CPU {DED98401-EAED-4BE0-84A9-494D77B0E70D}.Release|Any CPU.Build.0 = Release|Any CPU {F599C512-7224-4A27-A474-3AA9510D2A14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F599C512-7224-4A27-A474-3AA9510D2A14}.Debug|Any CPU.Build.0 = Debug|Any CPU {F599C512-7224-4A27-A474-3AA9510D2A14}.Release|Any CPU.ActiveCfg = Release|Any CPU {F599C512-7224-4A27-A474-3AA9510D2A14}.Release|Any CPU.Build.0 = Release|Any CPU {CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD31C6F6-F8DD-4B56-B08C-D9E8D5BA3E73}.Release|Any CPU.Build.0 = Release|Any CPU {5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B0DC38B-5BC9-4DAC-8527-8D1C33E97247}.Release|Any CPU.Build.0 = Release|Any CPU {53341B04-6D30-4137-943B-20D8706351E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {53341B04-6D30-4137-943B-20D8706351E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {53341B04-6D30-4137-943B-20D8706351E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {53341B04-6D30-4137-943B-20D8706351E8}.Release|Any CPU.Build.0 = Release|Any CPU {CB199ED6-3D1D-4E12-A15F-597B6A0BA564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB199ED6-3D1D-4E12-A15F-597B6A0BA564}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB199ED6-3D1D-4E12-A15F-597B6A0BA564}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB199ED6-3D1D-4E12-A15F-597B6A0BA564}.Release|Any CPU.Build.0 = Release|Any CPU {60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {60AE2AF5-D84C-4A8B-AA36-FD58A6A423D7}.Release|Any CPU.Build.0 = Release|Any CPU {70A43D24-4905-4A16-8CE2-165F73243B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {70A43D24-4905-4A16-8CE2-165F73243B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU {70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU {70A43D24-4905-4A16-8CE2-165F73243B8D}.Release|Any CPU.Build.0 = Release|Any CPU {ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Debug|Any CPU.Build.0 = Debug|Any CPU {ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.ActiveCfg = Release|Any CPU {ECDF1DD3-073D-4708-A20B-1F0F2D663065}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {69B34B22-198D-4BD0-AAFC-39A90F9ADAC2} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {8863A92B-E1A3-4203-8C08-C017E5B3D2CC} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {28747B1E-0784-4B7B-AE48-F5AD3C937262} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {08A826AA-2C2E-4E21-A3D8-60BF8E57772E} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {ED3C04BC-3DEF-4B5C-BF91-EE8F5C38F8E4} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {2E8F3332-545A-4454-9F75-1498A9B7B9BF} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {400BD703-3E6F-41E6-929B-5FD888EFD00A} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {73A5230F-0A90-4D2C-8D5E-D95C40DEF310} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {A61C6606-A884-47BC-BD9D-DD9A3E4BDCDA} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {0D7F5E5F-348D-4606-9CE4-F2FB220E12BA} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {40E39D18-CEDF-4BA1-8D9D-60DECAA30C9C} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {DED98401-EAED-4BE0-84A9-494D77B0E70D} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {5B0DC38B-5BC9-4DAC-8527-8D1C33E97247} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {53341B04-6D30-4137-943B-20D8706351E8} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {70A43D24-4905-4A16-8CE2-165F73243B8D} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} {ECDF1DD3-073D-4708-A20B-1F0F2D663065} = {BDB77DF2-2D7D-4363-BB2B-D6A3A8A69C7B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D003939-9797-4F37-B391-10047A780641} EndGlobalSection EndGlobal ================================================ FILE: Liquid.WebApi.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31612.314 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.WebApi.Http", "src\Liquid.WebApi.Http\Liquid.WebApi.Http.csproj", "{25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{7B0BC311-679F-4D1F-BE49-DF3E49863792}" ProjectSection(SolutionItems) = preProject .github\workflows\liquid-ci-cd-http-extensions-crud.yml = .github\workflows\liquid-ci-cd-http-extensions-crud.yml .github\workflows\liquid-ci-cd-http.yml = .github\workflows\liquid-ci-cd-http.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{13C213E0-722A-4CD5-9209-21E12FDA0CA8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.WebApi.Http.Tests", "test\Liquid.WebApi.Http.Tests\Liquid.WebApi.Http.Tests.csproj", "{39DDC514-44CD-43BE-9E5B-C3B86665895E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Debug|Any CPU.Build.0 = Debug|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Release|Any CPU.ActiveCfg = Release|Any CPU {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4}.Release|Any CPU.Build.0 = Release|Any CPU {39DDC514-44CD-43BE-9E5B-C3B86665895E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39DDC514-44CD-43BE-9E5B-C3B86665895E}.Debug|Any CPU.Build.0 = Debug|Any CPU {39DDC514-44CD-43BE-9E5B-C3B86665895E}.Release|Any CPU.ActiveCfg = Release|Any CPU {39DDC514-44CD-43BE-9E5B-C3B86665895E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4} = {13C213E0-722A-4CD5-9209-21E12FDA0CA8} {39DDC514-44CD-43BE-9E5B-C3B86665895E} = {13C213E0-722A-4CD5-9209-21E12FDA0CA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {83C46B4F-18D0-45CB-B7EC-9A5B8840F6A0} EndGlobalSection EndGlobal ================================================ FILE: README.md ================================================ # Liquid Application Framework ![GitHub issues](https://img.shields.io/github/issues/Avanade/Liquid-Application-Framework) ![GitHub](https://img.shields.io/github/license/Avanade/Liquid-Application-Framework) ![GitHub Repo stars](https://img.shields.io/github/stars/Avanade/Liquid-Application-Framework?style=social) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://avanade.github.io/code-of-conduct/) [![Ready Ava Maturity](https://img.shields.io/badge/Ready-Ava--Maturity-%23FF5800?labelColor=green)](https://avanade.github.io/maturity-model/) This repository contains Liquid Application Framework full code, samples, templates and [documentation](docs/About-Liquid.md). ## What is Liquid? Liquid is a **multi-cloud** framework designed to **accelerate the development** of cloud-native microservices while avoiding coupling your code to specific cloud providers. When writing Liquid applications, you stop worrying about the technology and focus on your business logic - Liquid abstracts most of the boilerplate and let you just write domain code that looks great and gets the job done. ## Feature - Abstracts a number of services from cloud providers such as Azure, AWS and Google Cloud to enable you to write code that could run anywhere. - Brings a directed programming model that will save you time on thinking how to structure your application, allowing you to focus on writing business code. | Liquid Application Framework Binaries | Version | | :-- | :--: | | [`Liquid.Core`](https://www.nuget.org/packages/Liquid.Core) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Core) | | [`Liquid.Repository.Mongo`](https://www.nuget.org/packages/Liquid.Repository.Mongo) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Repository.Mongo) | | [`Liquid.Repository.EntityFramework`](https://www.nuget.org/packages/Liquid.Repository.EntityFramework) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Repository.EntityFramework) | | [`Liquid.Repository.OData`](https://www.nuget.org/packages/Liquid.Repository.OData) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Repository.OData) | | [`Liquid.Cache.Memory`](https://www.nuget.org/packages/Liquid.Cache.Memory) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Cache.Memory) | | [`Liquid.Cache.NCache`](https://www.nuget.org/packages/Liquid.Cache.NCache) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Cache.NCache) | | [`Liquid.Cache.Redis`](https://www.nuget.org/packages/Liquid.Cache.Redis) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Cache.Redis) | | [`Liquid.Cache.SqlServer`](https://www.nuget.org/packages/Liquid.Cache.SqlServer) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Cache.SqlServer) | | [`Liquid.Messaging.Kafka`](https://www.nuget.org/packages/Liquid.Messaging.Kafka) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Messaging.Kafka) | | [`Liquid.Messaging.RabbitMq`](https://www.nuget.org/packages/Liquid.Messaging.RabbitMq) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Messaging.RabbitMq) | | [`Liquid.Messaging.ServiceBus`](https://www.nuget.org/packages/Liquid.Messaging.ServiceBus) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Messaging.ServiceBus) | | [`Liquid.WebApi.Http`](https://www.nuget.org/packages/Liquid.WebApi.Http) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.WebApi.Http) | | [`Liquid.Dataverse`](https://www.nuget.org/packages/Liquid.Dataverse) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Dataverse) | | [`Liquid.Storage.AzureStorage`](https://www.nuget.org/packages/Liquid.Storage.AzureStorage) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.Storage.AzureStorage) | | [`Liquid.GenAi.OpenAi`](https://www.nuget.org/packages/Liquid.GenAi.OpenAi) | ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Liquid.GenAi.OpenAi) | ## Getting Started You can use Liquid Templates to get your microservice started. Install the templates by running the following dotnet CLI command at the PowerShell prompt : ```Shell dotnet new install Liquid.Templates ``` and run dotnet new command with the name and parameters of the following templates: |Name|Description| | :-- | :-- | |`liquidcrudsolution` |Liquid WebAPI CRUD Solution (Domain and WebAPI projects) | |`liquidcrudaddentity` |Liquid entity class, CRUD mediator handlers and CRUD controller | |`liquiddomainaddhandler` |Liquid mediator command handler | |`liquiddomainproject` |Liquid Domain project (mediator command handler) | |`liquidwebapisolution` |Liquid WebAPI solution (Domain and WebAPI projects) | |`liquidwebapiaddentity` |Liquid entity class, mediator command handler and CRUD controller | |`liquidwebapiproject` |Liquid WebAPI project | |`liquidworkersolution` |Liquid WorkerService solution (Domain and WorkerService projects) | |`liquidworkerproject` |Liquid WorkerService project | |`liquidbcontextaddentity` |Liquid DbContext entity configuration class (for Entity Framework) | |`liquiddbcontextproject` |Liquid Repository project (EntityFramework DbContext configurations) | ### Sample: To create an WebAPI solution with CRUD handlers, you must: - execute command : ```Shell dotnet new liquidcrudsolution --projectName "some root namespace" --entityName "some entity" --entityIdType "type of your unique ID" ``` - open the folder where the command was executed, and open the project created in the IDE of your choice: ![template_sample](https://user-images.githubusercontent.com/30960065/153954780-0ec8a6c0-153e-4bbc-8f3a-4ccc9c1e7858.png) - follow the instructions found in the generated code TODOs, and run! > You can make changes in code, add more classes and project if you need, and also using others Liquid templates to do it! ## Contribute Some of the best ways to contribute are to try things out, file issues, and make pull-requests. - You can provide feedback by filing issues on GitHub or open a discussion in [Discussions tab](https://github.com/Avanade/Liquid-Application-Framework/discussions). We accept issues, ideas and questions. - You can contribute by creating pull requests for the issues that are listed. Look for issues marked as _ready_ if you are new to the project. - Avanade asks that all commits sign the [Developer Certificate of Origin](https://developercertificate.org/). In any case, be sure to take a look at [the contributing guide](CONTRIBUTING.md) before starting, and see our [security disclosure](https://github.com/Avanade/avanade-template/blob/main/SECURITY.md) policy. ## Who is Avanade? [Avanade](https://www.avanade.com) is the leading provider of innovative digital, cloud and advisory services, industry solutions and design-led experiences across the Microsoft ecosystem. ================================================ FILE: docs/About-Lightweight-Architectures.md ================================================ | [Main](About-Liquid.md) > About Lightweight Architectures | |----| Successful modern digital solutions, like startups, while starts small and agile, had to cope with accelerated growth, reaching hundreds of millions of concurrent users while still being able to compete and evolve rapidly. Hence, those companies (startups) have set the bar (of technology competence) to a much higher level than traditional enterprises were used to. Now, a new set of styles, patterns and practices rules the modern application life-cycle, targeting enterprises that are in their way to digital transformation (and competition). Such type of enterprise architecture is being so called lightweight, in the sense that it resembles the lightness of **"born-digital"** solutions when compared to what has been done in large traditional companies. ## Learn from some thought leaders - ### [Microservice Architectural Style by **Martin Fowler, _et al._**](https://www.martinfowler.com/articles/microservices.html) - ### [Cloud Design Patterns by **Microsoft**](https://docs.microsoft.com/en-us/azure/architecture/patterns/) - ### [The Twelve-Factor App by **Adam Wiggins**](https://12factor.net/) ================================================ FILE: docs/About-Liquid-Applications.md ================================================ | [Main](About-Liquid.md) > About Liquid Applications | |----| Accenture uses the term [Liquid Applications](https://www.accenture.com/us-en/capability-rapid-application-development-studio) to emphasize the ever-changing aspect of a given solution, meant do deal with unpredictable yet presumable external pressure from users, clients, competitors and regulators. Thus, for "liquid application", we should think about a custom business solution build on top of a [lightweight architecture](About-Lightweight-Architectures.md) and able to coexist and evolve in the digital environment. Such kind of application should be developed using a cloud-first, mobile-first and API-first approach, using Agile & DevOps practices and tools. Liquid have many [resources to ease the development and operation of such applications](Using-Liquid-for-building-your-application.md). ================================================ FILE: docs/About-Liquid.md ================================================ The **Liquid Application Framework** is a set of pre-integrated, ready-to-use binding code that supports the creation of [liquid applications](About-Liquid-Applications.md). It is based on reference [lightweight architectures](About-Lightweight-Architectures.md), cloud design patterns and [modern software engineering](https://www.avanade.com/en/technologies/modern-software-engineering) techniques. For building business software, Liquid can be thought of as a set of pre-built parts that drives a faster, safer, cheaper development work, while delivering high quality products. #Liquid boosts the lifecycle of modern applications. How does it do that? The framework increases the development productivity and quality, while eases operation, of business applications by reducing their complexity through use of pre-built and pre-integrated technical components. It also adds architectural uniformity and governance by making it possible for software engineers to focus on business domain logic, leaving the technical issues with to the framework. Last, but not least, Liquid allows for easy and centralized replacement and/or extension of platform components (_e.g._ database server) so applications built on top of it are tech-agnostic, _i.e._ minimizes vendor/platform lock-in. # See more 1. [**Introduction**](Introduction.md) 1. [**Key concepts**](Key-Concepts.md) 1. [**Using Liquid to build your application**](Using-Liquid-for-building-your-application.md) 1. [**Sample application: xxx** ](Sample-application-xxx.md) # Contribute Some of the best ways to contribute are to try things out, file issues, and make pull-requests. - You can provide feedback by filing issues on GitHub. We accept issues, ideas and questions. - You can contribute by creating pull requests for the issues that are listed. Look for issues marked as _good first issue_ if you are new to the project. In any case, be sure to take a look at [the contributing guide](https://github.com/Avanade/Liquid-Application-Framework/CONTRIBUTING.md) before starting. ================================================ FILE: docs/Business-logic-seggregation.md ================================================ | [Main](About-Liquid.md) > [Key Concepts](Key-Concepts.md) > Business Logic Separation | |----| For the sake of productivity, is better to have source code clear and legible, as much as possible. However, current technology platforms contain many components to be integrated and too many aspects to consider beyond the (business) algorithm in question. It is hard to deal with those technology matters in a isolated way, seggregated from the core business logic that is being written. Liquid seeks to do that, as good as possible, allowing to use only minimum and precise mentions to such technical capabilities in the business code. Take the following example showing how Liquid hides most of the complexity of connecting to a data repository to find and update some data and, in the same logic, publishing an event in a queue of a message/event bus: On Startup.cs: ```csharp ... //Injects the Messaging Service services.AddProducersConsumers(typeof(ProductPriceChangedPublisher).Assembly); ... ``` On ChangeProductPriceCommandHandler.cs: ```csharp public class ChangeProductPriceCommandHandler : RequestHandlerBase, IRequestHandler { private readonly IProductRepository _productRepository; private readonly ILightProducer _productPriceChangedPublisher; private readonly IMapper _mapper; private readonly IPostService _postService; public ChangeProductPriceCommandHandler(IMediator mediatorService, ILightContext contextService, ILightTelemetry telemetryService, IMapper mapperService, IProductRepository productRepository, ILightProducer productPriceChangedPublisher, IPostService postService) : base(mediatorService, contextService, telemetryService, mapperService) { } /// /// Handles the specified request. /// /// The request. /// The cancellation token. /// public async Task Handle(ChangeProductPriceCommand request, CancellationToken cancellationToken) { var product = await _productRepository.FindByIdAsync(new ObjectId(request.Id)); if (product != null) { product.Price = request.Price; await _productRepository.UpdateAsync(product); var changeProductPriceMessage = _mapper.Map(request); await _productPriceChangedPublisher.SendMessageAsync(changeProductPriceMessage); return true; } throw new ProductDoesNotExistException(); } } ``` The method `public async Task Handle(...` above is responsible for doing the business logic. It should find Product data in a repository, change its price, persist it back to the repository and publish an event. The classes `_productRepository` and `_productPriceChangedPublisher` hides the technical complexity mentioned before. Besides that, since those classes are injected on this Command Handler, they can be substituted by any other classes implementing the same interfaces (`IProductRepository` and `ILightProducer` respectivelly), doing a complete isolation of the business logic and the underlying infrastructure components. In this case, Liquid uses a common programming model in DDD hexagonal architectures, where external requests will be translated to Domain Commands or Domain Queries, and those will handle the business logic that responds to those requests. In that way, the requests can came from anywhere and in any format that could be handled outside the Domain. The same for the reponses. This strategy is followed for all technology capabilities that Liquid abstracts and pre-integrates. For instance, see bellow, how simple is to create the class to publish that event on a RAbbitMQ: ```csharp [RabbitMqProducer("rabbitMqConnectionString", "ProductPriceChanged")] public class ProductPriceChangedPublisher : RabbitMqProducer { /// /// Gets the rabbit mq settings. /// /// /// The rabbit mq settings. /// public override RabbitMqSettings RabbitMqSettings => new RabbitMqSettings { Durable = true, Persistent = true, Expiration = "172800000" }; /// /// Initializes a new instance of the class. /// /// The context factory. /// The telemetry factory. /// The logger factory. /// The messaging settings. public ProductPriceChangedPublisher(ILightContextFactory contextFactory, ILightTelemetryFactory telemetryFactory, ILoggerFactory loggerFactory, ILightConfiguration> messagingSettings) : base(contextFactory, telemetryFactory, loggerFactory, messagingSettings) { } } ``` The base class `RabbitMqProducer` will handle all the complexities inherent to connect to a RabbitMQ server and publish a message on a queue. That is the beauty of using platform specific "cartridges". This way, it should be very easy to develop an application that could run on an on-premises environment using RabbitMQ as the message bus, and, also, the same application could be changed to run on Azure, using Azure ServiceBus as the message bus. In fact, the application could be created to dinamically switch between different platform implementations, in run time. In contrast to what Liquid provides, see an example excerpt from the code of [.NET Core reference application](https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering/Ordering.BackgroundTasks): ``` ... private void RegisterEventBus(IServiceCollection services) { var subscriptionClientName = Configuration["SubscriptionClientName"]; if (Configuration.GetValue("AzureServiceBusEnabled")) { services.AddSingleton(sp => { var serviceBusPersisterConnection = sp.GetRequiredService(); var iLifetimeScope = sp.GetRequiredService(); var logger = sp.GetRequiredService>(); var eventBusSubcriptionsManager = sp.GetRequiredService(); return new EventBusServiceBus(serviceBusPersisterConnection, logger, eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); }); } ... RegisterEventBus(services); //create autofac based service provider var container = new ContainerBuilder(); container.Populate(services); return new AutofacServiceProvider(container.Build()); } ... private void ConfigureEventBus(IApplicationBuilder app) { var eventBus = app.ApplicationServices.GetRequiredService(); ... eventBus.Subscribe(); } ... public class OrderStatusChangedToShippedIntegrationEventHandler : IIntegrationEventHandler { private readonly IHubContext _hubContext; public OrderStatusChangedToShippedIntegrationEventHandler(IHubContext hubContext) { _hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext)); } public async Task Handle(OrderStatusChangedToShippedIntegrationEvent @event) { ... } ``` There are too many technical (platform specific) lines of code mixed with only a few application (business) ones: **OrderStatusChangedToShippedIntegrationEvent** and corresponding **Handle()**. In this scenario, it is very hard to understand, refactor and evolve this code due to both technical and business demands. With Liquid, the business logic will always be inside the domain, seggregated from the underlying technology, making it easier to understand. At same time, the underlying technology specifics will be outside the domain, abstracted in a way that makes possible to use different implementations that will work with the same business logic, while, also, allows to evolve these parts of code separatelly. ================================================ FILE: docs/Introduction.md ================================================ | [Main](About-Liquid.md) > Introduction | |----| In the last years, the always challenging task of developing multi-hundred-line-of-code business applications has reached elevated sophistication and professionalism. A high-quality application life-cycle is not only for high-tech companies anymore. To be or to become digital demands distinguished software engineering skills for companies of any industry and any size. Avanade has historically helped several companies and professionals achieving proficiency in the development of modern applications during their digital transformation. Now Avanade provides Liquid, a framework envisioned to boost such engineering capabilities, available as an Open Source project, for anyone looking for quick, standardized, flexible, extensible and high quality microservices development. | Topics | | :-- | | [Technical Context](#Technical-Context)
[Avanade as a Thought Leader](#Avanade-as-a-Thought-Leader)
[Business Drivers](#Business-Drivers)
[Guiding Principles](#Guiding-Principles) | ### Technical Context The development of enterprise custom application has passed through several architectural waves in the last couple decades, with each new wave adding some complexity on top of the old ones. In recent years, with the advent of modern architectural standards such as REST APIs, microservices on containers and single page applications as well as DevOps practices, the modern applications do have new strengths that are of great value to businesses and their customers. However, the complexity of designing, building and running such applications increased a lot, demanding great effort from architects, software and operation engineers to fully implement all the mandatory concepts and capabilities. Moreover, such effort is a repeating task while yet an error-prone one. That opens a great space for building a set of pre-built software components following a general-purpose technical design, thereby condensed as a framework for developing and running those so called modern applications. ### Avanade as a Thought Leader Born on the union of Microsoft and Accenture, Avanade has quickly become [a major competence center for the Microsoft's ecosystem of technology, services and partners](https://www.avanade.com/en-us/media-center/press-releases/2020-microsoft-global-alliance-si-partner-of-the-year), with a track record of hundreds of enterprise grade, innovative projects delivered. Microsoft, in its transformation into a cloud service company, has made a complete rotation from a proprietary vendor to an open-source friendly company, thus naturally getting more and more influence on the evolution of the industry. In particular, due to its influence in the Single Page Application (SPA) domain [with Angular & Typescript](https://techcrunch.com/2015/03/05/microsoft-and-google-collaborate-on-typescript-hell-has-not-frozen-over-yet/) and microservice architecture with [.NET Core & Docker](https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/) (along with its open cloud offering such [Windows Azure](http://azure.microsoft.com) and VisualStudio.com) Microsoft has helped to pave the way for the building of modern digital applications. On the other hand, Accenture is a top leader in technology consulting field and it continuously infuses Avanade with technical and methodological know-how in enterprise architecture and digital transformation. Therefore, Avanade is best positioned to advise methods, patterns and building blocks on top of mature stacks and tools such as Microsoft's as well as to materialize the usual tech-agnostic point of view of Accenture. ### Business Drivers The purpose of Liquid is to allow the building of modern applications with better quality in a more efficient way. Therefore, the following business drivers were stated: - **Reduce time-to-value with faster development and testing activities:** The learning curve is shorter if developers can abstract technical complexity and have their components easily and automatically put into a DevOps pipeline; - **Increase quality with code uniformity:** all application components can be written with the same high-level "technical" dialect thus promoting better code analysis, both static and dynamic; - **Improve application governance, maintenance and evolution:** a single point of technical decision for the entire code allows better standards implementation, code maintenance as well as easier introduction and/or replacement of technical components; - **Catalyze the use of delivery centers:** by getting most of the technical design out of the box, remote teams can focus on requirements (backlog) and on the design of the business solution (business logic on the application layer). ### Guiding Principles From a technical perspective, the Liquid development was stressed toward the following principles: - **Prescriptive and non-intrusive programming model for business components:** frameworks and platform services (_e.g._ .NET Core and CosmosDB, respectively) are designed for broad use-case scenarios, very much broader than the vast majority of enterprise digital solutions actually demands. Liquid stands for the later scenarios and this is the high value space that it tries to fulfill. Therefore, Liquid [prescribes a "light" way of building business application](Key-Concepts.md/#prescriptive-programming-model) without compromising the capability of developers to use the underlying technology in any other ways, if this is a requirement; - **Multi platform based on workbench components replacement:** Liquid puts itself between application (business) components and underlying platform (technical) components to avoid (or, at least, minimize) vendor and/or technology lock-in of the application, during both building and execution time. Hence there ought to be a way of [simply replacing Liquid "cartridges"](Platform-Abstraction-Layer.md) of each type (_e.g._ database repository) from one specific technology to another (_e.g._ CosmosDB and DynamoDB) to seamlessly moving application (business) logic from one platform to another. This, obviously, will depend a lot on how the application development team will deal with specific features provided by the underlying technology being used. Most of the time, we only use a small subset of those features, making it very easy to substitute similar technologies. But, even when this is not the case, Liquid helps to isolate those specifics, allowing the creation of portable code with high control, avoiding spreaded infrastructure code that makes maintenance harder; - **Built-in lightweight patterns for technology domain:** There are many [design patterns](About-Lightweight-Architectures.md) that must be applied to develop a modern digital application. Many of them can be given to the (upper) application layer as an out-of-the box capability of Liquid; - **Abstract technical complexity from business software developer:** The true hard work of enterprise architects is (or should be) the structuring of millions of lines of code a large company has. Legacy code has probably grown over decades in an unsupervised manner. That should not be the case with the brand-new code of modern digital application. While dealing with technology (delta) innovation/rennovation is a task architects deal every new year, dealing with applications' structuring and refactoring is (or should be) their daily job. The same is valid for business software developers. Liquid helps engineers [to focus on their useful (business) code](Business-logic-seggregation.md). ================================================ FILE: docs/Key-Concepts.md ================================================ | [Main](About-Liquid) > Key Concepts | |----| A small group of core concepts balances the many aspects dealt by Liquid and determines its general shape and strengths. ## Architectural Strategies - [**Platform abstraction layer:**](Platform-Abstraction-Layer.md) using the best from PaaS offerings while avoiding lock-in; - [**Business logic seggregation:**](Business-logic-seggregation.md) avoid mixing domain (business) logic along with underlying platform (technical) logic; - [**DevOps for microservices:**](DevOps-for-Microservices.md) giving this fine-grained unit of software an easier way of being built, deployed and tested in a continuous fashion. ## Prescriptive Programming Model - [**APIs as (formal) object models:**](API-as-formal-Object-Model.md) leveraging the abstraction and formality of a well-formed API as a way to avoid code redundancy and error propensity; - [**Encapsulated domain (business) logic:**](Encapsulated-Domain-Logic.md) putting domain logic in a format (of classes) independent of the protocols used to access it; - [**Business error handling:**](Business-Error-Handling.md) defining a simple yet comprehensive way of dealing with business logic deviation; - [**Microservice sizing:**](Microservice-Sizing.md) giving shape to the various parameters of a well-designed microservice. ================================================ FILE: docs/Platform-Abstraction-Layer.md ================================================ | [Main](About-Liquid.md) > [Key Concepts](Key-Concepts.md) > Platform Abstraction Layer | |----| The most fundamental strategy steering Liquid's design is its responsibility to decouple as much as possible the application components from the underlying platforms. ![PlatformAbstractionLayer.png](PlatformAbstractionLayer.png) Liquid concentrates most of platform (technology) specific dependencies in one single point of code thus freeing up the many application (business) components to access pre-integrated, high abstraction primitives for doing their jobs. Doing such technology integration repetitive tasks, while error-prone and risky, can considerably increase costs on software projects. Hence an abstraction layer like Liquid diminishes such costs as much as great is the number of business components written on top of it - without having to deal with those low levels details. Additionally (and even more important), now there are only a handful of dependency points on a given set of technology components (_i.e._ on their SDKs and APIs). Following the Liquid guiding principle of [multi-platform components](Introduction.md/#guiding-principles), the pretty easy [replacement of one specific Liquid cartridge by another](Choose-platform-components.md) allows the avoidance of technology lock-in in a controlled and productive way. See parts of a sample code that activates RabbitMQ as the message bus for asynchronous communication: On Startup.cs: ```csharp //Injects the Messaging Service services.AddProducersConsumers(typeof(ProductPriceChangedPublisher).Assembly); ``` On ProductPriceChangedPublisher.cs: ```csharp [RabbitMqProducer("messageBusConnectionString", "ProductPriceChanged")] public class ProductPriceChangedPublisher : RabbitMqProducer ``` While you can easily change this code to use Azure ServiceBus, for sample, this approach makes possible to [use the best of breed from all platforms](Leveling-up-Platform-Providers.md), because you are able to implement specific technical functionalities of each platform on the specific cartridge (RabbitMqProducer in this case). Your point of maintenance in the code for such kind of change will not touch any business logic and will be very easy to do. For use Azure ServiceBus instead of RabbitMQ, the only change would be on ProductPriceChangedPublisher.cs, as follows: ```csharp [ServiceBusProducer("messageBusConnectionString", "ProductPriceChanged")] public class ProductPriceChangedPublisher : ServiceBusProducer ``` Actually, the corollary of a given market standard is to be a commodity technology. Accordingly, Liquid helps to transcend the classical trade-off between being open & commodity or being locked-in & differentiated. Adding new technology platform capabilities to all business application components can be done, once again, from a centralized point, in a controlled and agile way. ================================================ FILE: docs/Using-Liquid-for-building-your-application.md ================================================ # WIP - This tutorial is still a Work In Progress and should not be used yet - TODO: create the templates - TODO: create the documents for each step - TODO: confirm this step-by-step strawman | [Main](About-Liquid.md) > Using Liquid for building your application | |----| The following are the basic instructions on how to build modern [liquid applications](About-Liquid-Applications.md) taking advantage of Liquid's features and [concepts](Key-Concepts.md). ## Prepare your DevOps environment 1. [Install Liquid project templates for starting](Install-project-templates.md) 1. [Choose platform components to support your business code](Choose-platform-components.md) 1. [Setup configurations per environment](Set-up-configurations-per-environment.md) 1. [Configure CI, CD and CT](Configure-CI-CD-and-CT.md) 1. [Deploy microservices on containers](Deploy-microservices.md) ## Code your microservices 6. [Define the APIs (and formats) exposed](Define-the-APIs.md) 1. [Structure internal (business) logic](Structure-internal-logic.md) 1. [Validate input parameters and data models](Validate-input-and-data.md) 1. [Authenticate and authorize the API](Secure-the-APIs.md) 1. [Persist and query data from repository](Persist-and-query-data.md) 1. [Make calls to internal classes](Make-internal-calls.md) 1. [Call other microservices ](Call-other-microservices.md) 1. [Call third-party APIs](Call-third-party-APIs.md) # Connect your front-end to your APIs 14. [Call the back-end](Calling-the-back-end.md) 1. [Call third-party back-ends](Call-third-party-back-ends.md) 1. [Add authentication to the front-end](Add-authentication-to-the-front-end.md) # Develop tests and mocks 17. [Seed test data](Seeding-test-data.md) 1. [Test APIs directly](Test-APIs-directly.md) 1. [Mock authentication](Mock-authentication.md) 1. [Mock calls to microservices](Mock-calls-to-microservices.md) # Add advanced behavior 21. [Gather telemetry from front-ends and back-ends](Gather-telemetry.md) 1. [Control state of changing data](Control-state-of-changing-data.md) 1. [Querying and present data with pagination](Querying-data-with-pagination.md) 1. [Building complex queries](Building-complex-queries.md) 1. [Deal with multiple cultures](Deal-with-multiple-cultures.md) ================================================ FILE: nuget.config ================================================ ================================================ FILE: samples/Liquid.Sample.Dataverse.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.34202.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Sample.Dataverse.Function", "src\Liquid.Sample.Dataverse.Function\Liquid.Sample.Dataverse.Function.csproj", "{577C7D6D-566D-4EA2-8C32-039C2C4E17DF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Sample.Dataverse.Domain", "src\Liquid.Sample.Dataverse.Domain\Liquid.Sample.Dataverse.Domain.csproj", "{7C47775D-45C7-463E-84DA-5B5B264D55F0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {577C7D6D-566D-4EA2-8C32-039C2C4E17DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {577C7D6D-566D-4EA2-8C32-039C2C4E17DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {577C7D6D-566D-4EA2-8C32-039C2C4E17DF}.Release|Any CPU.ActiveCfg = Release|Any CPU {577C7D6D-566D-4EA2-8C32-039C2C4E17DF}.Release|Any CPU.Build.0 = Release|Any CPU {7C47775D-45C7-463E-84DA-5B5B264D55F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C47775D-45C7-463E-84DA-5B5B264D55F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C47775D-45C7-463E-84DA-5B5B264D55F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C47775D-45C7-463E-84DA-5B5B264D55F0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB41E555-2F25-4D02-8513-7EDC443A17D4} EndGlobalSection EndGlobal ================================================ FILE: samples/Liquid.Sample.MessagingConsumer.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31410.357 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Sample.MessagingConsumer", "src\Liquid.Sample.MessagingConsumer\Liquid.Sample.MessagingConsumer.csproj", "{E4FE9A3E-4610-4EBF-AD0D-71A642B91282}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Sample.Domain", "src\Liquid.Sample.Domain\Liquid.Sample.Domain.csproj", "{54EF1FE4-82A3-4D39-AB35-C589DC73413C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E4FE9A3E-4610-4EBF-AD0D-71A642B91282}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E4FE9A3E-4610-4EBF-AD0D-71A642B91282}.Debug|Any CPU.Build.0 = Debug|Any CPU {E4FE9A3E-4610-4EBF-AD0D-71A642B91282}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4FE9A3E-4610-4EBF-AD0D-71A642B91282}.Release|Any CPU.Build.0 = Release|Any CPU {54EF1FE4-82A3-4D39-AB35-C589DC73413C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54EF1FE4-82A3-4D39-AB35-C589DC73413C}.Debug|Any CPU.Build.0 = Debug|Any CPU {54EF1FE4-82A3-4D39-AB35-C589DC73413C}.Release|Any CPU.ActiveCfg = Release|Any CPU {54EF1FE4-82A3-4D39-AB35-C589DC73413C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FD6C41A9-76DF-4FEE-98D9-154A31618D95} EndGlobalSection EndGlobal ================================================ FILE: samples/Liquid.Sample.WebApi.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Sample.Domain", "src\Liquid.Sample.Domain\Liquid.Sample.Domain.csproj", "{68DCA206-AD43-4D44-AA1B-2F2E52DEE89A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Sample.Api", "src\Liquid.Sample.Api\Liquid.Sample.Api.csproj", "{356B2F4F-0846-42FD-A013-1B68E6C3D484}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {68DCA206-AD43-4D44-AA1B-2F2E52DEE89A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {68DCA206-AD43-4D44-AA1B-2F2E52DEE89A}.Debug|Any CPU.Build.0 = Debug|Any CPU {68DCA206-AD43-4D44-AA1B-2F2E52DEE89A}.Release|Any CPU.ActiveCfg = Release|Any CPU {68DCA206-AD43-4D44-AA1B-2F2E52DEE89A}.Release|Any CPU.Build.0 = Release|Any CPU {356B2F4F-0846-42FD-A013-1B68E6C3D484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {356B2F4F-0846-42FD-A013-1B68E6C3D484}.Debug|Any CPU.Build.0 = Debug|Any CPU {356B2F4F-0846-42FD-A013-1B68E6C3D484}.Release|Any CPU.ActiveCfg = Release|Any CPU {356B2F4F-0846-42FD-A013-1B68E6C3D484}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C37CEE66-2BB3-4FF4-970A-2B1518BE93CA} EndGlobalSection EndGlobal ================================================ FILE: samples/src/Liquid.Sample.Api/Controllers/SampleController.cs ================================================ using Liquid.Sample.Domain.Entities; using Liquid.Sample.Domain.Handlers; using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using System.Net; namespace Liquid.Sample.Api.Controllers { [ApiController] [Route("[controller]")] public class SampleController : LiquidControllerBase { public SampleController(IMediator mediator) : base(mediator) { } [HttpGet("Sample")] public async Task Get([FromQuery] string id) => await ExecuteAsync(new SampleRequest(id), HttpStatusCode.OK); [HttpPost] public async Task Post([FromBody] SampleMessageEntity entity) { await Mediator.Send(new SampleEventRequest(entity)); return NoContent(); } } } ================================================ FILE: samples/src/Liquid.Sample.Api/Liquid.Sample.Api.csproj ================================================ net6.0 enable enable ================================================ FILE: samples/src/Liquid.Sample.Api/Program.cs ================================================ using Liquid.Messaging.ServiceBus.Extensions.DependencyInjection; using Liquid.Repository.Mongo.Extensions; using Liquid.Sample.Domain.Entities; using Liquid.Sample.Domain.Handlers; using Liquid.WebApi.Http.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings:Entities"); builder.Services.AddLiquidServiceBusProducer("Liquid:ServiceBus", "liquidoutput", true); builder.Services.AddLiquidHttp(typeof(SampleRequest).Assembly); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ================================================ FILE: samples/src/Liquid.Sample.Api/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: samples/src/Liquid.Sample.Api/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" }, "Console": { "IncludeScopes": true } }, "AllowedHosts": "*", "Liquid": { "swagger": { "name": "v1", "host": "", "schemes": [ "http", "https" ], "title": "Liquidv2.SimpleApi", "version": "v1", "description": "Simple WebApi Sample.", "SwaggerEndpoint": { "url": "/swagger/v1/swagger.json", "name": "SimpleWebApiSample" } }, "culture": { "defaultCulture": "pt-BR" }, "MyMongoDbSettings": { "DefaultDatabaseSettings": { "connectionString": "", "databaseName": "MySampleDb" }, "Entities": { "SampleEntity": { "CollectionName": "SampleCollection", "ShardKey": "id" } } }, "ServiceBus": { "Settings": [ { "ConnectionString": "", "EntityPath": "liquidoutput" }, { "ConnectionString": "", "EntityPath": "liquidinput" } ] } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Domain/Liquid.Sample.Dataverse.Domain.csproj ================================================ net6.0 enable enable ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Domain/PostSampleService.cs ================================================ using Liquid.Adapter.Dataverse; using Liquid.Adapter.Dataverse.Extensions; using Microsoft.Xrm.Sdk; namespace Liquid.Sample.Dataverse.Domain { public class PostSampleService { private readonly ILiquidDataverseAdapter _adapter; private readonly ILiquidMapper _mapper; public PostSampleService(ILiquidDataverseAdapter adapter, ILiquidMapper mapper) { _adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); } public async Task PostSample(string body) { var bodyEntity = await _mapper.Map(body, "sample"); var result = await _adapter.Create(bodyEntity); bodyEntity.Id = result; var response = bodyEntity.ToJsonString(); return response; } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # Azure Functions localsettings file #local.settings.json # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/DataverseIntegration.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Liquid.Sample.Dataverse.Domain; namespace Liquid.Sample.Dataverse.Function { public class DataverseIntegration { private readonly PostSampleService _service; public DataverseIntegration(PostSampleService service) { _service = service ?? throw new ArgumentNullException(nameof(service)); } [FunctionName("PostSample")] public async Task Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); string responseMessage = await _service.PostSample(data); return new OkObjectResult(responseMessage); } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/Liquid.Sample.Dataverse.Function.csproj ================================================ net6.0 v4 PreserveNewest PreserveNewest Never ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/Properties/serviceDependencies.json ================================================ { "dependencies": { "appInsights1": { "type": "appInsights" }, "storage1": { "type": "storage", "connectionId": "AzureWebJobsStorage" } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/Properties/serviceDependencies.local.json ================================================ { "dependencies": { "appInsights1": { "type": "appInsights.sdk" }, "storage1": { "type": "storage.emulator", "connectionId": "AzureWebJobsStorage" } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/Startup.cs ================================================ using Liquid.Adapter.Dataverse.Extensions.DependencyInjection; using Liquid.Sample.Dataverse.Function; using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using System.IO; [assembly: FunctionsStartup(typeof(Startup))] namespace Liquid.Sample.Dataverse.Function { public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { builder.Services.AddLiquidDataverseAdapter("DataverseClient"); } public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder) { FunctionsHostBuilderContext context = builder.GetContext(); builder.ConfigurationBuilder .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"local.setttings.json"), optional: true, reloadOnChange: true) .AddEnvironmentVariables(); } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/host.json ================================================ { "version": "2.0", "logging": { "applicationInsights": { "samplingSettings": { "isEnabled": true, "excludedTypes": "Request" }, "enableLiveMetricsFilters": true } } } ================================================ FILE: samples/src/Liquid.Sample.Dataverse.Function/local.settings.json ================================================ { "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "DataverseClient:ClientId": "", "DataverseClient:ClientSecret": "", "DataverseClient:url": "" } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Entities/SampleEntity.cs ================================================ using Liquid.Repository; using System; namespace Liquid.Sample.Domain.Entities { public class SampleEntity : LiquidEntity { public string MyProperty { get; set; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Entities/SampleMessageEntity.cs ================================================ using Liquid.Repository; using System; namespace Liquid.Sample.Domain.Entities { [Serializable] public class SampleMessageEntity { public string Id { get; set; } public string MyProperty { get; set; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SampleGet/SampleCommandHandler.cs ================================================ using Liquid.Repository; using Liquid.Sample.Domain.Entities; using MediatR; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Liquid.Sample.Domain.Handlers { public class SampleCommandHandler : IRequestHandler { private ILiquidRepository _repository; public SampleCommandHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(SampleRequest request, CancellationToken cancellationToken) { var item = await _repository.FindByIdAsync(Guid.Parse(request.Id)); var result = new SampleResponse(item); return result; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SampleGet/SampleRequest.cs ================================================ using MediatR; using System; namespace Liquid.Sample.Domain.Handlers { public class SampleRequest : IRequest { public string Id { get; set; } public SampleRequest(string id) { Id = id; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SampleGet/SampleRequestValidator.cs ================================================ using FluentValidation; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Sample.Domain.Handlers { public class SampleRequestValidator : AbstractValidator { public SampleRequestValidator() { RuleFor(request => request.Id).NotEmpty().NotNull(); } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SampleGet/SampleResponse.cs ================================================ using Liquid.Sample.Domain.Entities; using System.Collections.Generic; namespace Liquid.Sample.Domain.Handlers { public class SampleResponse { public SampleEntity Response { get; set; } public SampleResponse(SampleEntity response) { Response = response; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePost/SampleEventCommandHandler.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Messaging; using Liquid.Messaging.Interfaces; using Liquid.Repository; using Liquid.Sample.Domain.Entities; using MediatR; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Liquid.Sample.Domain.Handlers { public class SampleEventCommandHandler : IRequestHandler { private ILiquidProducer _producer; private readonly ILiquidRepository _repository; private readonly ILiquidContext _context; public SampleEventCommandHandler(ILiquidProducer producer, ILiquidContext context, ILiquidRepository repository) { _producer = producer; _context = context; _repository = repository; } public async Task Handle(SampleEventRequest request, CancellationToken cancellationToken) { await _repository.AddAsync(new SampleEntity() { Id = Guid.Parse(request.Entity.Id), MyProperty = request.Entity.MyProperty }); await _producer.SendMessageAsync(request.Entity, _context.current); } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePost/SampleEventRequest.cs ================================================ using Liquid.Sample.Domain.Entities; using MediatR; namespace Liquid.Sample.Domain.Handlers { public class SampleEventRequest : IRequest { public SampleMessageEntity Entity { get; set; } public SampleEventRequest(SampleMessageEntity entity) { Entity = entity; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePost/SampleEventRequestValidator.cs ================================================ using FluentValidation; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Sample.Domain.Handlers { public class SampleEventRequestValidator : AbstractValidator { public SampleEventRequestValidator() { RuleFor(request => request.Entity.Id).NotEmpty().NotNull(); } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePut/PutCommandHandler.cs ================================================ using Liquid.Repository; using Liquid.Sample.Domain.Entities; using MediatR; using System; using System.Threading; using System.Threading.Tasks; namespace Liquid.Sample.Domain.Handlers.SamplePut { public class PutCommandHandler : IRequestHandler { private readonly ILiquidRepository _repository; public PutCommandHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(PutCommandRequest request, CancellationToken cancellationToken) { await _repository.UpdateAsync(new SampleEntity() { Id = Guid.Parse(request.Message.Id), MyProperty = request.Message.MyProperty }); } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePut/PutCommandRequest.cs ================================================ using Liquid.Sample.Domain.Entities; using MediatR; namespace Liquid.Sample.Domain.Handlers.SamplePut { public class PutCommandRequest : IRequest { public SampleMessageEntity Message { get; set; } public PutCommandRequest(SampleMessageEntity message) { Message = message; } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Handlers/SamplePut/PutCommandRequestValidator.cs ================================================ using FluentValidation; namespace Liquid.Sample.Domain.Handlers.SamplePut { public class PutCommandRequestValidator : AbstractValidator { public PutCommandRequestValidator() { RuleFor(request => request.Message.Id).NotEmpty().NotNull(); } } } ================================================ FILE: samples/src/Liquid.Sample.Domain/Liquid.Sample.Domain.csproj ================================================  net6.0 ================================================ FILE: samples/src/Liquid.Sample.MessagingConsumer/Liquid.Sample.MessagingConsumer.csproj ================================================ net6.0 dotnet-Liquid.Sample.MessagingConsumer-5B39CB3F-CC5C-4EBB-AB99-178E83416FC9 ================================================ FILE: samples/src/Liquid.Sample.MessagingConsumer/Program.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Messaging.ServiceBus.Extensions.DependencyInjection; using Liquid.Repository.Mongo.Extensions; using Liquid.Sample.Domain.Entities; using Liquid.Sample.Domain.Handlers.SamplePut; using Microsoft.Extensions.Hosting; using System; namespace Liquid.Sample.MessagingConsumer { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddLiquidConfiguration(); services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings:Entities"); services.AddLiquidServiceBusProducer("ServiceBus", "liquidoutput", false); services.AddLiquidServiceBusConsumer ("ServiceBus", "liquidinput", false, typeof(PutCommandRequest).Assembly); }); } } ================================================ FILE: samples/src/Liquid.Sample.MessagingConsumer/Worker.cs ================================================ using Liquid.Messaging; using Liquid.Messaging.Interfaces; using Liquid.Sample.Domain.Entities; using Liquid.Sample.Domain.Handlers.SamplePut; using MediatR; using Microsoft.Extensions.Logging; using System.Threading; using System.Threading.Tasks; namespace Liquid.Sample.MessagingConsumer { public class Worker : ILiquidWorker { private readonly ILogger _logger; private readonly IMediator _mediator; public Worker(ILogger logger, IMediator mediator) { _logger = logger; _mediator = mediator; } public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { await _mediator.Send(new PutCommandRequest(args.Data)); } } } ================================================ FILE: samples/src/Liquid.Sample.MessagingConsumer/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: samples/src/Liquid.Sample.MessagingConsumer/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Liquid": { "culture": { "defaultCulture": "pt-BR" }, "ScopedContext": { "keys": [ { "keyName": "Connection", "required": false } ] }, "ScopedLogging": { "keys": [ { "keyName": "Connection", "required": false } ] }, "MyMongoDbSettings": { "DefaultDatabaseSettings": { "connectionString": "", "databaseName": "MySampleDb" }, "Entities": { "SampleEntity": { "CollectionName": "SampleCollection", "ShardKey": "id" } } } }, "ServiceBus": { "Settings": [ { "ConnectionString": "", "EntityPath": "liquidoutput" }, { "ConnectionString": "", "EntityPath": "liquidinput" } ] } } ================================================ FILE: src/Liquid.Cache.Memory/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using System; namespace Liquid.Cache.Memory.Extensions.DependencyInjection { /// /// LiquidCache using Redis extensions class. /// public static class IServiceCollectionExtension { /// /// Registers service and decorator, /// with its . /// /// /// An System.Action`1 to configure the provided /// . /// Indicates if this method must register a public static IServiceCollection AddLiquidMemoryDistributedCache(this IServiceCollection services, Action setupAction, bool withTelemetry = true) { services.AddDistributedMemoryCache(setupAction); return services.AddLiquidDistributedCache(withTelemetry); } } } ================================================ FILE: src/Liquid.Cache.Memory/Liquid.Cache.Memory.csproj ================================================  net8.0 Liquid.Cache.Memory enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full Distributed cache extension of Microsoft.Extensions.Caching.Memory. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Cache.NCache/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Alachisoft.NCache.Caching.Distributed; using Alachisoft.NCache.Caching.Distributed.Configuration; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Microsoft.Extensions.DependencyInjection; using System; namespace Liquid.Cache.NCache.Extensions.DependencyInjection { /// /// LiquidCache using NCache extensions class. /// public static class IServiceCollectionExtension { /// /// Registers service and decorator, /// with its . /// /// /// An System.Action`1 to configure the provided /// . /// Indicates if this method must register a public static IServiceCollection AddLiquidNCacheDistributedCache(this IServiceCollection services, Action setupAction, bool withTelemetry = true) { services.AddNCacheDistributedCache(setupAction); return services.AddLiquidDistributedCache(withTelemetry); } } } ================================================ FILE: src/Liquid.Cache.NCache/Liquid.Cache.NCache.csproj ================================================  net8.0 Liquid.Cache.NCache enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full Distributed cache extension of NCache.Microsoft.Extensions.Caching. This component is part of Liquid Application Framework. True Always Always Always ================================================ FILE: src/Liquid.Cache.NCache/client.ncconf ================================================ ================================================ FILE: src/Liquid.Cache.NCache/config.ncconf ================================================ ================================================ FILE: src/Liquid.Cache.NCache/tls.ncconf ================================================ certificate-name your-thumbprint certificate-name your-thumbprint false false false tls12 ================================================ FILE: src/Liquid.Cache.Redis/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.DependencyInjection; using System; namespace Liquid.Cache.Redis.Extensions.DependencyInjection { /// /// LiquidCache using Redis extensions class. /// public static class IServiceCollectionExtension { /// /// Registers service and decorator, /// with its . /// /// /// An System.Action`1 to configure the provided /// Microsoft.Extensions.Caching.StackExchangeRedis.RedisCacheOptions. /// Indicates if this method must register a public static IServiceCollection AddLiquidRedisDistributedCache(this IServiceCollection services, Action setupAction, bool withTelemetry = true) { services.AddStackExchangeRedisCache(setupAction); return services.AddLiquidDistributedCache(withTelemetry); } } } ================================================ FILE: src/Liquid.Cache.Redis/Liquid.Cache.Redis.csproj ================================================  net8.0 enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full Distributed cache extension of Microsoft.Extensions.Caching.StackExchangeRedis. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Cache.SqlServer/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Microsoft.Extensions.Caching.SqlServer; using Microsoft.Extensions.DependencyInjection; using System; namespace Liquid.Cache.SqlServer.Extensions.DependencyInjection { /// /// LiquidCache using SqlServer extensions class. /// public static class IServiceCollectionExtension { /// /// Registers service and decorator, /// with its . /// /// /// An System.Action`1 to configure the provided ///. /// Indicates if this method must register a public static IServiceCollection AddLiquidSqlServerDistributedCache(this IServiceCollection services, Action setupAction, bool withTelemetry = true) { services.AddDistributedSqlServerCache(setupAction); return services.AddLiquidDistributedCache(withTelemetry); } } } ================================================ FILE: src/Liquid.Cache.SqlServer/Liquid.Cache.SqlServer.csproj ================================================  net8.0 Liquid.Cache.SqlServer enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full Distributed cache extension of Microsoft.Extensions.Caching.SqlServer. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Core/AbstractMappers/LiquidMapper.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Liquid.Core.AbstractMappers { /// [ExcludeFromCodeCoverage] public abstract class LiquidMapper : ILiquidMapper { private readonly string _mapperName; /// /// Create a new instance of /// /// Mapper implementation name. protected LiquidMapper(string mapperName) { _mapperName = mapperName; } /// public async Task Map(TFrom dataObject, string entityName = null) { if (dataObject is null) { throw new ArgumentNullException(nameof(dataObject)); } try { return await MapImpl(dataObject, entityName); } catch (Exception e) { var msg = $"{_mapperName} throw data mapping error: '{e.Message}'"; throw new DataMappingException(msg, e); } } /// /// /// /// /// /// protected abstract Task MapImpl(TFrom dataObject, string entityName = null); } } ================================================ FILE: src/Liquid.Core/AbstractMappers/OcrResultMapper.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Liquid.Core.AbstractMappers { /// /// Defines object that map data between two instance types. /// /// type of data source object. [ExcludeFromCodeCoverage] public abstract class OcrResultMapper : ILiquidMapper { private readonly string _mapperName; /// /// Create a new instance of /// /// Mapper implementation name. public OcrResultMapper(string mapperName) { _mapperName = mapperName; } /// public async Task Map(TFrom dataObject, string entityName = null) { if (dataObject is null) { throw new ArgumentNullException(nameof(dataObject)); } try { return await MapImpl(dataObject); } catch (Exception e) { var msg = $"{_mapperName} throw data mapping error: '{e.Message}'"; throw new DataMappingException(msg, e); } } /// /// Create a new instance of /// with values obtained from . /// /// data source object instance. protected abstract Task MapImpl(TFrom dataObject); } } ================================================ FILE: src/Liquid.Core/Attributes/LiquidSectionNameAttribute.cs ================================================ using System; namespace Liquid.Core.Attributes { /// /// Defines which configuration section, the custom configuration will read from json file. /// /// [AttributeUsage(AttributeTargets.Class)] public class LiquidSectionNameAttribute : Attribute { /// /// Gets the name of the section. /// /// /// The name of the section. /// public string SectionName { get; } /// /// Initializes a new instance of the class. /// /// Name of the section. public LiquidSectionNameAttribute(string sectionName) { SectionName = sectionName; } } } ================================================ FILE: src/Liquid.Core/Base/Enumeration.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace Liquid.Core.Base { /// /// Enumeration Base Class. /// /// [ExcludeFromCodeCoverage] public abstract class Enumeration : IComparable { /// /// Initializes a new instance of the class. /// /// The value. /// The display name. protected Enumeration(int value, string displayName) { Value = value; DisplayName = displayName; } /// /// Gets the value. /// /// /// The value. /// public int Value { get; } /// /// Gets the display name. /// /// /// The display name. /// public string DisplayName { get; } /// /// Converts to string. /// /// /// A that represents this instance. /// public override string ToString() { return DisplayName; } /// /// Gets all. /// /// /// public static IEnumerable GetAll() where T : Enumeration, new() { var type = typeof(T); var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var info in fields) { var instance = new T(); if (info.GetValue(instance) is T locatedValue) { yield return locatedValue; } } } /// /// Determines whether the specified , is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object obj) { var otherValue = obj as Enumeration; if (otherValue == null) { return false; } var typeMatches = GetType() == obj.GetType(); var valueMatches = Value.Equals(otherValue.Value); return typeMatches && valueMatches; } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() { return Value.GetHashCode(); } /// /// Absolutes the difference. /// /// The first value. /// The second value. /// public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue) { var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value); return absoluteDifference; } /// /// Gets the enumeration from id value. /// /// /// The value. /// public static T FromValue(int value) where T : Enumeration, new() { var matchingItem = Parse(value, "value", item => item.Value == value); return matchingItem; } /// /// Gets the enumeration from display name. /// /// /// The display name. /// public static T FromDisplayName(string displayName) where T : Enumeration, new() { var matchingItem = Parse(displayName, "display name", item => item.DisplayName == displayName); return matchingItem; } /// /// Parses the specified value. /// /// /// /// The value. /// The description. /// The predicate. /// /// private static TEnumerable Parse(TValue value, string description, Func predicate) where TEnumerable : Enumeration, new() { var matchingItem = GetAll().FirstOrDefault(predicate); if (matchingItem != null) return matchingItem; var message = $"'{value}' is not a valid {description} in {typeof(TEnumerable)}"; throw new LiquidException(message); } /// /// Compares to. /// /// The other object to be compared. /// public int CompareTo(object obj) { return Value.CompareTo(((Enumeration)obj).Value); } } } ================================================ FILE: src/Liquid.Core/Base/LiquidInterceptorBase.cs ================================================ using Castle.DynamicProxy; using System; using System.Threading.Tasks; namespace Liquid.Core.Base { /// /// Base class to implement interceptors with actions after, before and on exception. /// public abstract class LiquidInterceptorBase : AsyncInterceptorBase { /// /// Initialize an instace of /// protected LiquidInterceptorBase() { } /// /// Traces start, stop and exception of the intercepted method. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// The function to proceed the proceedInfo. protected override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func proceed) { try { await BeforeInvocation(invocation, proceedInfo); await proceed(invocation, proceedInfo).ConfigureAwait(false); } catch (Exception ex) { await OnExceptionInvocation(invocation, proceedInfo, ex); throw; } finally { await AfterInvocation(invocation, proceedInfo, default(object)); } } /// /// Traces start, stop and exception of the intercepted method. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// The function to proceed the proceedInfo. protected sealed override async Task InterceptAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo, Func> proceed) { TResult result = default; try { await BeforeInvocation(invocation, proceedInfo); return await proceed(invocation, proceedInfo).ConfigureAwait(false); } catch (Exception ex) { await OnExceptionInvocation(invocation, proceedInfo, ex); throw; } finally { await AfterInvocation(invocation, proceedInfo, result).ConfigureAwait(false); } } /// /// Override in derived classes to intercept method invocations. /// /// The type of result object. /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// Result object. /// protected abstract Task AfterInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, TResult result); /// /// Override in derived classes to intercept method invocations. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// protected abstract Task BeforeInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo); /// /// Override in derived classes to intercept method invocations. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// THe exception object. /// protected abstract Task OnExceptionInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, Exception exception); } } ================================================ FILE: src/Liquid.Core/Decorators/LiquidContextDecorator.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Microsoft.Extensions.Options; using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Decorators { /// /// Inserts configured context keys in LiquidContext service. /// Includes its behavior in worker service before process execution. /// public class LiquidContextDecorator : ILiquidWorker { private readonly ILiquidWorker _inner; private readonly ILiquidContext _context; private readonly IOptions _options; /// /// Initialize a new instance of /// /// Decorated service. /// Scoped Context service. /// Scoped context keys set. public LiquidContextDecorator(ILiquidWorker inner, ILiquidContext context, IOptions options) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); _context = context ?? throw new ArgumentNullException(nameof(context)); _options = options ?? throw new ArgumentNullException(nameof(options)); } /// public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { object value = default; foreach (var key in _options.Value.Keys) { args.Headers?.TryGetValue(key.KeyName, out value); if (value is null && key.Required) throw new MessagingMissingContextKeysException(key.KeyName); _context.Upsert(key.KeyName, value); } if (_options.Value.Culture) { _context.Upsert("culture", CultureInfo.CurrentCulture.Name); } await _inner.ProcessMessageAsync(args, cancellationToken); } } } ================================================ FILE: src/Liquid.Core/Decorators/LiquidCultureDecorator.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Microsoft.Extensions.Options; using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Decorators { /// /// Configures the culture in the current thread. /// Includes its behavior in worker service before process execution. /// public class LiquidCultureDecorator : ILiquidWorker { private const string _culture = "culture"; private readonly IOptions _options; private readonly ILiquidWorker _inner; /// /// Initialize a new instance of /// /// Decorated service. /// Default culture configuration. public LiquidCultureDecorator(ILiquidWorker inner, IOptions options) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); _options = options ?? throw new ArgumentNullException(nameof(options)); } /// public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { object cultureCode = default; args.Headers?.TryGetValue(_culture, out cultureCode); if (cultureCode is null && !string.IsNullOrEmpty(_options.Value.DefaultCulture)) { cultureCode = _options.Value.DefaultCulture; } if (cultureCode != null) { CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(cultureCode.ToString()); CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(cultureCode.ToString()); } await _inner.ProcessMessageAsync(args, cancellationToken); } } } ================================================ FILE: src/Liquid.Core/Decorators/LiquidScopedLoggingDecorator.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Decorators { /// /// Inserts configured context keys in ILogger service scope. /// Includes its behavior in worker service before process execution. /// public class LiquidScopedLoggingDecorator : ILiquidWorker { private readonly ILogger> _logger; private readonly IOptions _options; private readonly ILiquidWorker _inner; /// /// Initialize a new instance of /// /// Decorated service. /// Default culture configuration. /// Logger service instance. public LiquidScopedLoggingDecorator(ILiquidWorker inner , IOptions options , ILogger> logger) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); _options = options ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { var scope = new List>(); object value = default; foreach (var key in _options.Value.Keys) { args.Headers?.TryGetValue(key.KeyName, out value); if (value is null && key.Required) throw new MessagingMissingScopedKeysException(key.KeyName); scope.Add(new KeyValuePair(key.KeyName, value)); } using (_logger.BeginScope(scope.ToArray())) { await _inner.ProcessMessageAsync(args, cancellationToken); } } } } ================================================ FILE: src/Liquid.Core/Entities/ChatCompletionResult.cs ================================================ namespace Liquid.Core.Entities { /// /// Chat completions result set. /// public class ChatCompletionResult { /// /// The content of the response message. /// public string Content { get; set; } /// /// The reason the model stopped generating tokens, together with any applicable details. /// public string FinishReason { get; set; } /// /// The total number of tokens processed for the completions request and response. /// public int Usage { get; set; } /// /// The number of tokens used by the prompt. /// public int PromptUsage { get; set; } /// /// The number of tokens used by the completion. /// public int CompletionUsage { get; set; } } } ================================================ FILE: src/Liquid.Core/Entities/ClientDictionary.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Entities { /// /// Client dictionary to store client instances. /// /// Type of client service. [ExcludeFromCodeCoverage] public class ClientDictionary { /// /// Number of executions for this client. /// public int Executions { get; set; } = 1; /// /// Client connection alias. /// public string ClientId { get; set; } /// /// Client instance. /// public T Client { get; set; } /// /// Initialize a new instance of . /// /// Client connection alias. /// Client instance. public ClientDictionary(string clientId, T client) { ClientId = clientId; Client = client; } } } ================================================ FILE: src/Liquid.Core/Entities/ConsumerErrorEventArgs.cs ================================================ using System; namespace Liquid.Core.Entities { /// /// Arguments for processing errors occurred during process execution. /// public class ConsumerErrorEventArgs { /// /// Represents errors that occur during process execution. /// public Exception Exception { get; set; } } } ================================================ FILE: src/Liquid.Core/Entities/ConsumerMessageEventArgs.cs ================================================ using System.Collections.Generic; namespace Liquid.Core.Entities { /// /// /// /// public class ConsumerMessageEventArgs { /// /// /// public TEvent Data { get; set; } /// /// /// public IDictionary Headers { get; set; } } } ================================================ FILE: src/Liquid.Core/Entities/FunctionBody.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace Liquid.Core.Entities { /// /// The body of a function to be called. /// [ExcludeFromCodeCoverage] public class FunctionBody { /// The name of the function to be called. public string Name { get; set; } /// /// A description of what the function does. The model will use this description when selecting the function and /// interpreting its parameters. /// public string Description { get; set; } /// /// The parameters the function accepts, described as a JSON Schema object. /// /// To assign an object to this property use . /// /// /// To assign an already formatted json string to this property use . /// /// public BinaryData Parameters { get; set; } /// Initializes a new instance of . /// The name of the function to be called. /// /// A description of what the function does. The model will use this description when selecting the function and /// interpreting its parameters. /// /// The parameters the function accepts, described as a JSON Schema object. /// /// public FunctionBody(string name, string description, BinaryData parameters) { if (string.IsNullOrEmpty(name)) { throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name)); } if (string.IsNullOrEmpty(description)) { throw new ArgumentException($"'{nameof(description)}' cannot be null or empty.", nameof(description)); } Name = name; Description = description; Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); } /// /// Initializes a new instance of . /// /// function definition JSON string. public FunctionBody(string functionBody) { var function = JsonSerializer.Deserialize(functionBody); Name = function.GetProperty("name").ToString(); Description = function.GetProperty("description").ToString(); Parameters = BinaryData.FromObjectAsJson(function.GetProperty("parameters")); } } } ================================================ FILE: src/Liquid.Core/Entities/LiquidBlob.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Entities { /// /// Set de propriedades referentes à um item do BlobStorage. /// [ExcludeFromCodeCoverage] public class LiquidBlob { /// /// Lista de tags referentes ao blob. /// public IDictionary Tags { get; set; } = new Dictionary(); /// /// Conteúdo do blob. /// public byte[] Blob { get; set; } /// /// Nome do arquivo no Storage. /// public string Name { get; set; } /// /// Caminho do blob. /// public string AbsoluteUri { get; set; } } } ================================================ FILE: src/Liquid.Core/Entities/LiquidEntity.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Entities { /// /// Represents the repository entity /// /// The type of the identifier. [ExcludeFromCodeCoverage] public class LiquidEntity { /// /// Gets or sets the identifier. /// /// /// The identifier. /// public virtual TIdentifier Id { get; set; } } } ================================================ FILE: src/Liquid.Core/Entities/OcrResult.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; namespace Liquid.Core.Entities { /// /// Optical Character Recognition result set. /// [ExcludeFromCodeCoverage] public class OcrResult { /// /// Recognition result content. /// public string Content { get; set; } /// ///Analyzed pages. /// public List Pages { get; set; } } /// /// Analyzed page content. /// [ExcludeFromCodeCoverage] public class PageInfo { /// /// recognition result page index. /// public int PageNumber { get; set; } /// /// The unit used by the words and lines data polygon properties. /// public string PolygonUnit { get; set; } /// /// Extracted words from the page. /// public List Words { get; set; } = new List(); /// /// Extracted lines from the page. /// public List Lines { get; set; } = new List(); } /// /// A word object consisting of a contiguous sequence of characters. /// [ExcludeFromCodeCoverage] public class WordData { /// /// Text content of the word. /// public string Content { get; set; } /// /// Confidence of correctly extracting the word. /// public float Confidence { get; set; } /// /// The polygon that outlines the content of this word. Coordinates are specified relative to the /// top-left of the page, and points are ordered clockwise from the left relative to the word /// orientation. /// public List Polygon { get; set; } = new List(); } /// /// A content line object consisting of an adjacent sequence of content elements /// [ExcludeFromCodeCoverage] public class LineData { /// /// Concatenated content of the contained elements in reading order. /// public string Content { get; set; } /// /// The polygon that outlines the content of this line. Coordinates are specified relative to the /// top-left of the page, and points are ordered clockwise from the left relative to the line /// orientation. /// public List Polygon { get; set; } = new List(); } } ================================================ FILE: src/Liquid.Core/Exceptions/DataMappingException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Exceptions { /// /// Occurs when an exception is raised during data mapping. /// [ExcludeFromCodeCoverage] public class DataMappingException : Exception { /// /// Initializes a new instance of the class. /// public DataMappingException() { } /// /// Initializes a new instance of the class with a specified error message. /// /// public DataMappingException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. /// /// /// public DataMappingException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/DatabaseContextException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when a Repository database throw an error. /// [ExcludeFromCodeCoverage] [Serializable] public class DatabaseContextException : LiquidException { /// public DatabaseContextException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// Error message custom text. /// Exception throwed by the client. public DatabaseContextException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/ExceptionCustomCodes.cs ================================================ using Liquid.Core.Base; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Exceptions { /// /// Contains Exceptions Custom Codes to be processed and converted to custom error codes. /// /// [ExcludeFromCodeCoverage] public class ExceptionCustomCodes : Enumeration { /// /// Indicates that the item is not found. /// public static readonly ExceptionCustomCodes NotFound = new ExceptionCustomCodes(404, "NotFound"); /// /// Indicates that an internal error has occurred. /// public static readonly ExceptionCustomCodes InternalError = new ExceptionCustomCodes(500, "InternalError"); /// /// Indicates that the request is bad formatted a validation error has occurred. /// public static readonly ExceptionCustomCodes BadRequest = new ExceptionCustomCodes(400, "BadRequest"); /// /// Indicates a conflict. /// public static readonly ExceptionCustomCodes Conflict = new ExceptionCustomCodes(409, "Conflict"); /// /// Indicates the resource is not accessible. /// public static readonly ExceptionCustomCodes Forbidden = new ExceptionCustomCodes(403, "Forbidden"); /// /// Indicates a timeout error. /// public static readonly ExceptionCustomCodes Timeout = new ExceptionCustomCodes(408, "Timeout"); /// /// Indicates the item is not authorized. /// public static readonly ExceptionCustomCodes Unauthorized = new ExceptionCustomCodes(401, "Unauthorized"); /// /// Initializes a new instance of the class. /// /// The value. /// The display name. public ExceptionCustomCodes(int value, string displayName) : base(value, displayName) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/LiquidCustomException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Class responsible for custom exception codes handling. /// /// [Serializable] [ExcludeFromCodeCoverage] public class LiquidCustomException : LiquidException { /// /// Gets the response code. /// /// /// The response code. /// public ExceptionCustomCodes ResponseCode { get; } /// /// Initializes a new instance of the class. /// /// The message. /// The response code. public LiquidCustomException(string message, ExceptionCustomCodes responseCode) : base(message) { ResponseCode = responseCode; } /// /// Initializes a new instance of the class. /// /// The message. /// The response code. /// The inner exception. public LiquidCustomException(string message, ExceptionCustomCodes responseCode, Exception innerException) : base(message, innerException) { ResponseCode = responseCode; } } } ================================================ FILE: src/Liquid.Core/Exceptions/LiquidDatabaseSettingsDoesNotExistException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when the database connection string is not found in appsettings file. Check the connection id and the configuration file. /// /// [ExcludeFromCodeCoverage] [Serializable] public class LiquidDatabaseSettingsDoesNotExistException : LiquidException { /// /// Initializes a new instance of the class. /// /// The connection identifier. public LiquidDatabaseSettingsDoesNotExistException(string databaseName) : base($"The connection string for database '{databaseName}' does not exist.") { } /// public LiquidDatabaseSettingsDoesNotExistException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/LiquidException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Liquid Base Custom Exception Class. Derived from class. /// /// [Serializable] [ExcludeFromCodeCoverage] public class LiquidException : Exception { /// /// Initializes a new instance of the class. /// public LiquidException() { } /// /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. public LiquidException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. public LiquidException(string message, Exception innerException) : base(message, innerException) { } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return $"Liquid Exception -> {base.ToString()}"; } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingConsumerException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when an exception is raised consuming a message. /// /// [Serializable] [ExcludeFromCodeCoverage] public class MessagingConsumerException : LiquidException { /// /// Initializes a new instance of the class. /// /// The inner exception. public MessagingConsumerException(Exception innerException) : base("An error has occurred consuming message. See inner exception for more detail.", innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingMissingConfigurationException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when a Configuration in settings configuration does not exist. /// /// [Serializable] [ExcludeFromCodeCoverage] public class MessagingMissingConfigurationException : LiquidException { /// public MessagingMissingConfigurationException(Exception innerException, string settingsName) : base($"The messaging configuration section {settingsName} is missing. See inner exception for more detail.", innerException) { } /// public MessagingMissingConfigurationException(string message) : base(message) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingMissingContextKeysException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// [ExcludeFromCodeCoverage] [Serializable] public class MessagingMissingContextKeysException : LiquidException { /// public MessagingMissingContextKeysException() { } /// public MessagingMissingContextKeysException(string contextKey) : base($"The value of required context key '{contextKey}' was not found in request.") { } /// public MessagingMissingContextKeysException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingMissingScopedKeysException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// [ExcludeFromCodeCoverage] [Serializable] public class MessagingMissingScopedKeysException : LiquidException { /// public MessagingMissingScopedKeysException() { } /// public MessagingMissingScopedKeysException(string contextKey) : base($"The value of required logging scoped key '{contextKey}' was not found in request.") { } /// public MessagingMissingScopedKeysException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingMissingSettingsException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when a Configuration in settings configuration does not exist. /// /// [Serializable] [ExcludeFromCodeCoverage] public class MessagingMissingSettingsException : LiquidException { /// /// Initializes a new instance of /// /// Configuration set name. public MessagingMissingSettingsException(string settingsName) : base($"The messaging configuration settings name {settingsName} does not exist. Please check your configuration file.") { } /// public MessagingMissingSettingsException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/MessagingProducerException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when an exception occurs sending a message. /// /// [Serializable] [ExcludeFromCodeCoverage] public class MessagingProducerException : LiquidException { /// /// Initializes a new instance of the class. /// /// The inner exception. public MessagingProducerException(Exception innerException) : base("An error has occurred when sending message. See inner exception for more detail.", innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/SerializerFailException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when the serialization fail. /// /// [Serializable] [ExcludeFromCodeCoverage] public class SerializerFailException : LiquidException { /// public SerializerFailException(string message) : base(message) { } /// public SerializerFailException(string nameOfContent, Exception innerException) : base($"An error occurred whilst serializing of content {nameOfContent} : ", innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/UnitOfWorkTransactionNotStartedException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when the transaction is not started. /// /// [ExcludeFromCodeCoverage] [Serializable] public class UnitOfWorkTransactionNotStartedException : LiquidException { /// /// Initializes a new instance of the class. /// public UnitOfWorkTransactionNotStartedException() : base("The transaction has been not started. Please start the transaction") { } /// public UnitOfWorkTransactionNotStartedException(string message) : base(message) { } /// public UnitOfWorkTransactionNotStartedException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Exceptions/UnitOfWorkTransactionWithoutRepositoryException.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Exceptions { /// /// Occurs when the transaction is called before a repository method is called. /// /// [Serializable] [ExcludeFromCodeCoverage] public class UnitofWorkTransactionWithoutRepositoryException : LiquidException { /// /// Initializes a new instance of the class. /// public UnitofWorkTransactionWithoutRepositoryException() : base("You need to get a repository first to start an transaction. Use 'GetRepository' method.") { } /// public UnitofWorkTransactionWithoutRepositoryException(string message) : base(message) { } /// public UnitofWorkTransactionWithoutRepositoryException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Core/Extensions/ByteExtension.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; namespace Liquid.Core.Extensions { /// /// Byte Extensions Class. /// [ExcludeFromCodeCoverage] public static class ByteExtension { /// /// Gets the size of the kb. /// /// The bytes. /// public static double GetKbSize(this byte[] bytes) { if (bytes == null || bytes.Length == 0) return 0; return bytes.Length / 1024d; } /// /// Gets the size of the mb. /// /// The bytes. /// public static double GetMbSize(this byte[] bytes) { if (bytes == null || bytes.Length == 0) return 0; return bytes.Length / Math.Pow(1024, 2); } /// /// Gets the size of the gb. /// /// The bytes. /// public static double GetGbSize(this byte[] bytes) { if (bytes == null || bytes.Length == 0) return 0; return bytes.Length / Math.Pow(1024, 3); } /// /// Parses byte array json to a specific object type. /// /// type of object to be parsed. /// /// Provides options to be used with System.Text.Json.JsonSerializer. public static T ParseJson(this byte[] json, JsonSerializerOptions options = null) { if (json == null || json.Length == 0) return default; var result = JsonSerializer.Deserialize(Encoding.Default.GetString(json), options); return result; } } } ================================================ FILE: src/Liquid.Core/Extensions/CommonExtensions.cs ================================================ using System.Collections.Generic; using System.Linq; namespace Liquid.Core.Extensions { /// /// Util Extensions Class /// public static class CommonExtensions { /// /// The Gzip content type /// public const string GZipContentType = "application/gzip"; /// /// The content type header /// public const string ContentTypeHeader = "ContentType"; /// /// Adds the range from the elements dictionary to the source dictionary. If the element from elements dictionary alreads exists in source, it will be discarded. /// /// The source dictionary. /// The elements to be added to source. public static void AddRange(this IDictionary source, IDictionary elements) { if (elements == null || !elements.Any()) return; foreach (var element in elements) { if (!source.ContainsKey(element.Key)) source.Add(element); } } } } ================================================ FILE: src/Liquid.Core/Extensions/DateTimeExtension.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace Liquid.Core.Extensions { /// /// Date time extensions class. /// [ExcludeFromCodeCoverage] public static class DateTimeExtension { /// /// Converts datetime to Iso 8601 format. /// /// The date. /// public static string ToIso8601(this DateTime date) { return date.ToString("o", DateTimeFormatInfo.InvariantInfo); } /// /// Converts datetime to SQL invariant format. /// /// The date. /// public static string ToSql(this DateTime date) { return date.ToString("yyyy-MM-dd HH:mm:ss", DateTimeFormatInfo.InvariantInfo); } /// /// To the Oracle SQL date. /// /// The date time. /// public static string ToOracleSql(this DateTime dateTime) { return $"to_date('{dateTime:dd.MM.yyyy HH:mm:ss}','dd.mm.yyyy hh24.mi.ss')"; } /// /// Converts datetime to Unix format. /// /// The date. /// public static long ToUnix(this DateTime date) { var timeSpan = date - new DateTime(1970, 1, 1, 0, 0, 0); return (long)timeSpan.TotalSeconds; } /// /// Gets date time from unix format. /// /// The unix. /// public static DateTime FromUnix(this long unix) { var epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); return epoch.AddSeconds(unix); } /// /// Gets the age from the birth date. /// /// The birth date. /// public static int GetAge(this DateTime birthDate) { var today = DateTime.Today; var result = today.Year - birthDate.Year; if (today.DayOfYear < birthDate.DayOfYear) { result--; } return result; } /// /// Converts the datetime to a specific time zone. /// /// The date time. /// The time zone identifier. /// public static DateTime ToTimeZone(this DateTime dateTime, string timeZoneId) { var universalDateTime = dateTime.ToUniversalTime(); var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); var convertedDateTime = TimeZoneInfo.ConvertTimeFromUtc(universalDateTime, timeZone); return convertedDateTime; } /// /// Determines whether this date is weekend. /// /// The date. /// /// true if the specified d is weekend; otherwise, false. /// public static bool IsWeekend(this DateTime date) { return !date.IsWeekday(); } /// /// Determines whether this instance is weekday. /// /// The date. /// /// true if the specified date is weekday; otherwise, false. /// public static bool IsWeekday(this DateTime date) { switch (date.DayOfWeek) { case DayOfWeek.Sunday: case DayOfWeek.Saturday: return false; default: return true; } } /// /// Adds the workdays. /// /// The date. /// The days. /// public static DateTime AddWorkdays(this DateTime date, int days) { while (date.IsWeekend()) date = date.AddDays(1.0); for (var i = 0; i < days; ++i) { date = date.AddDays(1.0); while (date.IsWeekend()) date = date.AddDays(1.0); } return date; } /// /// Gets the last day of month. /// /// The date time. /// public static DateTime GetLastDayOfMonth(this DateTime date) { return new DateTime(date.Year, date.Month, 1).AddMonths(1).AddDays(-1); } /// /// Gets the first day of month. /// /// The date. /// public static DateTime GetFirstDayOfMonth(this DateTime date) { return new DateTime(date.Year, date.Month, 1); } /// /// Get the elapsed time from date time now since the input DateTime /// /// Input DateTime /// Returns a TimeSpan value with the elapsed time since the input DateTime /// /// TimeSpan elapsed = dtStart.Elapsed(); /// public static TimeSpan Elapsed(this DateTime date) { return DateTime.Now.Subtract(date); } } } ================================================ FILE: src/Liquid.Core/Extensions/DependencyInjection/IServiceCollectionAutoMapperExtensions.cs ================================================ using AutoMapper; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace Liquid.Core.Extensions.DependencyInjection { /// /// Extensions to scan for AutoMapper classes and register the configuration, mapping, and extensions with the service collection: /// /// Finds classes and initializes a new , /// Scans for , , and implementations and registers them as , /// Registers as , and /// Registers as a configurable (default is ) /// /// After calling LiquidAddAutoMapper you can resolve an instance from a scoped service provider, or as a dependency /// To use /// /// QueryableExtensions.Extensions.ProjectTo{TDestination}(IQueryable,IConfigurationProvider, /// System.Linq.Expressions.Expression{System.Func{TDestination, object}}[]) /// /// /// you can resolve the instance directly for from an instance. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionAutoMapperExtensions { /// /// Adds the automatic mapper. /// /// The services. /// The assemblies. /// public static IServiceCollection LiquidAddAutoMapper(this IServiceCollection services, params Assembly[] assemblies) => AddAutoMapperClasses(services, null, assemblies); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The assemblies. /// public static IServiceCollection LiquidAddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The assemblies. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Assembly[] assemblies) => AddAutoMapperClasses(services, configAction, assemblies); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The assemblies. /// The service lifetime. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), assemblies, serviceLifetime); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The assemblies. /// The service lifetime. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, configAction, assemblies, serviceLifetime); /// /// Adds the automatic mapper. /// /// The services. /// The assemblies. /// The service lifetime. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, IEnumerable assemblies, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, null, assemblies, serviceLifetime); /// /// Adds the automatic mapper. /// /// The services. /// The profile assembly marker types. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, params Type[] profileAssemblyMarkerTypes) => AddAutoMapperClasses(services, null, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The profile assembly marker types. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Type[] profileAssemblyMarkerTypes) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The profile assembly marker types. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, params Type[] profileAssemblyMarkerTypes) => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly)); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The profile assembly marker types. /// The service lifetime. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, (sp, cfg) => configAction?.Invoke(cfg), profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); /// /// Adds the automatic mapper. /// /// The services. /// The configuration action. /// The profile assembly marker types. /// The service lifetime. /// public static IServiceCollection AddAutoMapper(this IServiceCollection services, Action configAction, IEnumerable profileAssemblyMarkerTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddAutoMapperClasses(services, configAction, profileAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), serviceLifetime); /// /// Adds the automatic mapper classes. /// /// The services. /// The configuration action. /// The assemblies to scan. /// The service lifetime. /// private static IServiceCollection AddAutoMapperClasses(IServiceCollection services, Action configAction, IEnumerable assembliesToScan, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { // Just return if we've already added AutoMapper to avoid double-registration if (services.Any(sd => sd.ServiceType == typeof(IMapper))) return services; assembliesToScan = assembliesToScan as Assembly[] ?? assembliesToScan.ToArray(); var toScan = (Assembly[])assembliesToScan; var allTypes = toScan .Where(a => !a.IsDynamic && a.GetName().Name != nameof(AutoMapper)) .Distinct() // avoid AutoMapper.DuplicateTypeMapConfigurationException .SelectMany(a => a.DefinedTypes) .ToArray(); void ConfigAction(IServiceProvider serviceProvider, IMapperConfigurationExpression cfg) { configAction?.Invoke(serviceProvider, cfg); cfg.AddMaps(toScan); } var openTypes = new[] { typeof(IValueResolver<,,>), typeof(IMemberValueResolver<,,,>), typeof(ITypeConverter<,>), typeof(IValueConverter<,>), typeof(IMappingAction<,>) }; foreach (var type in openTypes.SelectMany(openType => allTypes .Where(t => t.IsClass && !t.IsAbstract && t.AsType().ImplementGenericInterface(openType)))) { services.AddTransientAssemblies(type.AsType()); } services.AddSingleton(sp => new MapperConfiguration(cfg => ConfigAction(sp, cfg))); services.Add(new ServiceDescriptor(typeof(IMapper), sp => new Mapper(sp.GetRequiredService(), sp.GetService), serviceLifetime)); return services; } } } ================================================ FILE: src/Liquid.Core/Extensions/DependencyInjection/IServiceCollectionCoreExtensions.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.PipelineBehaviors; using MediatR; using Microsoft.Extensions.DependencyInjection; using System.Reflection; using FluentValidation; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Linq; using System; using Liquid.Core.Decorators; namespace Liquid.Core.Extensions.DependencyInjection { /// /// LiquidCache extensions class. /// public static class IServiceCollectionCoreExtensions { /// /// Registers a service and its /// depending on the value of . /// /// Extended IServiceCollection. /// indicates if this method must register a /// public static IServiceCollection AddLiquidDistributedCache(this IServiceCollection services, bool withTelemetry) { if (withTelemetry) { services.AddScoped(); services.AddScopedLiquidTelemetry(); } else services.AddScoped(); return services; } /// /// Injects mediator handler, validator and Liquid native telemetry for handlers. /// /// Extended service collection. /// Indicates if method should inject Liquid Telemetry Behavior. /// Indicates if method should inject Validators. /// List of assemblies that contains handlers and validators implemantations to be injected. public static void AddLiquidHandlers(this IServiceCollection services, bool withTelemetry, bool withValidators, params Assembly[] assemblies) { if (withValidators) { services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LiquidValidationBehavior<,>)); services.AddValidatorsFromAssemblies(assemblies); } if (withTelemetry) services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LiquidTelemetryBehavior<,>)); services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(assemblies)); } /// /// Register Liquid Worker Service /// with messaging pipeline , /// and domain handlers /// that exists in , with telemetry and validators. /// /// Extended service collection instance. /// Array of assemblies that contains domain handlers implementation. /// public static IServiceCollection AddLiquidMessageConsumer(this IServiceCollection services, params Assembly[] assemblies) where TWorker : class, ILiquidWorker { services.AddLiquidWorkerService(); services.AddLiquidPipeline(); services.AddLiquidDomain(assemblies); return services; } /// /// Register implementation service /// and . /// /// implementation type. /// Type of entity that will be consumed by this service. /// Extended service collection instance. public static IServiceCollection AddLiquidWorkerService(this IServiceCollection services) where TWorker : class, ILiquidWorker { services.AddScoped, TWorker>(); services.AddHostedService>(); return services; } /// /// Register domain handlers and /// its mappers /// that exists in , with telemetry and validators. /// /// Extended service collection instance. /// Array of assemblies that contains domain handlers implementation. public static IServiceCollection AddLiquidDomain(this IServiceCollection services, params Assembly[] assemblies) { services.LiquidAddAutoMapper(assemblies); services.AddLiquidHandlers(withTelemetry: true, withValidators: true, assemblies); return services; } /// /// Register and aditional behaviors /// , /// and . These additional behaviors will be /// performed in the reverse order they were registered. /// /// Extended service collection instance. public static IServiceCollection AddLiquidPipeline(this IServiceCollection services) { services.AddScoped(); services.Decorate, LiquidContextDecorator>(); services.Decorate, LiquidScopedLoggingDecorator>(); services.Decorate, LiquidCultureDecorator>(); return services; } /// /// Changes the previously registered service descriptor /// to the service. /// /// Interface type of service that should be decorated. /// Type of decorator service implementation. /// Extended service collection instance. public static IServiceCollection Decorate(this IServiceCollection services) where TInterface : class where TDecorator : class, TInterface { ServiceDescriptor innerDescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(TInterface)); if (innerDescriptor == null) { throw new InvalidOperationException($"{typeof(TInterface).Name} is not registered"); } var objectFactory = ActivatorUtilities.CreateFactory( typeof(TDecorator), new[] { typeof(TInterface) }); services.Replace(ServiceDescriptor.Describe( typeof(TInterface), s => (TInterface)objectFactory(s, new[] { s.CreateInstance(innerDescriptor) }), innerDescriptor.Lifetime) ); return services; } private static object CreateInstance(this IServiceProvider services, ServiceDescriptor descriptor) { if (descriptor.ImplementationInstance != null) return descriptor.ImplementationInstance; if (descriptor.ImplementationFactory != null) return descriptor.ImplementationFactory(services); return ActivatorUtilities.GetServiceOrCreateInstance(services, descriptor.ImplementationType); } } } ================================================ FILE: src/Liquid.Core/Extensions/DependencyInjection/IServiceCollectionLiquidExtension.cs ================================================ using Castle.DynamicProxy; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System; using System.Linq; namespace Liquid.Core.Extensions.DependencyInjection { /// /// Extends interface. /// public static class IServiceCollectionLiquidExtension { /// /// Register telemetry interceptor for defined services. /// /// Interface type of service that should be intercepted. /// Type of service that should be intercepted. /// Extended IServiceCollection instance. [Obsolete("This method will be removed in the next release. " + "Please use AddScopedLiquidTelemetry, AddTransientLiquidTelemetry or AddSingletonLiquidTelemetry.")] public static IServiceCollection AddLiquidTelemetryInterceptor(this IServiceCollection services) where TInterface : class where TService : TInterface { services.TryAddTransient(typeof(IAsyncInterceptor), typeof(LiquidTelemetryInterceptor)); services.TryAddSingleton(new ProxyGenerator()); return services.AddTransient((provider) => { var proxyGenerator = provider.GetService(); var service = (TInterface)provider.GetRequiredService(); return proxyGenerator.CreateInterfaceProxyWithTarget(service, provider.GetServices().ToArray()); }); } /// /// Register telemetry interceptor for defined services. /// /// Interface type of service that should be intercepted. /// Type of service that should be intercepted. /// Extended IServiceCollection instance. public static IServiceCollection AddScopedLiquidTelemetry(this IServiceCollection services) where TInterface : class where TService : TInterface { services.AddInterceptor(); return services.AddScoped((provider) => { var proxyGenerator = provider.GetService(); var service = (TInterface)provider.GetRequiredService(); return proxyGenerator.CreateInterfaceProxyWithTarget(service, provider.GetServices().ToArray()); }); } /// /// Register telemetry interceptor for defined services. /// /// Interface type of service that should be intercepted. /// Type of service that should be intercepted. /// Extended IServiceCollection instance. public static IServiceCollection AddTransientLiquidTelemetry(this IServiceCollection services) where TInterface : class where TService : TInterface { services.AddInterceptor(); return services.AddTransient((provider) => { var proxyGenerator = provider.GetService(); var service = (TInterface)provider.GetRequiredService(); return proxyGenerator.CreateInterfaceProxyWithTarget(service, provider.GetServices().ToArray()); }); } /// /// Register telemetry interceptor for defined services. /// /// Interface type of service that should be intercepted. /// Type of service that should be intercepted. /// Extended IServiceCollection instance. public static IServiceCollection AddSingletonLiquidTelemetry(this IServiceCollection services) where TInterface : class where TService : TInterface { services.AddInterceptor(); return services.AddSingleton((provider) => { var proxyGenerator = provider.GetService(); var service = (TInterface)provider.GetRequiredService(); return proxyGenerator.CreateInterfaceProxyWithTarget(service, provider.GetServices().ToArray()); }); } /// /// Register a service instance /// with service type. /// /// Service implementation type. /// Extended IServiceCollection instance. public static IServiceCollection AddInterceptor(this IServiceCollection services) { services.TryAddTransient(typeof(IAsyncInterceptor), typeof(TInterceptor)); services.TryAddSingleton(new ProxyGenerator()); return services; } /// /// Register Liquid implementation of Serializer services and Serializer provider. /// , and /// /// Extended IServiceCollection instance. public static IServiceCollection AddLiquidSerializers(this IServiceCollection services) { services.AddTransient(); services.AddTransient(); services.AddTransient(); return services; } } } ================================================ FILE: src/Liquid.Core/Extensions/DependencyInjection/IServiceCollectionTypeExtensions.cs ================================================ using Liquid.Core.Utils; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace Liquid.Core.Extensions.DependencyInjection { /// /// Service Collection Extensions Class. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionTypeExtensions { /// /// Adds a scoped service of the type specified in with an /// implementation of the types specified inside to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The assemblies that contains all implementation types. /// public static IServiceCollection AddScopedAssemblies(this IServiceCollection services, Type serviceType, params Assembly[] assemblies) { var typesToRegister = TypeUtils.GetTypesToRegister(serviceType, assemblies); foreach (var typeToRegister in typesToRegister) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddScoped(interfaceToRegister, typeToRegister); } } return services; } /// /// Adds a scoped service of the type specified in with an /// implementation of the types specified in to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The implementation types. /// public static IServiceCollection AddScopedAssemblies(this IServiceCollection services, Type serviceType, IEnumerable implementationTypes) { foreach (var typeToRegister in implementationTypes) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddScoped(interfaceToRegister, typeToRegister); } } return services; } /// /// Adds a transient service of the type specified in with an /// implementation of the types specified inside to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The assemblies that contains all implementation types. /// public static IServiceCollection AddTransientAssemblies(this IServiceCollection services, Type serviceType, params Assembly[] assemblies) { var typesToRegister = TypeUtils.GetTypesToRegister(serviceType, assemblies); foreach (var typeToRegister in typesToRegister) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddTransient(interfaceToRegister, typeToRegister); } } return services; } /// /// Adds a transient service of the type specified in with an /// implementation of the types specified in to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The implementation types. /// public static IServiceCollection AddTransientAssemblies(this IServiceCollection services, Type serviceType, IEnumerable implementationTypes) { foreach (var typeToRegister in implementationTypes) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddTransient(interfaceToRegister, typeToRegister); } } return services; } /// /// Adds a singleton service of the type specified in with an /// implementation of the types specified inside to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The assemblies that contains all implementation types. /// public static IServiceCollection AddSingletonAssemblies(this IServiceCollection services, Type serviceType, params Assembly[] assemblies) { var typesToRegister = TypeUtils.GetTypesToRegister(serviceType, assemblies); foreach (var typeToRegister in typesToRegister) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddSingleton(interfaceToRegister, typeToRegister); } } return services; } /// /// Adds a singleton service of the type specified in with an /// implementation of the types specified in to the /// specified . /// /// The to add the service to. /// The type of the service to register. /// The implementation types. /// public static IServiceCollection AddSingletonAssemblies(this IServiceCollection services, Type serviceType, IEnumerable implementationTypes) { foreach (var typeToRegister in implementationTypes) { var interfaces = typeToRegister.GetInterfaces(); var interfaceToRegister = interfaces.FirstOrDefault(t => t.GetGenericTypeDefinition() == serviceType); if (interfaceToRegister != null) { services.AddSingleton(interfaceToRegister, typeToRegister); } } return services; } } } ================================================ FILE: src/Liquid.Core/Extensions/DependencyInjection/IServiceProviderExtensions.cs ================================================ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Extensions.DependencyInjection { /// /// Service Provider Extensions Class. /// [ExcludeFromCodeCoverage] public static class IServiceProviderExtensions { /// /// Get all registered /// /// /// public static Dictionary GetAllServiceDescriptors(this IServiceProvider provider) { if (provider is ServiceProvider serviceProvider) { var result = new Dictionary(); var engine = serviceProvider.GetFieldValue("_engine"); var callSiteFactory = engine.GetPropertyValue("CallSiteFactory"); var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup"); if (descriptorLookup is IDictionary dictionary) { foreach (DictionaryEntry entry in dictionary) { result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last")); } } return result; } throw new NotSupportedException($"Type '{provider.GetType()}' is not supported!"); } } } ================================================ FILE: src/Liquid.Core/Extensions/EnumExtension.cs ================================================ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Liquid.Core.Extensions { /// /// Enum Extensions Class. /// [ExcludeFromCodeCoverage] public static class EnumExtension { /// /// Gets the description from enum value. /// /// The value. /// The enum description. public static string GetDescription(this Enum value) { var attribute = value.GetType() .GetField(value.ToString()) .GetCustomAttributes(typeof(DescriptionAttribute), false) .SingleOrDefault() as DescriptionAttribute; return attribute == null ? value.ToString() : attribute.Description; } /// /// Gets the enum attribute. /// /// /// The value. /// the enum attribute. public static T GetAttribute(this Enum value) where T : Attribute { var type = value.GetType(); var name = Enum.GetName(type, value); return type.GetField(name) .GetCustomAttributes(false) .OfType() .SingleOrDefault(); } } } ================================================ FILE: src/Liquid.Core/Extensions/IEnumerableExtension.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; namespace Liquid.Core.Extensions { /// /// Extensions for IEnumerable class. /// [ExcludeFromCodeCoverage] public static class IEnumerableExtension { /// /// Executes the action in each specified item in enumerable. /// /// Type of element inside enumerable. /// The enumerable. /// The action. public static void Each(this IEnumerable enumerable, Action action) { foreach (var item in enumerable) { action(item); } } /// /// Produces a comma separated values of string out of an IEnumerable. /// This would be useful if you want to automatically generate a CSV out of integer, string, or any other primitive data type collection or array. /// /// Type of element inside enumerable. /// The instance. /// The separator. /// public static string ToSeparatedString(this IEnumerable instance, char separator) { var array = instance?.ToArray(); if (array == null || !array.Any()) return null; var csv = new StringBuilder(); array.Each(value => csv.AppendFormat("{0}{1}", value, separator)); return csv.ToString(0, csv.Length - 1); } /// /// Produces a comma separated values of string out of an IEnumerable. /// This would be useful if you want to automatically generate a CSV out of integer, string, or any other primitive data type collection or array. /// /// Type of element inside enumerable. /// The instance. /// public static string ToSeparatedString(this IEnumerable instance) { return instance.ToSeparatedString(','); } /// /// Determines whether this collection is null or empty. /// /// Type of element in collection. /// The collection. /// /// true if the specified collection is null or empty; otherwise, false. /// public static bool IsNullOrEmpty(this IEnumerable collection) { return collection == null || !collection.Any(); } /// /// Determines whether a enumerable is not null or empty. /// /// /// The collection. /// /// true if [is not null or empty] [the specified collection]; otherwise, false. /// public static bool IsNotNullOrEmpty(this IEnumerable collection) { return collection != null && collection.Any(); } /// /// Orders the enumerable based on a property ASC or DESC. Example "OrderBy("Name desc")" /// /// /// The list. /// The sort expression. /// /// No property '" + property + "' in + " + typeof(T).Name + "' public static IEnumerable OrderBy(this IEnumerable list, string sortExpression) { sortExpression += ""; var parts = sortExpression.Split(' '); var descending = false; if (parts.Length > 0 && parts[0] != "") { var property = parts[0]; if (parts.Length > 1) { descending = parts[1].ToLower().Contains("desc"); } var prop = typeof(T).GetProperty(property); if (prop == null) { throw new LiquidException("No property '" + property + "' in + " + typeof(T).Name + "'"); } return descending ? list.OrderByDescending(x => prop.GetValue(x, null)) : list.OrderBy(x => prop.GetValue(x, null)); } return list; } } } ================================================ FILE: src/Liquid.Core/Extensions/IntExtension.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Utils { /// /// Number Extensions Class. /// [ExcludeFromCodeCoverage] public static class IntExtension { /// /// Determines whether a number is is prime number. /// /// The number. /// /// true if [is prime number] [the specified number]; otherwise, false. /// public static bool IsPrimeNumber(this int number) { if (number % 2 == 0) { return number == 2; } var sqrt = (int)Math.Sqrt(number); for (var t = 3; t <= sqrt; t += 2) { if (number % t == 0) { return false; } } return number != 1; } /// /// Determines whether a number is odd. /// /// The number. /// /// true if the specified number is odd; otherwise, false. /// public static bool IsOdd(this int number) { return number % 2 == 0; } /// /// Determines whether a number is even. /// /// The number. /// /// true if the specified number is even; otherwise, false. /// public static bool IsEven(this int number) { return number % 2 != 0; } /// /// Format a double using the local culture currency settings. /// /// The double to be formatted. /// The double formatted based on the local culture currency settings. public static string ToLocalCurrencyString(this double value) { return $"{value:C}"; } } } ================================================ FILE: src/Liquid.Core/Extensions/ObjectExtension.cs ================================================ using Liquid.Core.Utils; using System; using System.Globalization; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Text.Json; namespace Liquid.Core.Extensions { /// /// Object extensions class. /// public static class ObjectExtension { /// /// Determines whether [is of type] [the specified value]. /// /// /// The value. /// /// true if [is of type] [the specified value]; otherwise, false. /// public static bool IsOfType(this object value) { return value is T; } /// /// Determines whether this instance is datetime. /// /// The expression. /// /// true if the specified expression is datetime; otherwise, false. /// public static bool IsDatetime(this object expression) { return expression.ToDatetime(out DateTime? _); } /// /// Converts object to date time. /// /// The expression. /// The outer. /// public static bool ToDatetime(this object expression, out DateTime? outer) { outer = null; if (expression == null) return false; if (expression.IsOfType()) return true; if (DateTime.TryParseExact( Convert.ToString(expression, CultureInfo.InvariantCulture), DateTimeFormatInfo.CurrentInfo?.GetAllDateTimePatterns('d'), CultureInfo.CurrentCulture, DateTimeStyles.None, out var parsed)) outer = parsed; return outer.HasValue; } /// /// Determines whether this instance is boolean. /// /// The expression. /// /// true if the specified expression is boolean; otherwise, false. /// public static bool IsBoolean(this object expression) { return expression.ToBoolean(out bool? _); } /// /// Converts object to boolean. /// /// The expression. /// The outer. /// public static bool ToBoolean(this object expression, out bool? outer) { outer = null; if (expression == null) return false; if (bool.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture), out var parsed)) outer = parsed; return outer.HasValue; } /// /// Determines whether this instance is unique identifier. /// /// The expression. /// /// true if the specified expression is unique identifier; otherwise, false. /// public static bool IsGuid(this object expression) { return expression.ToGuid(out _); } /// /// Converts object to unique identifier. /// /// The expression. /// The outer. /// private static bool ToGuid(this object expression, out Guid? outer) { outer = null; if (expression == null) return false; if (Guid.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture), out var parsed)) outer = parsed; return outer.HasValue; } /// /// Determines whether this instance is numeric. /// /// The expression. /// /// true if the specified expression is numeric; otherwise, false. /// public static bool IsNumeric(this object expression) { return expression != null && double.TryParse(expression.ToString(), out _); } /// /// Determines whether this instance is integer. /// /// The expression. /// /// true if the specified expression is integer; otherwise, false. /// public static bool IsInteger(this object expression) { return expression.ToInteger(out long? _); } /// /// Converts object to integer. /// /// The expression. /// The outer. /// public static bool ToInteger(this object expression, out long? outer) { outer = null; if (expression == null) return false; if (long.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture), NumberStyles.Integer | NumberStyles.AllowParentheses | NumberStyles.AllowThousands | NumberStyles.AllowTrailingSign, CultureInfo.InvariantCulture, out var parsed)) outer = parsed; return outer.HasValue; } /// /// Determines whether this instance is double. /// /// The expression. /// /// true if the specified expression is double; otherwise, false. /// public static bool IsDouble(this object expression) { return expression.ToDouble(out double? _); } /// /// Converts object to double. /// /// The expression. /// The outer. /// public static bool ToDouble(this object expression, out double? outer) { outer = null; if (expression == null) return false; if (double.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture), NumberStyles.AllowThousands | NumberStyles.AllowTrailingSign | NumberStyles.Currency | NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed)) outer = parsed; return outer.HasValue; } /// /// Determines whether [is primitive type]. /// /// The object. /// /// true if [is primitive type] [the specified object]; otherwise, false. /// public static bool IsPrimitiveType(this object obj) { return obj.GetType().IsPrimitive; } /// /// Converts the object to json string. /// /// /// Provides options to be used with System.Text.Json.JsonSerializer. public static string ToJsonString(this object source, JsonSerializerOptions options = null) { return source == null ? null : JsonSerializer.Serialize(source, options); } /// /// Converts the object to json bytes. /// /// /// Provides options to be used with System.Text.Json.JsonSerializer. public static byte[] ToJsonBytes(this object source, JsonSerializerOptions options = null) { if (source == null) return new byte[0]; var instring = JsonSerializer.Serialize(source, options); return Encoding.Default.GetBytes(instring); } /// /// Gets the field value. /// /// The object. /// Name of the field. /// /// obj /// Couldn't find field {fieldName} in type {objType.FullName} public static object GetFieldValue(this object obj, string fieldName) { ArgumentNullException.ThrowIfNull(obj); Type objType = obj.GetType(); var fieldInfo = TypeUtils.GetFieldInfo(objType, fieldName); if (fieldInfo == null) throw new ArgumentOutOfRangeException(fieldName, $"Couldn't find field {fieldName} in type {objType.FullName}"); return fieldInfo.GetValue(obj); } /// /// Sets the field value. /// /// The object. /// Name of the field. /// The value. /// obj /// Couldn't find field {fieldName} in type {objType.FullName} public static void SetFieldValue(this object obj, string fieldName, object val) { ArgumentNullException.ThrowIfNull(obj); Type objType = obj.GetType(); var fieldInfo = TypeUtils.GetFieldInfo(objType, fieldName); if (fieldInfo == null) throw new ArgumentOutOfRangeException(fieldName, $"Couldn't find field {fieldName} in type {objType.FullName}"); fieldInfo.SetValue(obj, val); } /// /// Gets the property value. /// /// The object. /// Name of the property. /// /// obj /// Couldn't find property {propertyName} in type {objType.FullName} public static object GetPropertyValue(this object obj, string propertyName) { ArgumentNullException.ThrowIfNull(obj); Type objType = obj.GetType(); var propertyInfo = TypeUtils.GetPropertyInfo(objType, propertyName); if (propertyInfo == null) throw new ArgumentOutOfRangeException(propertyName, $"Couldn't find property {propertyName} in type {objType.FullName}"); return propertyInfo.GetValue(obj, null); } /// /// Sets the property value. /// /// The object. /// Name of the property. /// The value. /// obj /// Couldn't find property {propertyName} in type {objType.FullName} public static void SetPropertyValue(this object obj, string propertyName, object val) { ArgumentNullException.ThrowIfNull(obj); Type objType = obj.GetType(); var propertyInfo = TypeUtils.GetPropertyInfo(objType, propertyName); if (propertyInfo == null) throw new ArgumentOutOfRangeException(propertyName, $"Couldn't find property {propertyName} in type {objType.FullName}"); propertyInfo.SetValue(obj, val, null); } } } ================================================ FILE: src/Liquid.Core/Extensions/StreamExtension.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; namespace Liquid.Core.Extensions { /// /// Stream extensions class. /// [ExcludeFromCodeCoverage] public static class StreamExtension { /// /// Gets the string from UTF8 stream. /// /// The stream source. /// The result string from stream. public static string AsStringUtf8(this Stream source) { if (source == null) return null; string documentContents; using (var readStream = new StreamReader(source, Encoding.UTF8)) { documentContents = readStream.ReadToEnd(); } return documentContents; } /// /// Gets the string from ASCII stream. /// /// The stream source. /// The result string from stream. public static string AsStringAscii(this Stream source) { if (source == null) return null; string documentContents; using (var readStream = new StreamReader(source, Encoding.ASCII)) { documentContents = readStream.ReadToEnd(); } return documentContents; } /// /// Gets the string from UNICODE stream. /// /// The stream source. /// The result string from stream. public static string AsStringUnicode(this Stream source) { if (source == null) return null; string documentContents; using (var readStream = new StreamReader(source, Encoding.Unicode)) { documentContents = readStream.ReadToEnd(); } return documentContents; } /// /// Converts a stream to byte array. /// /// The stream. /// public static byte[] ToByteArray(this Stream stream) { using var ms = new MemoryStream(); stream.CopyTo(ms); return ms.ToArray(); } } } ================================================ FILE: src/Liquid.Core/Extensions/StringExtension.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; namespace Liquid.Core.Extensions { /// /// String Extensions Class. /// /// [ExcludeFromCodeCoverage] public static class StringExtension { /// /// Determines whether a string contains a specified value. /// /// The string source. /// The value to compare. /// Type of the comparison. /// /// true if source contains the specified value; otherwise, false. /// public static bool Contains(this string source, string value, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) { return source.IndexOf(value, comparisonType) >= 0; } /// /// Removes the line endings. /// /// The string value. /// The string without line endings. public static string RemoveLineEndings(this string value) { if (string.IsNullOrEmpty(value)) { return value; } var lineSeparator = ((char)0x2028).ToString(); var paragraphSeparator = ((char)0x2029).ToString(); return value.Replace("\r\n", string.Empty) .Replace("\n", string.Empty) .Replace("\r", string.Empty) .Replace(lineSeparator, string.Empty) .Replace(paragraphSeparator, string.Empty); } /// /// Converts the string representation of a Guid to its Guid /// equivalent. A return value indicates whether the operation /// succeeded. /// /// A string containing a Guid to convert. /// /// if was converted /// successfully; otherwise, . /// /// /// When this method returns, contains the Guid value equivalent to /// the Guid contained in , if the conversion /// succeeded, or if the conversion failed. /// The conversion fails if the parameter is a /// reference ( in /// Visual Basic), or is not of the correct format. /// public static bool IsGuid(this string guid) { return guid != null && Guid.TryParse(guid, out _); } /// /// Determines whether this string is a valid http url. /// /// The string. /// /// true if [is valid URL] [the specified text]; otherwise, false. /// public static bool IsValidHttpUrl(this string str) { return Uri.TryCreate(str, UriKind.Absolute, out _); } /// /// Appends to the string builder if matchers the condition. /// /// The builder. /// The value. /// if set to true appends to string builder. /// public static StringBuilder AppendIf(this StringBuilder builder, string value, bool condition) { if (condition) builder.Append(value); return builder; } /// /// Converts a string to guid. /// /// The string. /// public static Guid ToGuid(this string str) { Guid.TryParse(str, out var returnValue); return returnValue; } /// /// Converts string to enum object /// /// Type of enum /// String value to convert /// Returns enum object public static T ToEnum(this string value) where T : struct { return (T)Enum.Parse(typeof(T), value, true); } /// /// Computes the hash of the string using a specified hash algorithm /// /// The string to hash /// The hash key. /// The hash algorithm to use /// /// The resulting hash or an empty string on error /// public static string CreateHash(this string input, string key, HashType hashType) { try { var hash = GetComputedHash(input, key, hashType); var ret = new StringBuilder(); foreach (var hashByte in hash) ret.Append(hashByte.ToString("x2")); return ret.ToString(); } catch { return string.Empty; } } /// /// Gets the hash. /// /// The input. /// The key. /// The hash. /// private static byte[] GetComputedHash(string input, string key, HashType hash) { var inputBytes = Encoding.UTF8.GetBytes(input); var inputKey = Encoding.UTF8.GetBytes(key); return hash switch { HashType.HMacSha256 => new HMACSHA256(inputKey).ComputeHash(inputBytes), HashType.HMacSha384 => new HMACSHA384(inputKey).ComputeHash(inputBytes), HashType.HMacSha512 => new HMACSHA512(inputKey).ComputeHash(inputBytes), HashType.Sha256 => SHA256.Create().ComputeHash(inputBytes), HashType.Sha384 => SHA384.Create().ComputeHash(inputBytes), HashType.Sha512 => SHA512.Create().ComputeHash(inputBytes), _ => inputBytes, }; } /// /// Parses a string json to a specific object. /// /// type of object to be parsed. /// /// Provides options to be used with System.Text.Json.JsonSerializer. /// the object parsed from json. public static T ParseJson(this string json, JsonSerializerOptions options = null) { if (string.IsNullOrEmpty(json)) return default; var result = JsonSerializer.Deserialize(json, options); return result; } /// /// Gets the stream from string using UTF8 encoding. /// /// The string source. /// UTF8 encoded stream. public static Stream ToStreamUtf8(this string source) { if (source == null) return null; var stream = new MemoryStream(Encoding.UTF8.GetBytes(source)); return stream; } /// /// Gets the stream from string using ASCII encoding. /// /// The string source. /// ASCII encoded stream. public static Stream ToStreamAscii(this string source) { if (source == null) return null; var stream = new MemoryStream(Encoding.ASCII.GetBytes(source)); return stream; } /// /// Gets the stream from string using UTF8 encoding. /// /// The string source. /// Unicode encoded stream. public static Stream ToStreamUnicode(this string source) { if (source == null) return null; var stream = new MemoryStream(Encoding.Unicode.GetBytes(source)); return stream; } /// /// Serializes an object to xml. /// /// The object. /// public static string ToXml(this object obj) { string retVal; using (var ms = new MemoryStream()) { var xs = new XmlSerializer(obj.GetType()); xs.Serialize(ms, obj); ms.Flush(); ms.Position = 0; retVal = ms.AsStringUtf8(); } return retVal; } /// /// Parse a xml to an object. /// /// type of object. /// The xml string. /// public static T ParseXml(this string str) where T : new() { var xs = new XmlSerializer(typeof(T)); var stream = str.ToStreamUtf8(); if (xs.CanDeserialize(new XmlTextReader(stream))) { stream.Position = 0; return (T)xs.Deserialize(stream); } return default; } /// /// /// public enum HashType { /// /// The HMACSHA256 Hash type. /// HMacSha256, /// /// The HMACSHA384 Hash type. /// HMacSha384, /// /// The HMACSHA512 Hash type. /// HMacSha512, /// /// The SHA256 Hash type. /// Sha256, /// /// The SHA384 Hash type. /// Sha384, /// /// The SHA512 Hash type. /// Sha512 } } } ================================================ FILE: src/Liquid.Core/Extensions/TypeExtensions.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace Liquid.Core.Extensions { /// /// /// [ExcludeFromCodeCoverage] public static class TypeExtensions { /// /// Implements the generic interface. /// /// The type. /// Type of the interface. /// public static bool ImplementGenericInterface(this Type type, Type interfaceType) => type.IsGenericType(interfaceType) || type.GetTypeInfo().ImplementedInterfaces.Any(@interface => @interface.IsGenericType(interfaceType)); /// /// Determines whether [is generic type] [the specified generic type]. /// /// The type. /// Type of the generic. /// /// true if [is generic type] [the specified generic type]; otherwise, false. /// public static bool IsGenericType(this Type type, Type genericType) => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == genericType; /// /// Determines whether [is generic type]. /// /// The type. /// /// true if [is generic type] [the specified type]; otherwise, false. /// public static bool IsGenericType(this Type type) { return type.GetTypeInfo().IsGenericType; } /// /// Determines whether [is concrete type]. /// /// The type. /// /// true if [is concrete type] [the specified type]; otherwise, false. /// public static bool IsConcreteType(this Type type) { return !type.IsAbstract() && !type.IsArray && type != typeof(object) && !typeof(Delegate).IsAssignableFrom(type); } /// /// Determines whether this instance is abstract. /// /// The type. /// /// true if the specified type is abstract; otherwise, false. /// public static bool IsAbstract(this Type type) { return type.GetTypeInfo().IsAbstract; } /// /// Determines whether [is generic type definition of] [the specified type to check]. /// /// The generic type definition. /// The type to check. /// /// true if [is generic type definition of] [the specified type to check]; otherwise, false. /// public static bool IsGenericTypeDefinitionOf(this Type genericTypeDefinition, Type typeToCheck) { return typeToCheck.IsGenericType() && typeToCheck.GetGenericTypeDefinition() == genericTypeDefinition; } /// /// Determines whether [is generic implementation of] [the specified type]. /// /// The type. /// Type of the service. /// /// true if [is generic implementation of] [the specified type]; otherwise, false. /// public static bool IsGenericImplementationOf(this Type type, Type serviceType) { if (type == serviceType || serviceType.IsVariantVersionOf(type)) return true; return type.IsGenericType() && serviceType.IsGenericTypeDefinition() && type.GetGenericTypeDefinition() == serviceType; } /// /// Determines whether [is variant version of] [the specified other type]. /// /// The type. /// Type of the other. /// /// true if [is variant version of] [the specified other type]; otherwise, false. /// public static bool IsVariantVersionOf(this Type type, Type otherType) { return type.IsGenericType() && otherType.IsGenericType() && type.GetGenericTypeDefinition() == otherType.GetGenericTypeDefinition() && type.IsAssignableFrom(otherType); } /// /// Determines whether [is generic type definition]. /// /// The type. /// /// true if [is generic type definition] [the specified type]; otherwise, false. /// public static bool IsGenericTypeDefinition(this Type type) { return type.GetTypeInfo().IsGenericTypeDefinition; } /// /// Bases the type. /// /// The type. /// public static Type GetBaseType(this Type type) { return type.GetTypeInfo().BaseType; } } } ================================================ FILE: src/Liquid.Core/GenAi/Entities/LiquidChatContent.cs ================================================ using Liquid.Core.GenAi.Enums; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.GenAi.Entities { /// /// The content of the chat message. /// [ExcludeFromCodeCoverage] public class LiquidChatContent { /// /// The kind of content. /// public LiquidContentKind Kind { get; set; } /// /// The text content of the message. /// public string Text { get; set; } /// /// The image content url of the message. /// public Uri ImageUri { get; set; } } } ================================================ FILE: src/Liquid.Core/GenAi/Entities/LiquidChatMessage.cs ================================================ using Liquid.Core.GenAi.Enums; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.GenAi.Entities { /// /// Context message associated with a chat completions request. /// [ExcludeFromCodeCoverage] public class LiquidChatMessage { /// /// The chat role associated with this message. /// public LiquidMessageRole Role { get; set; } /// /// Initializes a new instance of the class with the specified role. /// /// The role associated with the chat message. This value cannot be null or empty. public LiquidChatMessage(LiquidMessageRole role) { Role = role; } /// /// The contents of the message. /// public LiquidChatContent[] Content { get; set; } = Array.Empty(); /// /// Adds a text content item to the current collection of chat contents. /// /// This method appends the specified text as a new content item to the existing /// collection. If the collection is initially null, it initializes the collection with the new content /// item. /// The text content to add. Cannot be null, empty, or consist solely of whitespace. /// Thrown if is null, empty, or consists only of whitespace. public void AddContent(string text) { if (string.IsNullOrWhiteSpace(text)) { throw new ArgumentException("Text content cannot be null or empty.", nameof(text)); } var content = new LiquidChatContent { Kind = LiquidContentKind.Text, Text = text }; if (Content == null) { Content = new[] { content }; } else { var contentList = new List(Content) { content }; Content = contentList.ToArray(); } } /// /// Adds an image content to the current collection of chat contents. /// /// If the content collection is initially empty, this method initializes it with the new /// image content. Otherwise, it appends the image content to the existing collection. /// The URI of the image to be added. Cannot be . /// Thrown if is . public void AddContent(Uri imageUri) { if (imageUri == null) { throw new ArgumentNullException(nameof(imageUri), "Image URI cannot be null."); } var content = new LiquidChatContent { Kind = LiquidContentKind.Image, ImageUri = imageUri }; if (Content == null) { Content = new[] { content }; } else { var contentList = new List(Content) { content }; Content = contentList.ToArray(); } } } } ================================================ FILE: src/Liquid.Core/GenAi/Entities/LiquidChatMessages.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.GenAi.Entities { /// /// The object of context messages associated with a chat completions request /// [ExcludeFromCodeCoverage] public class LiquidChatMessages { /// /// The collection of context messages associated with a chat completions request. /// public List Messages { get; set; } = new List(); /// /// Adds a message to the collection of chat messages. /// /// The chat message to add. Cannot be . /// Thrown if is . public void AddMessage(LiquidChatMessage message) { if (message == null) { throw new System.ArgumentNullException(nameof(message), "Message cannot be null"); } Messages.Add(message); } } } ================================================ FILE: src/Liquid.Core/GenAi/Enums/LiquidContentKind.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Core.GenAi.Enums { /// /// Represents the possibles of underlying data for a chat message's Content property. /// public enum LiquidContentKind { /// /// Text content /// Text, /// /// Image content /// Image } } ================================================ FILE: src/Liquid.Core/GenAi/Enums/LiquidMessageRole.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Core.GenAi.Enums { /// /// Specifies the role of a participant in a message exchange, such as a user, assistant, or system. /// public enum LiquidMessageRole { /// /// The user role, typically representing the end user or client. /// User, /// /// The assistant role, typically representing the AI or system responding to the user. /// Assistant, /// /// The system role, typically used for system-level messages or instructions. /// System } } ================================================ FILE: src/Liquid.Core/GenAi/Interfaces/ILiquidGenAi.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.GenAi.Entities; using Liquid.Core.GenAi.Settings; using Liquid.Core.Settings; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Liquid.Core.GenAi { /// /// This service is the hub of Liquid adapter custom completions for Generative AI. /// public interface ILiquidGenAi { /// /// Get chat completions for provided content and functions. /// /// Context messages associated with chat completions request. /// A list of functions the model may generate JSON inputs for. /// The options for chat completions request. Task FunctionCalling(LiquidChatMessages messages, List functions, CompletionsOptions settings); /// /// Get chat completions for provided chat context messages. /// /// A request chat message representing an input from the user. /// A request chat message containing system instructions that influence how the model will generate a chat completions /// response. /// The options for chat completions request. /// The collection of context messages associated with this chat completions request. /// Typical usage begins with a chat message for the System role that provides instructions for /// the behavior of the assistant, followed by alternating messages between the User and /// Assistant roles. Task CompleteChatAsync(string content, string prompt, CompletionsOptions settings, LiquidChatMessages chatHistory = null); /// /// Get chat completions for provided chat context messages and functions. /// /// Messages associated with chat completions request. /// A list of functions the model may generate JSON inputs for. /// The collection of context messages associated with this chat completions request. /// The options for chat completions request. Task CompleteChatAsync(LiquidChatMessages messages, CompletionsOptions settings, List functions = null, LiquidChatMessages chatHistory = null); } } ================================================ FILE: src/Liquid.Core/GenAi/Interfaces/ILiquidGenAiHandler.cs ================================================ using System; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.GenAi.Interfaces { /// /// This service is the hub of Liquid adapter audio stream handlers for Generative AI. /// public interface ILiquidGenAiHandler { /// /// The task that manages receipt of incoming simplified protocol messages from the frontend client. /// /// The WebSocket connection to the client. /// /// /// Task HandleInputMessagesAsync(WebSocket socket, CancellationToken cancellationToken = default); /// /// The task that manages the incoming updates from realtime API messages and model responses. /// /// The WebSocket connection to the client. /// /// /// Task HandleUpdatesFromServiceAsync(WebSocket socket, CancellationToken cancellationToken = default); } } ================================================ FILE: src/Liquid.Core/GenAi/Settings/CompletionsOptions.cs ================================================ using Liquid.Core.Settings; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Core.GenAi.Settings { /// /// The options for chat completions request. /// [ExcludeFromCodeCoverage] public class CompletionsOptions { /// /// Client connection alias to use for a chat completions request. /// This connection must be configured in application previously /// public string ClientId { get; set; } /// /// The deployment name to use for a chat completions request. /// public string DeploymentName { get; set; } /// /// The deployment name to use for an embeddings request. /// public string EmbeddingModelName { get; set; } /// /// Sampling temperature to use that controls the apparent creativity of generated /// completions. /// public float Temperature { get; set; } = (float)0.7; /// /// Gets the maximum number of tokens to generate. /// public int? MaxTokens { get; set; } = null; /// /// An alternative value to , called nucleus sampling, that causes /// the model to consider the results of the tokens with probability /// mass. /// public float TopP { get; set; } = (float)0.95; /// /// Gets or sets a value that influences the probability of generated tokens appearing based on their /// cumulative frequency in generated text. /// public int FrequencyPenalty { get; set; } = 0; /// /// Gets or sets a value that influences the probability of generated tokens appearing based on their /// existing presence in generated text. /// public int PresencePenalty { get; set; } = 0; } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidBackgroundService.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Implementations { /// /// Liquid BackgroundService implementation for message consumers. /// /// Type of message body. public class LiquidBackgroundService : BackgroundService { private readonly ILiquidConsumer _consumer; private readonly IServiceProvider _serviceProvider; /// /// Initialize a new instance of /// /// /// Consumer service with message handler definition for processing messages. public LiquidBackgroundService(IServiceProvider serviceProvider, ILiquidConsumer consumer) { _serviceProvider = serviceProvider; _consumer = consumer ?? throw new ArgumentNullException(nameof(consumer)); } /// /// This method is called when the Microsoft.Extensions.Hosting.IHostedService starts. /// Its return a task that represents the lifetime of the long /// running operation(s) being performed. /// /// Triggered when Microsoft.Extensions.Hosting.IHostedService.StopAsync(System.Threading.CancellationToken) /// is called. protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _consumer.ConsumeMessageAsync += ProcessMessageAsync; _consumer.RegisterMessageHandler(stoppingToken); await Task.CompletedTask; } /// /// This method is called when message handler gets a message. /// Return a task that represents the process to be executed /// by the message handler. /// /// /// public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { using (IServiceScope scope = _serviceProvider.CreateScope()) { var worker = scope.ServiceProvider.GetRequiredService>(); await worker.ProcessMessageAsync(args, cancellationToken); } } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidCache.cs ================================================ using Liquid.Core.Extensions; using Liquid.Core.Interfaces; using Microsoft.Extensions.Caching.Distributed; using System; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Implementations { /// public class LiquidCache : ILiquidCache { private readonly IDistributedCache _distributedCache; /// /// Initialize a new instance of /// /// /// public LiquidCache(IDistributedCache distributedCache) { _distributedCache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache)); } /// public byte[] Get(string key) { return _distributedCache.Get(key); } /// public Task GetAsync(string key, CancellationToken token = default) { return _distributedCache.GetAsync(key, token); } /// public void Refresh(string key) { _distributedCache.Refresh(key); } /// public Task RefreshAsync(string key, CancellationToken token = default) { return _distributedCache.RefreshAsync(key, token); } /// public void Remove(string key) { _distributedCache.Remove(key); } /// public Task RemoveAsync(string key, CancellationToken token = default) { return _distributedCache.RemoveAsync(key, token); } /// public void Set(string key, byte[] value, DistributedCacheEntryOptions options) { _distributedCache.Set(key, value, options); } /// public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default) { return _distributedCache.SetAsync(key, value, options, token); } /// public T Get(string key) { var response = _distributedCache.Get(key); return response.ParseJson(); } /// public async Task GetAsync(string key, CancellationToken token = default) { var response = await _distributedCache.GetAsync(key, token); return response.ParseJson(); } /// public void Set(string key, T value, DistributedCacheEntryOptions options) { _distributedCache.Set(key, value.ToJsonBytes(), options); } /// public async Task SetAsync(string key, T value, DistributedCacheEntryOptions options, CancellationToken token = default) { await _distributedCache.SetAsync(key, value.ToJsonBytes(), options, token); } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidContext.cs ================================================ using Liquid.Core.Interfaces; using System.Collections.Generic; namespace Liquid.Core.Implementations { /// public class LiquidContext : ILiquidContext { private readonly IDictionary _current = new Dictionary(); /// public IDictionary current => _current; /// public void Upsert(string key, object value) { if (_current.ContainsKey(key)) { _current[key] = value; } else { _current.TryAdd(key, value); } } /// public object Get(string key) { try { return current[key]; } catch { return null; } } /// public T Get(string key) { try { return (T)current[key]; } catch { return default; } } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidContextNotifications.cs ================================================ using Liquid.Core.Interfaces; using System; using System.Collections.Generic; namespace Liquid.Core.Implementations { /// public class LiquidContextNotifications : ILiquidContextNotifications { private readonly string _notificationKey = "notification_" + Guid.NewGuid(); private readonly ILiquidContext _liquidContext; /// /// Initialize an instance of /// /// public LiquidContextNotifications(ILiquidContext liquidContext) { _liquidContext = liquidContext; } /// public void InsertNotification(string message) { var notifications = _liquidContext.Get>(_notificationKey); if (notifications is null) notifications = new List(); notifications.Add(message); _liquidContext.Upsert(_notificationKey, notifications); } /// public void InsertNotifications(IList notifications) { _liquidContext.Upsert(_notificationKey, notifications); } /// public IList GetNotifications() { try { return _liquidContext.Get>(_notificationKey); } catch { return default; } } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidJsonSerializer.cs ================================================ using Liquid.Core.Interfaces; using System.Text.Json; namespace Liquid.Core.Implementations { /// /// Implementation of Liquid Serializer to Json. /// public class LiquidJsonSerializer : ILiquidSerializer { /// /// Serializes object to json string. /// /// object that shoud be serialized. /// public string Serialize(T content) { return JsonSerializer.Serialize(content); } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidSerializerProvider.cs ================================================ using Liquid.Core.Interfaces; using System; using System.Collections.Generic; using System.Linq; namespace Liquid.Core.Implementations { /// public class LiquidSerializerProvider : ILiquidSerializerProvider { private readonly IEnumerable _serializers; /// /// Initilize a new instance of /// /// public LiquidSerializerProvider(IEnumerable serializers) { _serializers = serializers ?? throw new ArgumentNullException(nameof(serializers)); } /// public ILiquidSerializer GetSerializerByType(Type serializerType) { return _serializers.SingleOrDefault(x => x.GetType() == serializerType); } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidTelemetryInterceptor.cs ================================================ using Castle.DynamicProxy; using Liquid.Core.Base; using Microsoft.Extensions.Logging; using System; using System.Diagnostics; using System.Threading.Tasks; namespace Liquid.Core.Implementations { /// /// Telemetry interceptor implementation. /// public class LiquidTelemetryInterceptor : LiquidInterceptorBase { private readonly ILogger _logger; private readonly Stopwatch _stopwatch; /// /// Initialize an instance of /// /// public LiquidTelemetryInterceptor(ILogger logger) { _logger = logger; _stopwatch = new Stopwatch(); } /// /// Generates log information from the end of method execution with metrics. /// /// Type of results object. /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// Result object. protected override Task AfterInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, TResult result) { _stopwatch.Stop(); var elapsedTime = _stopwatch.Elapsed; _logger.LogInformation("Execution of {methodName} from {typeFullName} has ended in {milliseconds}ms.", invocation.Method.Name, invocation.TargetType.FullName, elapsedTime.TotalMilliseconds); return Task.CompletedTask; } /// /// Generates log information from the start of method execution with metrics. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. protected override Task BeforeInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo) { _stopwatch.Start(); _logger.LogInformation("Starting execution of {methodName} from {typeFullName}.", invocation.Method.Name, invocation.TargetType.FullName); return Task.CompletedTask; } /// /// Generates an error log of the exception thrown by the method. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// The object. protected override Task OnExceptionInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, Exception exception) { _logger.LogError(exception, "Execution of {methodName} from {typeFullName} has thrown an exception.", invocation.Method.Name, invocation.TargetType.FullName); return Task.CompletedTask; } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidUnitOfWork.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Liquid.Core.Implementations { /// /// Controls transactions in all data contexts /// /// public class LiquidUnitOfWork : ILiquidUnitOfWork { private bool _disposed = false; private readonly List _datacontexts = new List(); private readonly IServiceProvider _serviceProvider; private bool _transactionStarted; /// /// Initializes a new instance of the class. /// /// The service provider. /// serviceProvider public LiquidUnitOfWork(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// /// Gets the repository from Service Provider and adds to UnitOfWork. /// /// The type of the repository. /// The type of the entity. /// The type of the identifier. /// public TRepository GetRepository() where TRepository : ILiquidRepository where TEntity : LiquidEntity { var repository = _serviceProvider.GetService(); if (!_datacontexts.Any(dataContext => dataContext.Id == repository.DataContext.Id)) { _datacontexts.Add(repository.DataContext); } return repository; } /// /// Starts the transaction of all data contexts in repositories inside UnitOfWork. /// /// public async Task StartTransactionAsync() { if (!_datacontexts.Any()) throw new UnitofWorkTransactionWithoutRepositoryException(); if (!_transactionStarted) { foreach (var datacontext in _datacontexts) { await datacontext.StartTransactionAsync(); } } _transactionStarted = true; } /// /// Commits all commands added to the database context. /// /// public async Task CommitAsync() { if (!_transactionStarted) throw new UnitOfWorkTransactionNotStartedException(); foreach (var datacontext in _datacontexts) { await datacontext.CommitAsync(); } _transactionStarted = false; _datacontexts.Clear(); } /// /// Rollbacks the transactions. /// /// public async Task RollbackTransactionAsync() { if (!_transactionStarted) throw new UnitOfWorkTransactionNotStartedException(); foreach (var datacontext in _datacontexts) { await datacontext.RollbackTransactionAsync(); } _transactionStarted = false; _datacontexts.Clear(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the allocated resources for all contexts /// in this unit of work. /// /// Indicates if method should perform dispose. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { foreach (var context in _datacontexts) { context.Dispose(); } } _disposed = true; } } } ================================================ FILE: src/Liquid.Core/Implementations/LiquidXmlSerializer.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using System; using System.IO; using System.Xml; using System.Xml.Serialization; namespace Liquid.Core.Implementations { /// /// Implementation of Liquid Serializer to XML. /// public class LiquidXmlSerializer : ILiquidSerializer { /// /// Serializes object to xml string. /// /// object that shoud be serialized. public string Serialize(T content) { try { var serializer = new XmlSerializer(content.GetType()); using var stringWriter = new StringWriter(); using XmlWriter writer = XmlWriter.Create(stringWriter); serializer.Serialize(writer, content); return stringWriter.ToString(); } catch (Exception ex) { throw new SerializerFailException(nameof(content), ex); } } } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidCache.cs ================================================ using Microsoft.Extensions.Caching.Distributed; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Represents a distributed cache of serialized values. /// public interface ILiquidCache : IDistributedCache { /// /// Gets a value with the given key. /// /// A string identifying the requested value. /// /// The located value or null. T Get(string key); /// /// Gets a value with the given key. /// /// A string identifying the requested value. /// Optional. The System.Threading.CancellationToken used to propagate notifications /// that the operation should be canceled. /// The System.Threading.Tasks.Task that represents the asynchronous operation, containing /// the located value or null. Task GetAsync(string key, CancellationToken token = default); /// /// Sets a value with the given key. /// /// A string identifying the requested value. /// The value to set in the cache. /// The cache options for the value. void Set(string key, T value, DistributedCacheEntryOptions options); /// /// Sets the value with the given key. /// /// A string identifying the requested value. /// The value to set in the cache. /// The cache options for the value. /// Optional. The System.Threading.CancellationToken used to propagate notifications /// that the operation should be canceled. /// The System.Threading.Tasks.Task that represents the asynchronous operation. Task SetAsync(string key, T value, DistributedCacheEntryOptions options, CancellationToken token = default); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidConsumer.cs ================================================ using Liquid.Core.Entities; using System; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Handles message consuming process. /// /// Type of message body. public interface ILiquidConsumer { /// /// Initialize handler for consume messages from topic or queue. /// Task RegisterMessageHandler(CancellationToken cancellationToken = default); /// /// Defining the message processing function. /// event Func, CancellationToken, Task> ConsumeMessageAsync; /// /// Definition of the error handling process. /// event Func ProcessErrorAsync; } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidContext.cs ================================================ using System.Collections.Generic; namespace Liquid.Core.Interfaces { /// /// Global context service. /// public interface ILiquidContext { /// /// Context itens. /// IDictionary current { get; } /// /// Return context item value. /// /// key of the item to be obtained. /// object Get(string key); /// /// Return context item value. /// /// key of the item to be obtained. /// Type of return object. T Get(string key); /// /// Insert or update itens in context. /// /// Key of the item. /// Value of the item. void Upsert(string key, object value); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidContextNotifications.cs ================================================ using System.Collections.Generic; namespace Liquid.Core.Interfaces { /// /// Manages notifications in the global context. /// public interface ILiquidContextNotifications { /// /// Add or update a notification message. /// /// Message text. void InsertNotification(string message); /// /// Add or update notifications list on context. /// /// Notification messages list. void InsertNotifications(IList notifications); /// /// Gets notification messages that exist in the global context. /// IList GetNotifications(); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidDataContext.cs ================================================ using System; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Represents the database context /// public interface ILiquidDataContext : IDisposable { /// /// Gets the identifier of data context. /// /// /// The identifier. /// string Id { get; } /// /// Starts the transaction of all data contexts in repositories inside UnitOfWork. /// /// Task StartTransactionAsync(); /// /// Commits all commands added to the database context. /// /// Task CommitAsync(); /// /// Rollbacks the transactions. /// /// Task RollbackTransactionAsync(); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidMapper.cs ================================================ using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Defines object that map data between two instance types. /// /// type of data source object. /// results object type. public interface ILiquidMapper { /// /// Create a new instance of /// with values obtained from . /// /// data source object instance. /// optional entity name. Task Map(TFrom dataObject, string entityName = null); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidOcr.cs ================================================ using Liquid.Core.Entities; using System.IO; using System.Threading.Tasks; namespace Liquid.Ai.Core.Interfaces { /// /// Service to analyze information from documents and images and extract it into structured data. /// It provides the ability to use prebuilt models to analyze receipts, /// business cards, invoices, to extract document content, and more. /// public interface ILiquidRecognition { /// /// Analyzes pages from one or more documents, using a model built with custom documents or one of the prebuilt /// models provided by the Form Recognizer service. /// /// The stream containing one or more documents to analyze. /// /// The ID of the model to use for analyzing the input documents. When using a custom built model /// for analysis, this parameter must be the ID attributed to the model during its creation. When /// using one of the service's prebuilt models, one of the supported prebuilt model IDs must be passed. /// /// The ID of the client configuration to use for analyzing the input documents. /// When using a default instace of client, this parameter don't need to be passed, but it's default value /// must be configured on application settings. Task AnalyzeDocumentAsync(Stream doc, string clientId = "default" , string? modelId = "prebuilt-layout"); /// /// Analyzes pages from one or more documents, using a model built with custom documents or one of the prebuilt /// models provided by the Form Recognizer service. /// /// The absolute URI of the remote file to analyze documents from. /// /// The ID of the model to use for analyzing the input documents. When using a custom built model /// for analysis, this parameter must be the ID attributed to the model during its creation. When /// using one of the service's prebuilt models, one of the supported prebuilt model IDs must be passed. /// /// The ID of the client configuration to use for analyzing the input documents. /// When using a default instace of client, this parameter don't need to be passed, but it's default value /// must be configured on application settings. Task AnalyzeDocumentFromUriAsync(string uri, string clientId = "default", string? modelId = "prebuilt-layout"); /// /// Classifies one or more documents using a document classifier built with custom documents. /// /// The ID of the document classifier to use. /// The stream containing one or more documents to classify. /// The ID of the client configuration to use for analyzing the input documents. /// When using a default instace of client, this parameter don't need to be passed, but it's default value /// must be configured on application settings. Task ClassifyDocumenAsync(Stream doc, string clientId = "default", string? modelId = "prebuilt-layout"); /// /// Classifies one or more documents using a document classifier built with custom documents. /// /// The absolute URI of the remote file to classify documents from. /// The stream containing one or more documents to classify. /// The ID of the client configuration to use for analyzing the input documents. /// When using a default instace of client, this parameter don't need to be passed, but it's default value /// must be configured on application settings. Task ClassifyDocumenFromUriAsync(string uri, string clientId = "default", string? modelId = "prebuilt-layout"); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidProducer.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Handle send messages process. /// /// public interface ILiquidProducer { /// /// Structures and processes sending a list of of messages. /// /// Body of messages to be sent. Task SendMessagesAsync(IEnumerable messageBodies); /// /// Structures and processes sending a message with /// its headers . /// /// Body of message to be sent. /// Message header properties. Task SendMessageAsync(TEntity messageBody, IDictionary customProperties = null); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidRepository.cs ================================================ using Liquid.Core.Entities; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// This interface represents a single repository for the specific entity. /// /// The type of the entity. /// The type of the identifier. public interface ILiquidRepository where TEntity : LiquidEntity { /// /// Gets the data context associated to repository. /// /// /// The data context. /// ILiquidDataContext DataContext { get; } /// /// Adds the specified entity item in repository. /// /// The entity. Task AddAsync(TEntity entity); /// /// Removes the specified entity item from repository by informed Id. /// /// Identifier of entity that should be removed. /// Task RemoveByIdAsync(TIdentifier id); /// /// Updates the specified entity item in repository. /// /// The entity. /// Task UpdateAsync(TEntity entity); /// /// Finds the entity by identifier. /// /// The entity identifier. /// The entity Task FindByIdAsync(TIdentifier id); /// /// Finds the specified entity by the search predicate. /// /// The search predicate. /// /// List of entities. /// Task> WhereAsync(Expression> whereClause); /// /// Retrieve all entities. /// /// List of all entities. Task> FindAllAsync(); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidSerializer.cs ================================================ namespace Liquid.Core.Interfaces { /// /// Serialization /// public interface ILiquidSerializer { /// /// Serialize an object to string. /// /// object that shoud be serialized. string Serialize(T content); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidSerializerProvider.cs ================================================ using System; namespace Liquid.Core.Interfaces { /// /// Generates a new instance of Serializer Services /// public interface ILiquidSerializerProvider { /// /// Gets instance of validation service by the type entered in . /// /// Type of serializer to get. /// ILiquidSerializer GetSerializerByType(Type serializerType); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidStorage.cs ================================================ using Liquid.Core.Entities; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Definition of BlobStorage integration service. /// public interface ILiquidStorage { /// /// Upload a specific blob. /// /// Blob content. /// Blob path. /// Blob container name. /// Blob list of tags. Task UploadBlob(byte[] data, string name, string containerName, IDictionary tags = null); /// /// Remove blob by id. /// /// blob name. /// Blob container name. Task Delete(string id, string containerName); /// /// Filter blob by tags and remove them. /// /// Tags for filter. /// Blob container name. Task DeleteByTags(IDictionary tags, string containerName); /// /// Get all blobs from a container. /// /// Blob container name. /// List of . Task> GetAllBlobs(string containerName); /// /// Filter blobs by tags. /// /// Tags for filter. /// Blob container name. /// List of . Task> ReadBlobsByTags(IDictionary tags, string containerName); /// /// Dowload a specific blob. /// /// Blob Id. /// Blob container name. /// . Task ReadBlobsByName(string blobName, string containerName); /// /// generates a Blob Shared Access Signature (SAS) Uri /// based on the parameters passed. The SAS is signed by the shared key /// credential of the client. /// /// The id of the blob. /// Name of the container where the blob is stored. /// The time at which the shared access signature becomes invalid. /// This field must be omitted if it has been specified in an /// associated stored access policy. /// The permissions associated with the shared access signature. The /// user is restricted to operations allowed by the permissions. /// Blob sas uri absolute path. string GetBlobSasUri(string blobName, string containerName, DateTimeOffset expiresOn, string permissions); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidUnitOfWork.cs ================================================ using Liquid.Core.Entities; using System; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Interface responsible for Managing the repositories transactions. /// public interface ILiquidUnitOfWork : IDisposable { /// /// Gets the repository from Service Provider and adds to UnitOfWork. /// /// The type of the repository. /// The type of the entity. /// The type of the identifier. /// TRepository GetRepository() where TRepository : ILiquidRepository where TEntity : LiquidEntity; /// /// Starts the transaction of all data contexts in repositories inside UnitOfWork. /// /// Task StartTransactionAsync(); /// /// Commits all commands added to the database context. /// /// Task CommitAsync(); /// /// Rollbacks the transactions. /// /// Task RollbackTransactionAsync(); } } ================================================ FILE: src/Liquid.Core/Interfaces/ILiquidWorker.cs ================================================ using Liquid.Core.Entities; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Interfaces { /// /// Liquid Worker Service interface. /// The implementation should be message handling process. /// /// Type of message body. public interface ILiquidWorker { /// /// This method is called when message handler gets a message. /// The implementation should return a task that represents /// the process to be executed by the message handler. /// /// /// Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken); } } ================================================ FILE: src/Liquid.Core/Liquid.Core.csproj ================================================  net8.0 Liquid.Core MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.1.0 true {C33A89FC-4F4D-4274-8D0F-29456BA8F76B} true Full The Liquid Base component contains main functions to be used on all Liquid Framework components. True Always ================================================ FILE: src/Liquid.Core/Localization/Entities/LocalizationCollection.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace Liquid.Core.Localization.Entities { /// /// Resource collection class. /// [ExcludeFromCodeCoverage] internal class LocalizationCollection { /// /// Gets or sets the resources. /// /// /// The resources. /// [JsonPropertyName("items")] public IEnumerable Items { get; set; } /// /// Initializes a new instance of the class. /// public LocalizationCollection() { Items = new List(); } } } ================================================ FILE: src/Liquid.Core/Localization/Entities/LocalizationItem.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace Liquid.Core.Localization.Entities { /// /// Resource item class. /// [ExcludeFromCodeCoverage] internal class LocalizationItem { /// /// Gets or sets the key. /// /// /// The key. /// [JsonPropertyName("key")] public string Key { get; set; } /// /// Gets or sets the value. /// /// /// The value. /// [JsonPropertyName("values")] public IEnumerable Values { get; set; } /// /// Initializes a new instance of the class. /// public LocalizationItem() { Values = new List(); } } } ================================================ FILE: src/Liquid.Core/Localization/Entities/LocalizationValue.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace Liquid.Core.Localization.Entities { /// /// Resource value class. /// [ExcludeFromCodeCoverage] internal class LocalizationValue { /// /// Gets or sets the resource value. /// /// /// The object value. /// [JsonPropertyName("value")] public string Value { get; set; } /// /// Gets or sets the channels. /// /// /// The channels. /// [JsonPropertyName("channels")] public string Channels { get; set; } /// /// Gets the channels as enumerable. /// /// /// The channels list. /// [JsonIgnore] public IEnumerable ChannelsList { get { if (string.IsNullOrEmpty(Channels)) { return new List(); } return Channels.ToLower().Split(';'); } } } } ================================================ FILE: src/Liquid.Core/Localization/ILocalization.cs ================================================ using System; using System.Globalization; namespace Liquid.Core.Localization { /// /// Resource catalog interface. /// [Obsolete("This class will be removed or refactored in the next release.")] public interface ILocalization { /// /// Gets the specified string according to culture. /// /// The resource key. /// /// The string associated with resource key. /// [Obsolete("This method will be removed or refactored in the next release.")] string Get(string key); /// /// Gets the specified string according to culture. /// /// The resource key. /// The channel. /// /// The string associated with resource key. /// [Obsolete("This method will be removed or refactored in the next release.")] string Get(string key, string channel); /// /// Gets the specified string according to culture. /// /// The resource key. /// The culture. /// The channel. /// /// The string associated with resource key. /// [Obsolete("This method will be removed or refactored in the next release.")] string Get(string key, CultureInfo culture, string channel = null); } } ================================================ FILE: src/Liquid.Core/Localization/JsonFileLocalization.cs ================================================ using Liquid.Core.Extensions; using Liquid.Core.Localization.Entities; using Liquid.Core.Settings; using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Threading; namespace Liquid.Core.Localization { /// /// Resource file catalog class. /// /// [Obsolete("This class will be removed or refactored in the next release.")] [ExcludeFromCodeCoverage] public class JsonFileLocalization : ILocalization { private readonly IDictionary _localizationItems; /// /// Initializes a new instance of the class. /// /// The configuration. public JsonFileLocalization(IOptions configuration) { _localizationItems = ReadLocalizationFiles(configuration); } /// /// Gets the specified string according to culture. /// /// The resource key. /// /// The string associated with resource key. /// /// key [Obsolete("This method will be removed or refactored in the next release.")] public string Get(string key) { return Get(key, Thread.CurrentThread.CurrentCulture); } /// /// Gets the specified string according to culture. /// /// The resource key. /// The channel. /// /// The string associated with resource key. /// /// key [Obsolete("This method will be removed or refactored in the next release.")] public string Get(string key, string channel) { return Get(key, Thread.CurrentThread.CurrentCulture, channel); } /// /// Gets the specified string according to culture. /// /// The resource key. /// The culture. /// The channel. /// /// The string associated with resource key. /// /// /// key /// or /// culture /// /// [Obsolete("This method will be removed or refactored in the next release.")] public string Get(string key, CultureInfo culture, string channel = null) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key)); if (culture == null) throw new ArgumentNullException(nameof(culture)); try { if (_localizationItems.TryGetValue(culture, out var localizationItem)) { var item = localizationItem.Items.GetResourceItem(key); var itemValue = item?.GetResourceValue(channel); if (itemValue != null) { return itemValue.Value; } } return key; } catch (Exception ex) { throw new LocalizationException(key, ex); } } /// /// Reads the resource collection from file. /// /// The configuration. /// private static IDictionary ReadLocalizationFiles(IOptions configuration) { var items = new ConcurrentDictionary(); try { var path = AppDomain.CurrentDomain.BaseDirectory; var files = Directory.EnumerateFiles(path, "localization*.json", SearchOption.AllDirectories); foreach (var fileName in files) { var fileInfo = new FileInfo(fileName); var filenameParts = fileInfo.Name.Split('.'); var culture = filenameParts.Length == 2 ? configuration.Value.DefaultCulture : filenameParts[1]; using var fileReader = new StreamReader(fileName); var json = fileReader.ReadToEnd(); var localizationItems = json.ParseJson(); items.TryAdd(new CultureInfo(culture), localizationItems); } } catch (Exception exception) { throw new LocalizationReaderException(exception); } return items; } } } ================================================ FILE: src/Liquid.Core/Localization/LocalizationException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Localization { /// /// Occurs when it's not possible to read a resource. /// /// [Serializable] [ExcludeFromCodeCoverage] public class LocalizationException : LiquidException { /// /// Initializes a new instance of the class. /// /// The key. /// The inner exception. public LocalizationException(string key, Exception innerException) : base($"Unable to read resource from key: {key}, please see inner exception.", innerException) { } } } ================================================ FILE: src/Liquid.Core/Localization/LocalizationExtensions.cs ================================================ using Liquid.Core.Localization.Entities; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Liquid.Core.Localization { /// /// Resource extensions class. /// [ExcludeFromCodeCoverage] public static class LocalizationExtensions { /// /// Gets the resource item from collection. /// /// The resources. /// The key. /// internal static LocalizationItem GetResourceItem(this IEnumerable resources, string key) { var result = resources.FirstOrDefault(r => r.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); return result; } /// /// Gets the resource value. /// /// The resource item. /// The channel. /// /// cultureCode internal static LocalizationValue GetResourceValue(this LocalizationItem resourceItem, string channel = null) { var values = resourceItem.Values; if (channel == null) { return values.FirstOrDefault(); } var resourceValues = values as LocalizationValue[] ?? values.ToArray(); var returnValue = resourceValues.FirstOrDefault(v => v.ChannelsList.Contains(channel.ToLower())); return returnValue ?? resourceValues.FirstOrDefault(); } /// /// Adds the localization using the default filename localization.json /// /// The services. [Obsolete("This method will be removed or refactored in the next release.")] public static void AddLocalizationService(this IServiceCollection services) { services.AddSingleton(); } } } ================================================ FILE: src/Liquid.Core/Localization/LocalizationReaderException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Core.Localization { /// /// Occurs when it's not possible to read the resources collection from data source. /// /// [Serializable] [ExcludeFromCodeCoverage] public class LocalizationReaderException : LiquidException { /// /// Initializes a new instance of the class. /// /// The inner exception. public LocalizationReaderException(Exception innerException) : base("An error occurred while reading the resource collection from datasource.", innerException) { } } } ================================================ FILE: src/Liquid.Core/PipelineBehaviors/LiquidTelemetryBehavior.cs ================================================ using MediatR; using Microsoft.Extensions.Logging; using System; using System.Diagnostics; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.PipelineBehaviors { /// /// Telemetry Behavior implementation for Mediator pipelines. /// /// /// public class LiquidTelemetryBehavior : IPipelineBehavior where TRequest : IRequest { private readonly ILogger> _logger; private readonly Stopwatch _stopwatch; /// /// Initialize an instance of /// /// Logging object. public LiquidTelemetryBehavior(ILogger> logger) { _logger = logger; _stopwatch = new Stopwatch(); } /// /// Pipeline handler. Generates telemetry logs before, after and on exception case during request execution. /// /// Incoming request. /// Cancellation token. /// Awaitable delegate for the next action in the pipeline. Eventually this delegate /// represents the handler. public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { var methodInfo = next.GetMethodInfo(); TResponse response = default; try { await BeforeRequest(methodInfo); response = await next(); } catch (Exception ex) { await OnExceptionResponse(methodInfo, ex); throw; } finally { await AfterResponse(methodInfo); } return response; } private Task AfterResponse(MethodInfo methodInfo) { _stopwatch.Stop(); var elapsedTime = _stopwatch.Elapsed; _logger.LogInformation("Execution of {methodName} from {typeFullName} has ended in {milliseconds}ms." , methodInfo.Name, nameof(methodInfo.ReflectedType), elapsedTime.TotalMilliseconds); return Task.CompletedTask; } private Task OnExceptionResponse(MethodInfo methodInfo, Exception exception) { _logger.LogError(exception, "Execution of {methodName} from {typeFullName} has thrown an exception.", methodInfo.Name, nameof(methodInfo.ReflectedType)); return Task.CompletedTask; } private Task BeforeRequest(MethodInfo methodInfo) { _stopwatch.Start(); _logger.LogInformation("Starting execution of {methodName} from {typeFullName}.", methodInfo.Name, methodInfo.ReflectedType); return Task.CompletedTask; } } } ================================================ FILE: src/Liquid.Core/PipelineBehaviors/LiquidValidationBehavior.cs ================================================ using FluentValidation; using MediatR; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.PipelineBehaviors { /// /// Validation Request Behavior implementation for Mediator pipelines. /// /// /// public class LiquidValidationBehavior : IPipelineBehavior where TRequest : IRequest { private readonly IEnumerable> _validators; /// /// Initialize an instance of /// /// Validator for a type. public LiquidValidationBehavior(IEnumerable> validators) { _validators = validators; } /// /// Pipeline handler. Perform validation and await the next delegate. /// /// Incoming request. /// Cancellation token. /// Awaitable delegate for the next action in the pipeline. Eventually this delegate /// represents the handler. public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { if (_validators.Any()) { var context = new ValidationContext(request); var results = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); var errors = results.SelectMany(r => r.Errors).Where(f => f != null).ToList(); if (errors.Count != 0) throw new ValidationException(errors); } return await next(); } } } ================================================ FILE: src/Liquid.Core/Settings/CultureSettings.cs ================================================ using Liquid.Core.Attributes; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Represents the Culture Configuration inside appsettings.json. /// [LiquidSectionName("liquid:culture")] [ExcludeFromCodeCoverage] public class CultureSettings { /// /// Gets the default culture. /// /// /// The default culture. /// public string DefaultCulture { get; set; } } } ================================================ FILE: src/Liquid.Core/Settings/DatabaseSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Database configuration properties. /// [ExcludeFromCodeCoverage] public class DatabaseSettings { /// /// Gets or sets the database connection string. /// /// /// The database connection string. /// public string ConnectionString { get; set; } /// /// Gets or sets the name of the database. /// /// /// The name of the database. /// public string DatabaseName { get; set; } } } ================================================ FILE: src/Liquid.Core/Settings/GenAiOptions.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Core.Settings { /// /// The options for chat completions request. /// [ExcludeFromCodeCoverage] public class GenAiOptions { public List Settings { get; set; } } /// /// The settings for chat completions request. /// [ExcludeFromCodeCoverage] public class GenAiSettings { /// /// Client connection alias. /// public string ClientId { get; set; } /// /// The URI for an GenAI resource as retrieved from, for example, Azure Portal. ///This should include protocol and hostname. /// public string Url { get; set; } /// /// Key to use to authenticate with the service. /// public string Key { get; set; } /// /// The maximum number of retries to allow. /// public int MaxRetries { get; set; } = 0; /// /// the maximum delay in milliseconds between retries. /// public int RetryMinDelay { get; set; } = 1000; /// /// the maximum delay in milliseconds between retries. /// public int RetryMaxDelay { get; set; } = 10000; /// /// if set to true, the delay between retries will grow exponentially, /// limited by the values of and . /// Otherwise, the delay will be fixed by the value of . /// public bool ExponentialBackoff { get; set; } = false; } } ================================================ FILE: src/Liquid.Core/Settings/OcrOptions.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Set of OCR service settings. /// [ExcludeFromCodeCoverage] public class OcrOptions { /// /// Collection of OCR service connection settings. /// public List Settings { get; set; } } /// /// OCR service connection settings. /// [ExcludeFromCodeCoverage] public class OcrSettings { /// /// OCR service connection alias. /// public string ClientId { get; set; } /// /// OCR service absolute Uri. /// public string Url { get; set; } /// /// OCR service access key. /// public string Key { get; set; } } } ================================================ FILE: src/Liquid.Core/Settings/ScopedContextSettings.cs ================================================ using Liquid.Core.Attributes; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Context key settings. /// [ExcludeFromCodeCoverage] [LiquidSectionName("Liquid:ScopedContext")] public class ScopedContextSettings { /// /// List of keys that should be created on context. /// public List Keys { get; set; } = new List(); /// /// Indicates if the current culture must be included on context keys. /// public bool Culture { get; set; } = false; } } ================================================ FILE: src/Liquid.Core/Settings/ScopedKey.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Definition of scoped key type. /// [ExcludeFromCodeCoverage] public class ScopedKey { /// /// Name of the scoped key. /// public string KeyName { get; set; } /// /// Indicates if this key is mandatory. /// public bool Required { get; set; } = false; } } ================================================ FILE: src/Liquid.Core/Settings/ScopedLoggingSettings.cs ================================================ using Liquid.Core.Attributes; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Settings { /// /// Scoped logging setting properties. /// [ExcludeFromCodeCoverage] [LiquidSectionName("Liquid:ScopedLogging")] public class ScopedLoggingSettings { /// /// List of keys that should be created on logger scope. /// public List Keys { get; set; } = new List(); } } ================================================ FILE: src/Liquid.Core/Utils/TypeUtils.cs ================================================ using Liquid.Core.Extensions; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace Liquid.Core.Utils { /// /// Type Helper Extensions Class. /// [ExcludeFromCodeCoverage] public static class TypeUtils { /// /// Gets the types to register. /// /// Type of the service. /// The assemblies. /// public static Type[] GetTypesToRegister(Type serviceType, IEnumerable assemblies) { var typesToRegister = assemblies .Distinct() .Where(assembly => !assembly.IsDynamic) .SelectMany(assembly => assembly.GetTypes()) .Where(type => type.IsConcreteType() && !type.GetTypeInfo().IsGenericTypeDefinition && ServiceIsAssignableFromImplementation(serviceType, type)); return typesToRegister.ToArray(); } /// /// Services the is assignable from implementation. /// /// The service. /// The implementation. /// private static bool ServiceIsAssignableFromImplementation(Type service, Type implementation) { if (service.IsAssignableFrom(implementation) || service.IsGenericTypeDefinitionOf(implementation)) return true; if (implementation.GetInterfaces().Any(type => type.IsGenericImplementationOf(service))) return true; var type1 = implementation.GetBaseType() ?? (implementation != typeof(object) ? typeof(object) : null); for (var type2 = type1; type2 != null as Type; type2 = type2.GetBaseType()) { if (type2.IsGenericImplementationOf(service)) return true; } return false; } /// /// Gets the field information. /// /// The type. /// Name of the field. /// public static FieldInfo GetFieldInfo(Type type, string fieldName) { FieldInfo fieldInfo; do { fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } while (fieldInfo == null && type != null); return fieldInfo; } /// /// Gets the property information. /// /// The type. /// Name of the property. /// public static PropertyInfo GetPropertyInfo(Type type, string propertyName) { PropertyInfo propertyInfo; do { propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } while (propertyInfo == null && type != null); return propertyInfo; } } } ================================================ FILE: src/Liquid.Core/Utils/ZipUtils.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Text; namespace Liquid.Core.Utils { /// /// Compression and Decompression library. /// [ExcludeFromCodeCoverage] public static class ZipUtils { #region Gzip /// /// Compress a string using Gzip format and UTF8 encoding. /// /// The input string. /// The compressed string. public static byte[] GzipCompress(this string inputString) { return inputString.GzipCompress(Encoding.UTF8); } /// /// Compress a string using Gzip format. /// /// The input string. /// The encoding. /// /// The compressed string. /// public static byte[] GzipCompress(this string inputString, Encoding encoding) { var inputBytes = encoding.GetBytes(inputString); byte[] output; using (var outputStream = new MemoryStream()) { using (var gZipStream = new GZipStream(outputStream, CompressionMode.Compress)) { gZipStream.Write(inputBytes, 0, inputBytes.Length); } output = outputStream.ToArray(); } return output; } /// /// Decompress a compressed string using Gzip format and UTF8 encoding. /// /// The compressed bytes. /// The decompressed string. public static string GzipDecompress(this byte[] inputBytes) { return inputBytes.GzipDecompress(Encoding.UTF8); } /// /// Decompress a compressed string using Gzip format. /// /// The compressed bytes. /// The encoding. /// /// The decompressed string. /// public static string GzipDecompress(this byte[] inputBytes, Encoding encoding) { string decompressed; using (var inputStream = new MemoryStream(inputBytes)) using (var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress)) using (var outputStream = new MemoryStream()) { gZipStream.CopyTo(outputStream); var outputBytes = outputStream.ToArray(); decompressed = encoding.GetString(outputBytes); } return decompressed; } #endregion #region Deflate /// /// Compress a string using Deflate format. /// /// The input string. /// The compressed string. public static byte[] DeflateCompress(this string inputString) { return inputString.DeflateCompress(Encoding.UTF8); } /// /// Compress a string using Deflate format. /// /// The input string. /// The encoding. /// /// The compressed string. /// public static byte[] DeflateCompress(this string inputString, Encoding encoding) { var inputBytes = encoding.GetBytes(inputString); byte[] output; using (var outputStream = new MemoryStream()) { using (var deflateStream = new DeflateStream(outputStream, CompressionMode.Compress)) { deflateStream.Write(inputBytes, 0, inputBytes.Length); } output = outputStream.ToArray(); } return output; } /// /// Decompress a compressed string using Deflate format. /// /// The compressed bytes. /// The decompressed string. public static string DeflateDecompress(this byte[] inputBytes) { return inputBytes.DeflateDecompress(Encoding.UTF8); } /// /// Decompress a compressed string using Deflate format. /// /// The compressed bytes. /// The encoding. /// /// The decompressed string. /// public static string DeflateDecompress(this byte[] inputBytes, Encoding encoding) { string decompressed; using (var inputStream = new MemoryStream(inputBytes)) using (var deflateStream = new DeflateStream(inputStream, CompressionMode.Decompress)) using (var outputStream = new MemoryStream()) { deflateStream.CopyTo(outputStream); var outputBytes = outputStream.ToArray(); decompressed = encoding.GetString(outputBytes); } return decompressed; } #endregion } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Extensions/DependencyInjection/IApplicationBuilderExtensions.cs ================================================ using System.Diagnostics.CodeAnalysis; using Elastic.Apm.NetCoreAll; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; namespace Liquid.Core.Telemetry.ElasticApm.Extensions.DependencyInjection { /// /// Extends interface. /// public static class IApplicationBuilderExtensions { /// /// Adds to the application builder. /// /// Extended application builder. /// implementation. public static IApplicationBuilder UseLiquidElasticApm(this IApplicationBuilder builder, IConfiguration configuration) { if (configuration.HasElasticApmEnabled()) { builder.UseAllElasticApm(configuration); } return builder; } } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Extensions/DependencyInjection/IServiceCollectionExtensions.cs ================================================ using Elastic.Apm; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Telemetry.ElasticApm.Implementations; using Liquid.Core.Telemetry.ElasticApm.MediatR; using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Liquid.Core.Telemetry.ElasticApm.Extensions.DependencyInjection { /// /// Extends interface. /// public static class IServiceCollectionExtensions { /// /// Register telemetry interceptor and behaviour for Elastic APM. /// /// Extended instance. /// implementation. public static IServiceCollection AddLiquidElasticApmTelemetry(this IServiceCollection services, IConfiguration configuration) { if (configuration.HasElasticApmEnabled()) { services.AddSingleton(s => Agent.Tracer); services.AddInterceptor(); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LiquidElasticApmTelemetryBehavior<,>)); } return services; } } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Extensions/IConfigurationExtension.cs ================================================ using System; using Microsoft.Extensions.Configuration; namespace Liquid.Core.Telemetry.ElasticApm.Extensions { /// /// Extends interface. /// internal static class IConfigurationExtension { /// /// Checks if Elastic APM is enabled. /// /// Extended instance. /// True if Elastic APM is enabled, otherwise False. internal static bool HasElasticApmEnabled(this IConfiguration config) { var apmConfigured = config.GetSection("ElasticApm:ServerUrl").Value != null; if (apmConfigured) { var isEnabled = false; var apmEnvironment = (Environment.GetEnvironmentVariable("ELASTIC_APM_ENABLED") != null && bool.TryParse(Environment.GetEnvironmentVariable("ELASTIC_APM_ENABLED"), out isEnabled)); if (!apmEnvironment) { return bool.TryParse(config["ElasticApm:Enabled"], out isEnabled) ? isEnabled : apmConfigured; } return isEnabled; } return false; } } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Implementations/LiquidElasticApmInterceptor.cs ================================================ using System; using System.Threading.Tasks; using Castle.DynamicProxy; using Elastic.Apm.Api; using Liquid.Core.Base; using Microsoft.Extensions.Logging; namespace Liquid.Core.Telemetry.ElasticApm.Implementations { /// /// Implement interceptors for Elastic APM with actions after, before and on exception. /// public sealed class LiquidElasticApmInterceptor : LiquidInterceptorBase { private readonly ILogger _logger; private readonly ITracer _tracer; private ISpan span; /// /// Initialize an instance of /// /// implementation. /// Elastic APM implementation. public LiquidElasticApmInterceptor(ILogger logger, ITracer tracer) { _logger = logger; _tracer = tracer; } /// /// Generates log information from the end of method execution with metrics. /// /// Type of results object. /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// Result object. protected override Task AfterInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, TResult result) { span?.End(); return Task.CompletedTask; } /// /// Generates log information from the start of method execution with metrics. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. protected override Task BeforeInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo) { span = _tracer?.CurrentTransaction?.StartSpan(invocation.TargetType.Name, invocation.Method.Name); return Task.CompletedTask; } /// /// Generates an error log of the exception thrown by the method. /// /// The method invocation. /// The Castle.DynamicProxy.IInvocationProceedInfo. /// The object. protected override Task OnExceptionInvocation(IInvocation invocation, IInvocationProceedInfo proceedInfo, Exception exception) { span?.End(); _logger?.LogError(exception, $"Execution of {invocation.Method.Name} from {invocation.TargetType.Name} has thrown an exception."); return Task.CompletedTask; } } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Implementations/LiquidElasticApmTelemetryBehavior.cs ================================================ using System; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Elastic.Apm.Api; using MediatR; using Microsoft.Extensions.Logging; namespace Liquid.Core.Telemetry.ElasticApm.MediatR { /// /// Implements for Elastic APM. /// /// The type of request. /// Type of response object obtained upon return of request. public sealed class LiquidElasticApmTelemetryBehavior : IPipelineBehavior where TRequest : IRequest { private readonly ILogger> _logger; private readonly ITransaction _transaction; /// /// Initialize an instance of /// /// implementation where TCategoryName is a instance. /// Elastic APM implementation. public LiquidElasticApmTelemetryBehavior(ILogger> logger, ITracer tracer) { _logger = logger; _transaction = tracer?.CurrentTransaction; } /// /// Handles MediatR pipeline operation. /// /// The request command or query. /// Notification about operation to be cancelled. /// Mext operation to be performed. /// public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { var methodInfo = next.GetMethodInfo(); TResponse response = default; var span = _transaction?.StartSpan(methodInfo.Name, methodInfo.ReflectedType.Name); try { await BeforeRequest(methodInfo); response = await next(); } catch (Exception ex) { await OnExceptionResponse(methodInfo, ex); throw; } finally { await AfterResponse(methodInfo); span?.End(); } return response; } private Task AfterResponse(MethodInfo methodInfo) { _logger?.LogInformation($"Execution of {methodInfo.Name} from {methodInfo.ReflectedType.Name} has ended."); return Task.CompletedTask; } private Task OnExceptionResponse(MethodInfo methodInfo, Exception exception) { _logger?.LogError(exception, $"Execution of {methodInfo.Name} from {methodInfo.ReflectedType.Name} has thrown an exception."); return Task.CompletedTask; } private Task BeforeRequest(MethodInfo methodInfo) { _logger?.LogInformation($"Starting execution of {methodInfo.Name} from {methodInfo.ReflectedType.Name}."); return Task.CompletedTask; } } } ================================================ FILE: src/Liquid.Core.Telemetry.ElasticApm/Liquid.Core.Telemetry.ElasticApm.csproj ================================================  net8.0 Liquid.Core.Telemetry.ElasticApm MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2021 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true {AD354CF6-C132-4B5D-944D-0DFE21509781} true Full This is the Liquid component to collect telemetry and send it to ElasticAPM. True ================================================ FILE: src/Liquid.Dataverse/DataverseClientFactory.cs ================================================ using Microsoft.Extensions.Options; using Microsoft.PowerPlatform.Dataverse.Client; using System.Diagnostics.CodeAnalysis; namespace Liquid.Dataverse { /// public class DataverseClientFactory : IDataverseClientFactory { private readonly IOptions _options; /// /// Initialize a new instance of /// /// Configuration settigs. /// public DataverseClientFactory(IOptions options) { ArgumentNullException.ThrowIfNull(options); _options = options; } /// [ExcludeFromCodeCoverage] public IOrganizationServiceAsync GetClient() { var settings = _options.Value; var connectionString = string.Format("AuthType=ClientSecret;url={0};ClientId={1};ClientSecret={2};", settings.Url, settings.ClientId, settings.ClientSecret); var service = new ServiceClient(connectionString); return service; } } } ================================================ FILE: src/Liquid.Dataverse/DataverseEntityMapper.cs ================================================ using Liquid.Core.AbstractMappers; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Metadata; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Diagnostics.CodeAnalysis; namespace Liquid.Dataverse { /// /// Implementation of that /// maps json string data to a new instance of . /// [ExcludeFromCodeCoverage] public class DataverseEntityMapper : LiquidMapper { private readonly ILiquidDataverse _dataverseAdapter; private Dictionary _entitiesMetadata = new Dictionary(); /// /// Initialize a new instance of /// /// /// public DataverseEntityMapper(ILiquidDataverse adapter) : base(nameof(DataverseEntityMapper)) { _dataverseAdapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); } /// protected override async Task MapImpl(string jsonString, string? entityName = null) { ArgumentNullException.ThrowIfNull(entityName); var entityAttributes = await GetEntityAttributes(entityName); if (entityAttributes == null) throw new ArgumentNullException(nameof(entityAttributes), $"Entity {entityName} not found."); var entity = JsonToEntity(entityAttributes, jsonString); return entity; } private async Task?> GetEntityAttributes(string entityName, List? noMappingFields = null) { var entityMetadata = _entitiesMetadata.FirstOrDefault(x => x.Key == entityName).Value; if (entityMetadata == null) { entityMetadata = await _dataverseAdapter.GetMetadata(entityName); _entitiesMetadata.TryAdd(entityName, entityMetadata); } var attributes = entityMetadata.Attributes?.ToList(); if (attributes != null) { attributes = attributes.Where(p => p.IsLogical != null).ToList(); } if (noMappingFields != null) { foreach (var noMappingField in noMappingFields) { attributes = attributes?.Where(p => p.LogicalName != noMappingField)?.ToList(); } } var listAttributes = attributes?.ToList(); return listAttributes; } private static Entity JsonToEntity(List attributes, string values) { var entidade = new Entity(); var valuesObject = JsonConvert.DeserializeObject(values); if (valuesObject == null) return entidade; foreach (var atrribute in attributes) { var logicalName = atrribute.LogicalName.ToUpper(); if (valuesObject[logicalName] == null) continue; if (valuesObject[logicalName].ToString() != "") { switch (atrribute.AttributeType.ToString()) { case "String": case "Memo": entidade[atrribute.LogicalName] = (string)valuesObject[logicalName]; break; case "Virtual": var options = valuesObject[logicalName].ToList(); OptionSetValueCollection collectionOptionSetValues = new OptionSetValueCollection(); foreach (var option in options) { collectionOptionSetValues.Add(new OptionSetValue(int.Parse(option["Value"].ToString()))); } entidade[atrribute.LogicalName] = collectionOptionSetValues; break; case "Integer": entidade[atrribute.LogicalName] = (int)valuesObject[logicalName]; break; case "Decimal": entidade[atrribute.LogicalName] = (decimal)valuesObject[logicalName]; break; case "Boolean": entidade[atrribute.LogicalName] = (bool)valuesObject[logicalName]; break; case "Picklist": entidade[atrribute.LogicalName] = new OptionSetValue((int)valuesObject[logicalName]); break; case "DateTime": entidade[atrribute.LogicalName] = Convert.ToDateTime((string)valuesObject[logicalName]); break; case "Money": if ((int)valuesObject[logicalName] > 0) entidade[atrribute.LogicalName] = new Money((int)valuesObject[logicalName]); break; case "Double": entidade[atrribute.LogicalName] = (double)valuesObject[logicalName]; break; case "Lookup": case "Customer": case "Owner": entidade[atrribute.LogicalName] = new EntityReference(valuesObject[logicalName]["LogicalName"].ToString() , new Guid(valuesObject[logicalName]["Id"].ToString())); break; } } } return entidade; } } } ================================================ FILE: src/Liquid.Dataverse/DataverseSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Dataverse { /// /// Set of dataverse connection configs. /// [ExcludeFromCodeCoverage] public class DataverseSettings { /// /// User Id to use. /// public string ClientId { get; set; } /// /// User secret to use. /// public string ClientSecret { get; set; } /// /// Dataverse Instance to connect too. /// public string Url { get; set; } } } ================================================ FILE: src/Liquid.Dataverse/Extensions/DependencyInjection/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Xrm.Sdk; using System.Diagnostics.CodeAnalysis; namespace Liquid.Dataverse.Extensions.DependencyInjection { /// /// Extension methods of /// [ExcludeFromCodeCoverage] public static class IServiceCollectionExtensions { /// /// Registers service, it's dependency /// , and also set configuration /// option . /// Also register service. /// /// /// configuration section of dataverse settings. public static IServiceCollection AddLiquidDataverseAdapter(this IServiceCollection services, string dataverseSection) { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(dataverseSection).Bind(settings); }); services.AddTransient(); services.AddSingleton(); services.AddSingleton, DataverseEntityMapper>(); return services; } } } ================================================ FILE: src/Liquid.Dataverse/Extensions/EntityExtensions.cs ================================================ using Microsoft.Xrm.Sdk; using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace Liquid.Dataverse.Extensions { /// /// Extension methods of . /// [ExcludeFromCodeCoverage] public static class EntityExtensions { /// /// Converte os /// para JsonString. /// /// Entidade origem. /// public static string ToJsonString(this Entity entity) { var resultSet = new Dictionary(); foreach (var attribute in entity.Attributes) { resultSet.Add(attribute.Key, attribute.Value); } return JsonSerializer.Serialize(resultSet); } } } ================================================ FILE: src/Liquid.Dataverse/IDataverseClientFactory.cs ================================================ using Microsoft.PowerPlatform.Dataverse.Client; namespace Liquid.Dataverse { /// /// Defines Dataverse provider. /// public interface IDataverseClientFactory { /// /// Initialize a new instance of /// connected too . /// IOrganizationServiceAsync GetClient(); } } ================================================ FILE: src/Liquid.Dataverse/ILiquidDataverse.cs ================================================ using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Query; namespace Liquid.Dataverse { /// /// Dataverse integration service definition. /// public interface ILiquidDataverse { /// /// Insert an using additional parameters to optionally prevent custom synchronous logic execution, suppress Power Automate trigger and enforce duplicate detection rules evaluation. /// /// entity definition. /// /// /// /// created entity Id. Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); /// /// Update an record using parameters to enforce optimistic concurrency, prevent custom synchronous logic execution, suppress Power Automate trigger, and enforce duplicate detection rules evaluation. /// /// entity definition. /// /// /// /// Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true); /// /// Read by . /// /// primarykey value. /// table name. /// column set should return. Task GetById(Guid id, string entityName, ColumnSet? columns = null); /// /// Read table according filter conditions. /// /// table name. /// query conditions. /// conlumn se should return. Task> ListByFilter(string entityName, FilterExpression filter, ColumnSet? columns = null); /// /// Exclude an item from table by primarykey optionally using parameters to prevent custom synchronous logic execution and enforce optimistic concurrency. /// /// primarykey value /// table name. /// /// Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false); /// /// Read table according query conditions. /// /// table name. /// query conditions Task> ListByFilter(string entityName, QueryExpression query); /// /// Read table properties. /// /// table name. /// set. Task GetMetadata(string entityName); /// /// Update state and status from an . /// /// entity reference. /// new state value. /// new status value. Task SetState(EntityReference entity, string state, string status); /// /// Insert or update an . /// /// entity definition. Task Upsert(Entity entity); } } ================================================ FILE: src/Liquid.Dataverse/Liquid.Dataverse.csproj ================================================  net8.0 enable enable Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 8.0.0 true true Adapter for Microsoft Dataverse integrations. This component is part of Liquid Application Framework. logo.png https://github.com/Avanade/Liquid-Application-Framework True \ ================================================ FILE: src/Liquid.Dataverse/LiquidDataverse.cs ================================================ using Microsoft.Crm.Sdk.Messages; using Microsoft.PowerPlatform.Dataverse.Client; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Query; namespace Liquid.Dataverse { /// public class LiquidDataverse : ILiquidDataverse { private readonly IDataverseClientFactory _serviceFactory; private readonly IOrganizationServiceAsync _client; /// /// Initialize a new instance of /// /// /// public LiquidDataverse(IDataverseClientFactory serviceFactory) { _serviceFactory = serviceFactory ?? throw new ArgumentNullException(nameof(serviceFactory)); _client = _serviceFactory.GetClient(); } public async Task GetById(Guid id, string entityName, ColumnSet? columns = null) { if (columns == null) columns = new ColumnSet(true); var result = await _client.RetrieveAsync(entityName, id, columns); return result; } /// public async Task> ListByFilter(string entityName, FilterExpression? filter = null, ColumnSet? columns = null) { List results = new List(); QueryExpression queryData = new QueryExpression(entityName); if (filter != null) queryData.Criteria = filter; if (columns != null) queryData.ColumnSet = columns; var result = await _client.RetrieveMultipleAsync(queryData); if (result?.Entities != null) { foreach (var item in result.Entities) { results.Add(item); } } return results; } /// public async Task> ListByFilter(string entityName, QueryExpression query) { if (query is null) { throw new ArgumentNullException(nameof(query)); } List results = new List(); var result = await _client.RetrieveMultipleAsync(query); if (result?.Entities != null) { foreach (var item in result.Entities) { results.Add(item); } } return results; } /// public async Task GetMetadata(string entityName) { var retrieveEntityRequest = new RetrieveEntityRequest { EntityFilters = EntityFilters.All, LogicalName = entityName }; var response = await _client.ExecuteAsync(retrieveEntityRequest); var metadata = (RetrieveEntityResponse)response; return metadata.EntityMetadata; } /// public async Task SetState(EntityReference entity, string state, string status) { var setStateRequest = new SetStateRequest() { EntityMoniker = new EntityReference { Id = entity.Id, LogicalName = entity.LogicalName, }, State = new OptionSetValue(int.Parse(state)), Status = new OptionSetValue(int.Parse(status)) }; await _client.ExecuteAsync(setStateRequest); } /// public async Task Upsert(Entity entity) { var request = new UpsertRequest() { Target = entity }; await _client.ExecuteAsync(request); } /// public Task Create(Entity targetEntity, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) { var createRequest = new CreateRequest { Target = targetEntity }; createRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); createRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); createRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); var resultCreate = (CreateResponse)_client.Execute(createRequest); return Task.FromResult(resultCreate.id); } /// public async Task Update(Entity entity, bool useOptimisticConcurrency = false, bool bypassSynchronousCustomLogic = false, bool suppressPowerAutomateTrigger = false, bool suppressDuplicateDetectionRules = true) { var updateRequest = new UpdateRequest { Target = entity }; if (useOptimisticConcurrency) { updateRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; } updateRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousCustomLogic); updateRequest.Parameters.Add("SuppressCallbackRegistrationExpanderJob", suppressPowerAutomateTrigger); updateRequest.Parameters.Add("SuppressDuplicateDetection", suppressDuplicateDetectionRules); await _client.ExecuteAsync(updateRequest); } /// public async Task Delete(Guid id, string entityName, bool bypassSynchronousLogic = false, bool useOptimisticConcurrency = false) { var deleteRequest = new DeleteRequest { Target = new EntityReference(entityName, id) }; if (useOptimisticConcurrency) { deleteRequest.ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches; } deleteRequest.Parameters.Add("BypassCustomPluginExecution", bypassSynchronousLogic); await _client.ExecuteAsync(deleteRequest); } } } ================================================ FILE: src/Liquid.GenAi.OpenAi/Extensions/IServiceCollectionExtension.cs ================================================ using Liquid.Core.GenAi; using Liquid.GenAi.OpenAi.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Diagnostics.CodeAnalysis; namespace Liquid.GenAi.OpenAi.Extensions { /// /// Extension methods to register Liquid OpenAi Completions services. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionExtension { /// /// extension method to register Liquid OpenAi Completions service, /// factory and binding settings from configuration. /// /// extension method reference /// configuration section name public static IServiceCollection AddLiquidOpenAiCompletions(this IServiceCollection services, string sectionName) { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); services.AddSingleton(); services.AddTransient(); return services; } } } ================================================ FILE: src/Liquid.GenAi.OpenAi/IOpenAiClientFactory.cs ================================================ using OpenAI.Chat; namespace Liquid.GenAi.OpenAi { /// /// Provide generator methods. /// public interface IOpenAiClientFactory { /// /// Provide a instance of with conection started. /// /// OpenAI connections alias. ChatClient GetOpenAIClient(string clientId); } } ================================================ FILE: src/Liquid.GenAi.OpenAi/Liquid.GenAi.OpenAi.csproj ================================================  net8.0 enable enable Liquid.GenAi.OpenAi enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.1.1 true true Full Liquid adapter for Azure OpenAi completions. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.GenAi.OpenAi/OpenAiAdapter.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.GenAi; using Liquid.Core.GenAi.Entities; using Liquid.Core.GenAi.Enums; using Liquid.Core.GenAi.Settings; using OpenAI.Chat; using System.Diagnostics.CodeAnalysis; namespace Liquid.GenAi.OpenAi { /// [ExcludeFromCodeCoverage] public class OpenAiAdapter : ILiquidGenAi { private readonly IOpenAiClientFactory _factory; /// /// Initialize a new instance of /// /// Open IA client Factory. /// public OpenAiAdapter(IOpenAiClientFactory factory) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); } /// public async Task FunctionCalling(LiquidChatMessages messages, List functions, CompletionsOptions settings) { var client = _factory.GetOpenAIClient(settings.ClientId); var requestMessages = new List(); messages.Messages.ForEach(m => requestMessages.Add(MapChatRequestMessage(m))); var option = MapChatCompletionOptions(settings); functions.ForEach(f => option.Tools.Add(ChatTool.CreateFunctionTool(f.Name, f.Description, f.Parameters))); var responseWithoutStream = await client.CompleteChatAsync(requestMessages, option); var response = responseWithoutStream.Value.Content[0].Text; var result = new ChatCompletionResult() { FinishReason = responseWithoutStream.Value.FinishReason.ToString(), Content = response, Usage = responseWithoutStream.Value.Usage.TotalTokenCount, PromptUsage = responseWithoutStream.Value.Usage.InputTokenCount, CompletionUsage = responseWithoutStream.Value.Usage.OutputTokenCount, }; return result; } /// public async Task CompleteChatAsync(string content, string prompt, CompletionsOptions settings, LiquidChatMessages chatHistory = null) { var client = _factory.GetOpenAIClient(settings.ClientId); var messages = GetChatMessagesAsync(content, prompt, chatHistory); var option = MapChatCompletionOptions(settings); var responseWithoutStream = await client.CompleteChatAsync(messages, option); var response = responseWithoutStream.Value.Content[0].Text; var result = new ChatCompletionResult() { FinishReason = responseWithoutStream.Value.FinishReason.ToString(), Content = response, Usage = responseWithoutStream.Value.Usage.TotalTokenCount, PromptUsage = responseWithoutStream.Value.Usage.InputTokenCount, CompletionUsage = responseWithoutStream.Value.Usage.OutputTokenCount, }; return result; } /// public async Task CompleteChatAsync(LiquidChatMessages messages, CompletionsOptions settings, List functions = null, LiquidChatMessages chatHistory = null) { var client = _factory.GetOpenAIClient(settings.ClientId); var requestMessages = new List(); messages.Messages.ForEach(m => requestMessages.Add(MapChatRequestMessage(m))); var option = MapChatCompletionOptions(settings); if (functions != null) { functions.ForEach(f => option.Tools.Add(ChatTool.CreateFunctionTool(f.Name, f.Description, f.Parameters))); } if (chatHistory?.Messages != null && chatHistory.Messages.Count > 0) { foreach (var message in chatHistory.Messages) { requestMessages.Add(MapChatRequestMessage(message)); } } var responseWithoutStream = await client.CompleteChatAsync(requestMessages, option); var response = responseWithoutStream.Value.FinishReason != ChatFinishReason.ToolCalls ? responseWithoutStream.Value.Content[0].Text : responseWithoutStream.Value.ToolCalls[0].FunctionArguments.ToString(); var result = new ChatCompletionResult() { FinishReason = responseWithoutStream.Value.FinishReason.ToString(), Content = response, Usage = responseWithoutStream.Value.Usage.TotalTokenCount, PromptUsage = responseWithoutStream.Value.Usage.InputTokenCount, CompletionUsage = responseWithoutStream.Value.Usage.OutputTokenCount, }; return result; } /// /// get chat messages for a chat completions request. /// /// content of the user message /// prompt message /// chat context messages private static List GetChatMessagesAsync(string content, string prompt, LiquidChatMessages? chatHistory = null) { var messages = new List { new SystemChatMessage(prompt) }; if (chatHistory?.Messages != null && chatHistory.Messages.Count > 0) { foreach (var message in chatHistory.Messages) { messages.Add(MapChatRequestMessage(message)); } } messages.Add(new UserChatMessage(content)); return messages; } /// /// Return a chat request message based on the role of the message. /// /// chat message /// private static ChatMessage MapChatRequestMessage(LiquidChatMessage message) { ChatMessage? chatRequestMessage = null; switch (message.Role) { case LiquidMessageRole.System: chatRequestMessage = new SystemChatMessage(CreateContent(message)); break; case LiquidMessageRole.Assistant: chatRequestMessage = new AssistantChatMessage(CreateContent(message)); break; case LiquidMessageRole.User: chatRequestMessage = new UserChatMessage(CreateContent(message)); break; default: break; } if (chatRequestMessage == null) { throw new ApplicationException($"The folowing message is invalid: {message}"); } return chatRequestMessage; } private static IEnumerable CreateContent(LiquidChatMessage message) { var messageList = message.Content.ToList(); var content = new List(); messageList.ForEach(x => content.Add(x.Kind == LiquidContentKind.Text ? ChatMessageContentPart.CreateTextPart(x.Text) : ChatMessageContentPart.CreateImagePart(x.ImageUri))); return content; } /// /// Return a chat completions options based on the chat completions settings. /// /// Chat completions settings /// private static ChatCompletionOptions MapChatCompletionOptions(CompletionsOptions settings) { return new ChatCompletionOptions() { Temperature = settings.Temperature, MaxOutputTokenCount = settings.MaxTokens, TopP = settings.TopP, FrequencyPenalty = settings.FrequencyPenalty, PresencePenalty = settings.PresencePenalty }; } } } ================================================ FILE: src/Liquid.GenAi.OpenAi/OpenAiClientFactory.cs ================================================ using Azure; using Azure.AI.OpenAI; using Liquid.Core.Entities; using Liquid.GenAi.OpenAi.Settings; using Microsoft.Extensions.Options; using OpenAI.Chat; using System.ClientModel.Primitives; using System.Diagnostics.CodeAnalysis; namespace Liquid.GenAi.OpenAi { /// public class OpenAiClientFactory : IOpenAiClientFactory { private readonly IOptions _settings; private readonly List> _openAiClients; /// /// Initialize a new instance of /// /// Connection options. /// public OpenAiClientFactory(IOptions settings) { _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _openAiClients = new List>(); } /// public ChatClient GetOpenAIClient(string clientId) { var settings = _settings.Value.Settings.Where(x => x.ClientId == clientId).ToList(); if (settings.Count == 0) { throw new KeyNotFoundException($"No settings found for client ID: {clientId}"); } var clientDefinition = _openAiClients.Where(x => x.ClientId == clientId)?.MinBy(x => x.Executions); if (clientDefinition != null) { clientDefinition.Executions++; return clientDefinition.Client; } var response = CreateClient(settings, clientId); if (response == null) { throw new KeyNotFoundException($"No settings found for client ID: {clientId}"); } return response; } private ChatClient? CreateClient(List? settings, string clientId) { if (settings == null || settings.Count == 0) { throw new ArgumentNullException(nameof(settings)); } ChatClient? client = null; foreach (var setting in settings) { var options = new AzureOpenAIClientOptions(); options.RetryPolicy = new ClientRetryPolicy(setting.MaxRetries); AzureOpenAIClient azureClient = new(new Uri(setting.Url), new AzureKeyCredential(setting.Key), options); client = azureClient.GetChatClient(setting.DeploymentName); _openAiClients.Add(new ClientDictionary(setting.ClientId, client)); } var result = _openAiClients.Where(x => x.ClientId == clientId).MinBy(x => x.Executions); return result?.Client; } } } ================================================ FILE: src/Liquid.GenAi.OpenAi/Settings/OpenAiOptions.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.GenAi.OpenAi.Settings { /// /// The options for chat completions request. /// [ExcludeFromCodeCoverage] public class OpenAiOptions { /// /// Client connection list to use for create OpenAi clients. /// public List Settings { get; set; } } /// /// The settings for chat completions request. /// [ExcludeFromCodeCoverage] public class OpenAiSettings { /// /// Client connection alias. /// public string ClientId { get; set; } /// /// The URI for an GenAI resource as retrieved from, for example, Azure Portal. ///This should include protocol and hostname. /// public string Url { get; set; } /// /// Key to use to authenticate with the service. /// public string Key { get; set; } /// /// The deployment name to use for a chat completions request. /// public string DeploymentName { get; set; } /// /// The maximum number of retries to allow. /// public int MaxRetries { get; set; } = 0; } } ================================================ FILE: src/Liquid.Messaging.Kafka/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Reflection; namespace Liquid.Messaging.Kafka.Extensions.DependencyInjection { /// /// Startup extension methods. Used to configure the startup application. /// public static class IServiceCollectionExtension { /// /// Register a with its dependency, and with /// . /// /// Type of entity that will be consumed by this service instance. /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidKafkaProducer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) { services.TryAddTransient(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); if (activateTelemetry) { services.AddSingleton>(); services.AddSingletonLiquidTelemetry, KafkaProducer>(); } else { services.AddSingleton, KafkaProducer>(); } return services; } /// /// Register Liquid resources for consumers /// /// and a service with its dependency, with /// . /// /// Type of entity that will be consumed by this service instance. /// Type of implementation from /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. /// Array of assemblies that contains domain handlers implementation. public static IServiceCollection AddLiquidKafkaConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true, params Assembly[] assemblies) where TWorker : class, ILiquidWorker { services.AddLiquidMessageConsumer(assemblies); services.AddConsumer(sectionName, activateTelemetry); return services; } /// /// Register a service with its dependency, and with /// . /// In order for consumers injected by this method to work correctly and /// domain handlers/services in your build configurator. /// /// Type of implementation from /// Type of entity that will be consumed by the service instance. /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidKafkaConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) where TWorker : class, ILiquidWorker { services.AddLiquidWorkerService(); services.AddConsumer(sectionName, activateTelemetry); return services; } private static IServiceCollection AddConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) { services.AddTransient(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); if (activateTelemetry) { services.AddSingleton>(); services.AddSingletonLiquidTelemetry, KafkaConsumer>(); } else { services.AddSingleton, KafkaConsumer>(); } return services; } } } ================================================ FILE: src/Liquid.Messaging.Kafka/Extensions/HeadersExtension.cs ================================================ using Confluent.Kafka; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; namespace Liquid.Messaging.Kafka.Extensions { /// /// Kafka Extensions Class. /// [ExcludeFromCodeCoverage] internal static class HeadersExtension { /// /// Adds the custom headers. /// /// The message attributes. /// The custom headers. public static Headers AddCustomHeaders(this Headers messageAttributes, IDictionary customHeaders) { foreach (var (key, value) in customHeaders) { if (value != null) { messageAttributes.Add(key, Encoding.UTF8.GetBytes(value.ToString())); } } return messageAttributes; } /// /// Gets the custom headers. /// /// The message attributes. /// public static Dictionary GetCustomHeaders(this Headers messageAttributes) { var returnValue = new Dictionary(); foreach (var header in messageAttributes) { returnValue.TryAdd(header.Key, Encoding.UTF8.GetString(header.GetValueBytes())); } return returnValue; } } } ================================================ FILE: src/Liquid.Messaging.Kafka/IKafkaFactory.cs ================================================ using Confluent.Kafka; using Liquid.Messaging.Kafka.Settings; using System; namespace Liquid.Messaging.Kafka { /// /// Provides new instances if Kafka and . /// public interface IKafkaFactory { /// /// Create a new instance of a high-level Apache Kafka consumer with deserialization capability. /// /// Consumer configuration set. IConsumer GetConsumer(KafkaSettings settings); /// /// Create a neu instance of a high level producer with serialization capability. /// /// Consumer configuration set. IProducer GetProducer(KafkaSettings settings); } } ================================================ FILE: src/Liquid.Messaging.Kafka/KafkaConsumer.cs ================================================ using Confluent.Kafka; using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Extensions; using Liquid.Messaging.Kafka.Settings; using Microsoft.Extensions.Options; using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.Kafka { /// public class KafkaConsumer : ILiquidConsumer { private readonly IKafkaFactory _factory; private readonly KafkaSettings _settings; private IConsumer _client; /// public event Func, CancellationToken, Task> ConsumeMessageAsync; /// public event Func ProcessErrorAsync; /// /// Initialize a new instance of /// /// Kafka client provider service. /// Configuration properties set. /// public KafkaConsumer(IKafkaFactory kafkaFactory, IOptions kafkaSettings) { _factory = kafkaFactory ?? throw new ArgumentNullException(nameof(kafkaFactory)); _settings = kafkaSettings?.Value ?? throw new ArgumentNullException(nameof(kafkaSettings)); } /// public Task RegisterMessageHandler(CancellationToken cancellationToken = default) { if (ConsumeMessageAsync is null) { throw new NotImplementedException($"The {nameof(ProcessErrorAsync)} action must be added to class."); } ProcessErrorAsync += ProcessError; _client = _factory.GetConsumer(_settings); var task = Task.Run( async () => { using (_client) { _client.Subscribe(_settings.Topic); await MessageHandler(cancellationToken); } }); return task; } /// /// Process incoming messages. /// /// Propagates notification that operations should be canceled. protected async Task MessageHandler(CancellationToken cancellationToken) { try { while (!cancellationToken.IsCancellationRequested) { var deliverEvent = _client.Consume(cancellationToken); _ = Task.Run(async () => { try { await ConsumeMessageAsync(GetEventArgs(deliverEvent), cancellationToken); if (!_settings.EnableAutoCommit) { _client.Commit(deliverEvent); } } catch (Exception ex) { var errorArgs = new ConsumerErrorEventArgs { Exception = ex, }; await ProcessErrorAsync(errorArgs); } }); } } catch (Exception ex) { throw new MessagingConsumerException(ex); } } private ConsumerMessageEventArgs GetEventArgs(ConsumeResult deliverEvent) { var headers = deliverEvent.Message.Headers.GetCustomHeaders(); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var data = JsonSerializer.Deserialize(deliverEvent.Message.Value, options); return new ConsumerMessageEventArgs { Data = data, Headers = headers }; } private Task ProcessError(ConsumerErrorEventArgs args) { _client.Close(); throw args.Exception; } } } ================================================ FILE: src/Liquid.Messaging.Kafka/KafkaFactory.cs ================================================ using Confluent.Kafka; using Liquid.Core.Exceptions; using Liquid.Messaging.Kafka.Settings; using System; namespace Liquid.Messaging.Kafka { /// public class KafkaFactory : IKafkaFactory { /// public IConsumer GetConsumer(KafkaSettings settings) { try { var config = MapConsumerSettings(settings); var client = new ConsumerBuilder(config).Build(); return client; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, "for topic '" + settings?.Topic + "'"); } } /// public IProducer GetProducer(KafkaSettings settings) { try { var config = MapProducerSettings(settings); return new ProducerBuilder(config).Build(); } catch(Exception ex) { throw new MessagingMissingConfigurationException(ex, "for topic '" + settings?.Topic + "'"); } } private static ConsumerConfig MapConsumerSettings(KafkaSettings settings) { if(settings == null) throw new ArgumentNullException(nameof(settings)); return new ConsumerConfig { SocketKeepaliveEnable = settings.SocketKeepAlive, SocketTimeoutMs = settings.Timeout, BootstrapServers = settings.ConnectionString, ClientId = settings.ConnectionId, EnableAutoCommit = settings.EnableAutoCommit, GroupId = settings.GroupId }; } private static ProducerConfig MapProducerSettings(KafkaSettings settings) { if (settings == null) throw new ArgumentNullException(nameof(settings)); return new ProducerConfig { SocketKeepaliveEnable = settings.SocketKeepAlive, SocketTimeoutMs = settings.Timeout, BootstrapServers = settings.ConnectionString, ClientId = settings.ConnectionId, }; } } } ================================================ FILE: src/Liquid.Messaging.Kafka/KafkaProducer.cs ================================================ using Confluent.Kafka; using Liquid.Core.Utils; using Liquid.Core.Extensions; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Extensions; using Liquid.Messaging.Kafka.Settings; using System; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Options; namespace Liquid.Messaging.Kafka { /// public class KafkaProducer : ILiquidProducer { private readonly IProducer _client; private readonly KafkaSettings _settings; /// /// Initializes a new instance of the class. /// /// /// /// public KafkaProducer(IOptions settings, IKafkaFactory factory) { if (factory is null) { throw new ArgumentNullException(nameof(factory)); } _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); _client = factory.GetProducer(_settings); } /// public async Task SendMessagesAsync(IEnumerable messageBodies) { try { foreach (var message in messageBodies) { await SendMessageAsync(message); } } catch (Exception ex) { throw new MessagingProducerException(ex); } } /// public async Task SendMessageAsync(TEntity messageBody, IDictionary customProperties = null) { try { var message = GetMessage(messageBody, customProperties); await _client.ProduceAsync(_settings.Topic, message); } catch (Exception ex) { throw new MessagingProducerException(ex); } } private Message GetMessage(TEntity messageBody, IDictionary customProperties) { var message = !_settings.CompressMessage ? messageBody.ToJsonString() : Encoding.UTF8.GetString(messageBody.ToJsonString().GzipCompress()); var request = new Message { Value = message, Headers = customProperties is null ? null : new Headers().AddCustomHeaders(customProperties) }; return request; } } } ================================================ FILE: src/Liquid.Messaging.Kafka/Liquid.Messaging.Kafka.csproj ================================================  net8.0 Liquid.Messaging.Kafka MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true The Liquid.Messaging.Kafka provides producer and consumer patterns to allow the send and consumption of Messaging inside your microservice. This component allows send and consuming messages from Kafka. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Messaging.Kafka/Settings/KafkaSettings.cs ================================================ using Liquid.Core.Attributes; namespace Liquid.Messaging.Kafka.Settings { /// /// Kafka configuration properties set. /// [LiquidSectionName("liquid:messaging:kafka:")] public class KafkaSettings { /// /// Bootstrap server connection string. /// public string ConnectionString { get; set; } /// /// Socket keep alive flag. /// public bool SocketKeepAlive { get; set; } /// /// Client identifier. /// public string ConnectionId { get; set; } /// /// Topic to consumer subscribe to. A regex can be specified to subscribe to the set of /// all matching topics. /// public string Topic { get; set; } /// /// Default timeout for network requests.Producer: ProduceRequests will use the /// lesser value of `socket.timeout.ms` and remaining `message.timeout.ms` for the /// first message in the batch. Consumer: FetchRequests will use `fetch.wait.max.ms` /// + `socket.timeout.ms`. Admin: Admin requests will use `socket.timeout.ms` or /// explicitly set `rd_kafka_AdminOptions_set_operation_timeout()` value. default: 60000 /// public int Timeout { get; set; } = 6000; /// /// Automatically and periodically commit offsets in the background. Note: setting /// this to false does not prevent the consumer from fetching previously committed /// start offsets. default: true /// public bool EnableAutoCommit { get; set; } = true; /// /// Indicates whether to be a compressed message. /// public bool CompressMessage { get; set; } /// /// Client group id string. All clients sharing the same group.id belong to the same group. /// public string GroupId { get; set; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Extensions/DependencyInjection/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Liquid.Messaging.RabbitMq.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Reflection; namespace Liquid.Messaging.RabbitMq.Extensions.DependencyInjection { /// /// Startup extension methods. Used to configure the startup application. /// public static class IServiceCollectionExtension { /// /// Register a with its dependency, and with /// . /// /// Type of entity that will be consumed by this service instance. /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidRabbitMqProducer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) { services.TryAddTransient(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); if (activateTelemetry) { services.AddSingleton>(); services.AddSingletonLiquidTelemetry, RabbitMqProducer>(); } else { services.AddSingleton, RabbitMqProducer>(); } return services; } /// /// Register Liquid resources for consumers /// /// and a service with its dependency, with /// . /// /// Type of entity that will be consumed by this service instance. /// Type of implementation from /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. /// Array of assemblies that contains domain handlers implementation. public static IServiceCollection AddLiquidRabbitMqConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true, params Assembly[] assemblies) where TWorker : class, ILiquidWorker { services.AddLiquidMessageConsumer(assemblies); services.AddConsumer(sectionName, activateTelemetry); return services; } /// /// Register a service with its dependency, and with /// . /// In order for consumers injected by this method to work correctly, you will need to register the Liquid settings and /// domain handlers/services in your build configurator. /// /// Type of implementation from /// Type of entity that will be consumed by the service instance. /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidRabbitMqConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) where TWorker : class, ILiquidWorker { services.AddLiquidWorkerService(); services.AddConsumer(sectionName, activateTelemetry); return services; } private static IServiceCollection AddConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) { services.AddSingleton(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); if (activateTelemetry) { services.AddSingleton>(); services.AddSingletonLiquidTelemetry, RabbitMqConsumer>(); } else { services.AddSingleton, RabbitMqConsumer>(); } return services; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/IRabbitMqFactory.cs ================================================ using Liquid.Messaging.RabbitMq.Settings; using RabbitMQ.Client; namespace Liquid.Messaging.RabbitMq { /// /// RabbitMq provider. /// public interface IRabbitMqFactory { /// /// Initialize and return a new instance of /// /// RabbitMq producer configuration properties set. IModel GetSender(RabbitMqProducerSettings settings); /// /// Initialize and return a new instance of /// /// RabbitMq consumer configuration properties set. IModel GetReceiver(RabbitMqConsumerSettings settings); } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Liquid.Messaging.RabbitMq.csproj ================================================  net8.0 Liquid.Messaging.RabbitMq MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.1 true The Liquid.Messaging.RabbitMq provides producer and consumer patterns to allow the send and consumption of Messaging inside your microservice. The main components are ILightProducer and ILightConsumer. This component allows send and consuming messages from RabbitMq. This component is part of Liquid Application Framework. {6CFF2DEC-50E9-417C-A6E1-A0EFCD1EB94C} True ================================================ FILE: src/Liquid.Messaging.RabbitMq/RabbitMqConsumer.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Extensions; using Liquid.Core.Interfaces; using Liquid.Core.Utils; using Liquid.Messaging.RabbitMq.Settings; using Microsoft.Extensions.Options; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.RabbitMq { /// /// RabbitMq Consumer Adapter Class. /// /// The type of the message object. /// /// public class RabbitMqConsumer : ILiquidConsumer { private readonly bool _autoAck; private IModel _channelModel; private readonly IRabbitMqFactory _factory; private readonly IOptions _settings; /// public event Func, CancellationToken, Task> ConsumeMessageAsync; /// public event Func ProcessErrorAsync; /// /// Initilize an instance of /// /// RabbitMq client factory. /// Configuration properties set. public RabbitMqConsumer(IRabbitMqFactory factory, IOptions settings) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); _settings = settings ?? throw new ArgumentNullException(nameof(settings)); if (_settings.Value == null) { throw new ArgumentNullException(nameof(_settings.Value), "The settings value must be set."); } _autoAck = _settings.Value.AdvancedSettings?.AutoAck ?? true; } /// public async Task RegisterMessageHandler(CancellationToken cancellationToken = default) { if (ConsumeMessageAsync is null) { throw new NotImplementedException($"The {nameof(ConsumeMessageAsync)} action must be added to class."); } _channelModel = _factory.GetReceiver(_settings.Value); var consumer = new EventingBasicConsumer(_channelModel); consumer.Received += async (model, deliverEvent) => { await MessageHandler(deliverEvent, cancellationToken); }; _channelModel.BasicConsume(_settings.Value.Queue, _autoAck, consumer); } /// /// Process incoming messages. /// /// Message to be processed. /// Propagates notification that operations should be canceled. protected async Task MessageHandler(BasicDeliverEventArgs deliverEvent, CancellationToken cancellationToken) { try { await ConsumeMessageAsync(GetEventArgs(deliverEvent), cancellationToken); if (!_autoAck) { _channelModel.BasicAck(deliverEvent.DeliveryTag, false); } } catch (Exception) { if (!_autoAck) { var queueAckMode = _settings.Value.AdvancedSettings.QueueAckModeSettings ?? new QueueAckModeSettings() { QueueAckMode = QueueAckModeEnum.BasicAck, Requeue = true }; switch (queueAckMode.QueueAckMode) { case QueueAckModeEnum.BasicAck: _channelModel.BasicNack(deliverEvent.DeliveryTag, false, queueAckMode.Requeue); break; case QueueAckModeEnum.BasicReject: _channelModel.BasicReject(deliverEvent.DeliveryTag, queueAckMode.Requeue); break; default: _channelModel.BasicNack(deliverEvent.DeliveryTag, false, true); break; } } } } private ConsumerMessageEventArgs GetEventArgs(BasicDeliverEventArgs deliverEvent) { var data = deliverEvent.BasicProperties?.ContentType == CommonExtensions.GZipContentType ? deliverEvent.Body.ToArray().GzipDecompress().ParseJson() : deliverEvent.Body.ToArray().ParseJson(); var headers = deliverEvent.BasicProperties?.Headers; return new ConsumerMessageEventArgs { Data = data, Headers = headers }; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/RabbitMqFactory.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Messaging.RabbitMq.Settings; using RabbitMQ.Client; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq { /// [ExcludeFromCodeCoverage] public class RabbitMqFactory : IRabbitMqFactory, IDisposable { private IConnection _connection; private IModel _model; private bool _disposed; /// /// Initialize a new instace of /// public RabbitMqFactory() { } /// public IModel GetReceiver(RabbitMqConsumerSettings settings) { try { if (_connection == null && _model == null) { var connectionFactory = new ConnectionFactory { Uri = new Uri(settings.QueueSettings.ConnectionString), RequestedHeartbeat = TimeSpan.FromSeconds(settings.QueueSettings?.RequestHeartBeatInSeconds ?? 60), AutomaticRecoveryEnabled = settings.QueueSettings?.AutoRecovery ?? true }; _connection = connectionFactory.CreateConnection(settings.QueueSettings.ConnectionName); _model = _connection.CreateModel(); } if (settings.QueueSettings.Prefetch.HasValue && settings.QueueSettings.PrefetchCount.HasValue && settings.QueueSettings.Global.HasValue) { _model.BasicQos(settings.QueueSettings.Prefetch.Value, settings.QueueSettings.PrefetchCount.Value, settings.QueueSettings.Global.Value); } _model.QueueBind(settings.Queue, settings.Exchange, string.Empty); return _model; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, "for queue '" + settings?.Queue + "'"); } } /// public IModel GetSender(RabbitMqProducerSettings settings) { try { if (_connection == null && _model == null) { var connectionFactory = new ConnectionFactory { Uri = new Uri(settings.QueueSettings.ConnectionString), RequestedHeartbeat = TimeSpan.FromSeconds(settings.QueueSettings?.RequestHeartBeatInSeconds ?? 60), AutomaticRecoveryEnabled = settings.QueueSettings?.AutoRecovery ?? true }; _connection = connectionFactory.CreateConnection(settings.QueueSettings.ConnectionName); _model = _connection.CreateModel(); } return _model; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, "for exange '" + settings?.Exchange + "'"); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// Indicates if method should perform dispose. protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _connection?.Dispose(); _model?.Dispose(); } _disposed = true; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/RabbitMqProducer.cs ================================================ using Liquid.Core.Extensions; using Liquid.Core.Utils; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.RabbitMq.Settings; using RabbitMQ.Client; using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Options; namespace Liquid.Messaging.RabbitMq { /// public class RabbitMqProducer : ILiquidProducer { private readonly RabbitMqProducerSettings _settings; private readonly IModel _channelModel; /// /// Initializes a new instance of the class. /// /// /// /// public RabbitMqProducer(IRabbitMqFactory factory, IOptions settings) { if (factory is null) { throw new ArgumentNullException(nameof(factory)); } _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); _channelModel = factory.GetSender(_settings); } /// public async Task SendMessageAsync(TEntity messageBody, IDictionary customProperties = null) { if (customProperties == null) customProperties = new Dictionary(); try { await Task.Run(() => { var messageProperties = GetProperties(customProperties); var messageBytes = !_settings.CompressMessage ? messageBody.ToJsonBytes() : messageBody.ToJsonString().GzipCompress(); _channelModel.BasicPublish(_settings.Exchange, string.Empty, messageProperties, messageBytes); }); } catch (Exception ex) { throw new MessagingProducerException(ex); } } /// public async Task SendMessagesAsync(IEnumerable messageBodies) { try { foreach (var message in messageBodies) { await SendMessageAsync(message); } } catch (Exception ex) { throw new MessagingProducerException(ex); } } private IBasicProperties GetProperties(IDictionary customProperties) { var messageProperties = _channelModel.CreateBasicProperties(); var messageId = Guid.NewGuid().ToString(); messageProperties.Persistent = _settings.AdvancedSettings?.Persistent ?? false; messageProperties.Expiration = _settings.AdvancedSettings?.Expiration ?? "30000"; messageProperties.Headers = customProperties; messageProperties.MessageId = messageId; if (_settings.CompressMessage) { messageProperties.ContentType = CommonExtensions.GZipContentType; } return messageProperties; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/AdvancedSettings.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Settings { /// /// RabbitMq Custom Settings Class. /// [ExcludeFromCodeCoverage] public class AdvancedSettings { /// /// Gets or sets the type of the exchange. /// /// /// The type of the exchange. /// public string ExchangeType { get; set; } /// /// Gets or sets a value indicating whether Exchange/Queue[automatic delete]. /// /// /// true if [automatic delete]; otherwise, false. /// public bool AutoDelete { get; set; } /// /// Gets or sets a value indicating whether Exchange/Queue is durable. /// /// /// true if durable; otherwise, false. /// public bool Durable { get; set; } /// /// Gets or sets a value indicating whether [automatic ack]. /// /// /// true if [automatic ack]; otherwise, false. /// public bool AutoAck { get; set; } /// /// Gets or sets the expiration. /// /// /// The expiration. /// public string Expiration { get; set; } /// /// Gets or sets a value indicating whether the message is persistent. /// /// /// true if persistent; otherwise, false. /// public bool Persistent { get; set; } /// /// Gets or sets a value indicating whether this Queue is exclusive. /// /// /// true if exclusive; otherwise, false. /// public bool Exclusive { get; set; } /// /// Gets or sets the exchange arguments. /// /// /// The exchange arguments. /// public IDictionary ExchangeArguments { get; set; } /// /// Gets or sets the queue arguments. /// /// /// The queue arguments. /// public IDictionary QueueArguments { get; set; } /// /// Queue Ack Mode Settings /// public QueueAckModeSettings QueueAckModeSettings { get; set; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/QueueAckModeEnum.cs ================================================ namespace Liquid.Messaging.RabbitMq.Settings { /// /// Defines the AckMode /// public enum QueueAckModeEnum { /// /// Basic Ack /// BasicAck = 0, /// /// Reject Ack /// BasicReject = 1 } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/QueueAckModeSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Settings { /// /// Queue Ack Mode Settings /// [ExcludeFromCodeCoverage] public class QueueAckModeSettings { /// /// Gets or sets a value indicating whether the message should requeued. /// /// /// true if [requeue]; otherwise, false. /// public bool Requeue { get; set; } /// /// Defines the AckMode /// public QueueAckModeEnum QueueAckMode { get; set; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/RabbitMqConsumerSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Settings { /// /// RabbitMq Consumer configurations set. /// [ExcludeFromCodeCoverage] public class RabbitMqConsumerSettings { /// /// Gets a value indicating whether [compress message]. /// /// /// true if [compress message]; otherwise, false. /// public bool CompressMessage { get; set; } /// /// Gets the topic exchange. /// /// /// The topic. /// public string Exchange { get; set; } /// /// Gets the subscription queue. /// /// /// The subscription. /// public string Queue { get; set; } /// /// Gets or sets the advanced settings. /// /// /// The advanced settings. /// public AdvancedSettings AdvancedSettings { get; set; } /// /// Infra basic settings. /// public RabbitMqSettings QueueSettings { get; set; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/RabbitMqProducerSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Settings { /// /// RabbitMq Producer configurations set. /// [ExcludeFromCodeCoverage] public class RabbitMqProducerSettings { /// /// Gets a value indicating whether [compress message]. /// /// /// true if [compress message]; otherwise, false. /// public bool CompressMessage { get; set; } /// /// Gets the topic exchange. /// /// /// The topic. /// public string Exchange { get; set; } /// /// Gets or sets the advanced settings. /// /// /// The advanced settings. /// public AdvancedSettings AdvancedSettings { get; set; } /// /// Infra basic settings. /// public RabbitMqSettings QueueSettings { get; set; } } } ================================================ FILE: src/Liquid.Messaging.RabbitMq/Settings/RabbitMqSettings.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Settings { /// /// RabbitMq configurations set. /// [ExcludeFromCodeCoverage] public class RabbitMqSettings { /// /// Gets or sets the connection string. /// /// /// The connection string. /// public string ConnectionString { get; set; } /// /// Gets or sets the name of the connection. /// /// /// The connection string. /// public string ConnectionName { get; set; } /// /// Gets or sets the request heart beat in seconds. /// /// /// The request heart beat in seconds. /// public ushort? RequestHeartBeatInSeconds { get; set; } /// /// Gets or sets the prefetch count. /// /// /// The prefetch count. /// public ushort? PrefetchCount { get; set; } /// /// Gets or sets the prefetch. /// /// /// The prefetch. /// public uint? Prefetch { get; set; } /// /// Gets or sets a value indicating whether the Qos is global. /// /// /// true if global; otherwise, false. /// public bool? Global { get; set; } /// /// Gets or sets the auto recovery property of Rabbit to recover exchanges and queue bindings. /// /// /// The auto recovery value. /// public bool? AutoRecovery { get; set; } } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/Extensions/DependencyInjection/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Liquid.Messaging.ServiceBus.Settings; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.Extensions.Configuration; namespace Liquid.Messaging.ServiceBus.Extensions.DependencyInjection { /// /// Startup extension methods. Used to configure the startup application. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionExtensions { /// /// Register a with its dependency, and with /// . /// /// Type of entity that will be consumed by this service instance. /// Extended service collection instance. /// Configuration section name. /// Entity path to configure this producer. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidServiceBusProducer(this IServiceCollection services, string sectionName, string entityPath, bool activateTelemetry = true) { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); services.TryAddTransient(); if (activateTelemetry) { services.AddSingleton((provider) => { return ActivatorUtilities.CreateInstance>(provider, entityPath); }); services.AddSingletonLiquidTelemetry, ServiceBusProducer>(); } else { services.AddSingleton>((provider) => { return ActivatorUtilities.CreateInstance>(provider, entityPath); }); } return services; } /// /// Register Liquid resources for consumers /// /// and a service with its dependency, with /// . /// /// Type of entity that will be consumed by this service instance. /// Type of implementation from /// Extended service collection instance. /// Configuration section name. /// Entity name to configure for this worker. /// Indicates if telemetry interceptor must be registered. /// Array of assemblies that contains domain handlers implementation. public static IServiceCollection AddLiquidServiceBusConsumer(this IServiceCollection services , string sectionName , string entityPath , bool activateTelemetry = true , params Assembly[] assemblies) where TWorker : class, ILiquidWorker { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); services.AddLiquidMessageConsumer(assemblies); services.AddConsumer(entityPath, activateTelemetry); return services; } /// /// Register a service with its dependency, and with /// . /// In order for consumers injected by this method to work correctly and /// domain handlers/services in your build configurator. /// /// Type of implementation from /// Type of entity that will be consumed by the service instance. /// Extended service collection instance. /// Configuration section name. /// Indicates if telemetry interceptor must be registered. public static IServiceCollection AddLiquidServiceBusConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) where TWorker : class, ILiquidWorker { services.AddLiquidWorkerService(); services.AddConsumer(sectionName, activateTelemetry); return services; } private static IServiceCollection AddConsumer(this IServiceCollection services, string sectionName, bool activateTelemetry = true) { services.AddTransient(); if (activateTelemetry) { services.AddSingleton((provider) => { return ActivatorUtilities.CreateInstance>(provider, sectionName); }); services.AddSingletonLiquidTelemetry, ServiceBusConsumer>(); } else { services.AddSingleton>((provider) => { return ActivatorUtilities.CreateInstance>(provider, sectionName); }); } return services; } } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/IServiceBusFactory.cs ================================================ using Azure.Messaging.ServiceBus; namespace Liquid.Messaging.ServiceBus { /// /// Service Bus client Provider. /// public interface IServiceBusFactory { /// /// Initialize and return a new instance of . /// ServiceBusSender GetSender(string entityPath); /// /// Initialize and return a new instance of /// ServiceBusProcessor GetProcessor(string entityPath); /// /// Initialize and return a new instance of /// ServiceBusReceiver GetReceiver(string entityPath); } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/Liquid.Messaging.ServiceBus.csproj ================================================  net8.0 Liquid.Messaging.ServiceBus MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.1 true The Liquid.Messaging.ServiceBus provides producer and consumer patterns to allow the send and consumption of Messaging inside your microservice. The main components are ILiquidProducer and ILiquidConsumer. This component allows send and consuming messages from Azure Service Bus. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Messaging.ServiceBus/ServiceBusConsumer.cs ================================================ using Azure.Messaging.ServiceBus; using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using System; using System.Collections.Generic; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.ServiceBus { /// public class ServiceBusConsumer : ILiquidConsumer { private ServiceBusProcessor _messageProcessor; private readonly IServiceBusFactory _factory; private readonly string _settingsName; /// public event Func, CancellationToken, Task> ConsumeMessageAsync; /// public event Func ProcessErrorAsync; /// /// Initilize an instance of /// /// Service Bus client factory. /// Configuration section name for this service instance. public ServiceBusConsumer(IServiceBusFactory factory, string settingsName) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); _settingsName = settingsName ?? throw new ArgumentNullException(nameof(settingsName)); } /// public async Task RegisterMessageHandler(CancellationToken cancellationToken = default) { if (ConsumeMessageAsync is null) { throw new NotImplementedException($"The {nameof(ProcessErrorAsync)} action must be added to class."); } _messageProcessor = _factory.GetProcessor(_settingsName); ProcessErrorAsync += ProcessError; _messageProcessor.ProcessMessageAsync += MessageHandler; _messageProcessor.ProcessErrorAsync += ErrorHandler; await _messageProcessor.StartProcessingAsync(cancellationToken); } /// /// Process incoming messages. /// /// Message to be processed. protected async Task MessageHandler(ProcessMessageEventArgs message) { await ConsumeMessageAsync(GetEventArgs(message.Message), new CancellationToken()); } /// /// Process exception from message handler. /// /// protected async Task ErrorHandler(ProcessErrorEventArgs args) { await ProcessErrorAsync(new ConsumerErrorEventArgs() { Exception = args.Exception }); } private ConsumerMessageEventArgs GetEventArgs(ServiceBusReceivedMessage message) { var data = JsonSerializer.Deserialize(Encoding.UTF8.GetString(message.Body)); var headers = (IDictionary)message.ApplicationProperties; return new ConsumerMessageEventArgs { Data = data, Headers = headers }; } /// /// Process error from message handler. /// /// /// protected static Task ProcessError(ConsumerErrorEventArgs args) { throw new MessagingConsumerException(args.Exception); } } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/ServiceBusFactory.cs ================================================ using Azure.Messaging.ServiceBus; using Liquid.Core.Exceptions; using Liquid.Messaging.ServiceBus.Settings; using Microsoft.Extensions.Options; using System; using System.Linq; namespace Liquid.Messaging.ServiceBus { /// public class ServiceBusFactory : IServiceBusFactory { private readonly ServiceBusSettings _options; /// /// Initialize a new instace of /// /// Configuration Providers public ServiceBusFactory(IOptions settings) { _options = settings.Value ?? throw new ArgumentNullException(nameof(settings)); } /// public ServiceBusProcessor GetProcessor(string settingsName) { try { var config = _options.Settings.FirstOrDefault(x => x.EntityPath == settingsName); if (config == null) { throw new ArgumentOutOfRangeException(nameof(settingsName), $"The settings name '{settingsName}' is not found in the configuration."); } var options = new ServiceBusProcessorOptions(); options.ReceiveMode = config.PeekLockMode ? ServiceBusReceiveMode.PeekLock : ServiceBusReceiveMode.ReceiveAndDelete; options.MaxConcurrentCalls = config.MaxConcurrentCalls; var serviceBusClient = new ServiceBusClient(config.ConnectionString); ServiceBusProcessor processor; if (config.Subscription is null) { processor = serviceBusClient.CreateProcessor(config.EntityPath, options); } else { processor = serviceBusClient.CreateProcessor(config.EntityPath, config.Subscription, options); } return processor; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, settingsName); } } /// public ServiceBusSender GetSender(string settingsName) { var config = _options.Settings.FirstOrDefault(x => x.EntityPath == settingsName); if (config == null) { throw new ArgumentOutOfRangeException(nameof(settingsName), $"The settings name '{settingsName}' is not found in the configuration."); } try { var serviceBusClient = new ServiceBusClient(config.ConnectionString); var sender = serviceBusClient.CreateSender(config.EntityPath); return sender; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, settingsName); } } /// public ServiceBusReceiver GetReceiver(string settingsName) { var config = _options.Settings.FirstOrDefault(x => x.EntityPath == settingsName); if (config == null) { throw new ArgumentOutOfRangeException(nameof(settingsName), $"The settings name '{settingsName}' is not found in the configuration."); } try { var options = new ServiceBusReceiverOptions(); options.ReceiveMode = config.PeekLockMode ? ServiceBusReceiveMode.PeekLock : ServiceBusReceiveMode.ReceiveAndDelete; var serviceBusClient = new ServiceBusClient(config.ConnectionString); var receiver = serviceBusClient.CreateReceiver(config.EntityPath, options); return receiver; } catch (Exception ex) { throw new MessagingMissingConfigurationException(ex, settingsName); } } } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/ServiceBusProducer.cs ================================================ using Azure.Messaging.ServiceBus; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; namespace Liquid.Messaging.ServiceBus { /// public class ServiceBusProducer : ILiquidProducer { private readonly ServiceBusSender _messageSender; /// /// Initialize a new instance of . /// /// Service Bus client factory. /// Configuration section name for this service instance. public ServiceBusProducer(IServiceBusFactory factory, string settingsName) { if (settingsName is null) throw new ArgumentNullException(nameof(settingsName)); _messageSender = factory?.GetSender(settingsName) ?? throw new ArgumentNullException(nameof(factory)); } /// public async Task SendMessagesAsync(IEnumerable messageBodies) { try { await _messageSender.SendMessagesAsync(messageBodies.Select(e => ToMessage(e)).ToList()); } catch (Exception ex) { throw new MessagingProducerException(ex); } } /// public async Task SendMessageAsync(TEntity messageBody, IDictionary customProperties = null) { try { await _messageSender.SendMessageAsync(ToMessage(messageBody, customProperties)); } catch (Exception ex) { throw new MessagingProducerException(ex); } } private ServiceBusMessage ToMessage(TEntity messageBody, IDictionary customProperties = null) { var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(messageBody)); if (customProperties != null) { foreach (var property in customProperties) { message.ApplicationProperties.Add(property); } } return message; } } } ================================================ FILE: src/Liquid.Messaging.ServiceBus/Settings/ServiceBusSettings.cs ================================================ using System; using System.Collections.Generic; namespace Liquid.Messaging.ServiceBus.Settings { /// /// Service bus configuration properties set. /// public class ServiceBusSettings { /// /// Properties set list of service bus configurations. /// public List Settings { get; set; } } /// /// Properties set of Service Bus entity configuration. /// public class ServiceBusEntitySettings { /// /// Connection string of Service Bus resource. /// public string ConnectionString { get; set; } /// /// Topic or Queue path. /// public string EntityPath { get; set; } /// /// Topic subscription path. /// public string Subscription { get; set; } /// /// Indicates max number of concurrent consumer calls. /// The default value is 1. /// public int MaxConcurrentCalls { get; set; } = 1; /// ///Indicates whether the consumer must lock the message during execution and complete, ///abort or move to DLQ according to processing result, ///otherwise it deletes message from queue/topic immediately after reading, ///regardless of processing result. /// The default value is true. /// public bool PeekLockMode { get; set; } = true; } } ================================================ FILE: src/Liquid.Repository.EntityFramework/EntityFrameworkDataContext.cs ================================================ using Microsoft.EntityFrameworkCore; using System; using System.Threading.Tasks; namespace Liquid.Repository.EntityFramework { /// /// Implements the EntityFramework data context for repositories. /// /// /// The type of the . public class EntityFrameworkDataContext : IEntityFrameworkDataContext where TContext : DbContext { private bool _disposed = false; private readonly TContext _databaseContext; /// /// Gets the identifier of data context. /// /// /// The identifier. /// public string Id { get; } /// public TContext DbClient => _databaseContext; /// /// Initializes a new instance of the class. /// /// Data base context object . public EntityFrameworkDataContext(TContext dbContext) { _databaseContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); } /// public async Task StartTransactionAsync() { await _databaseContext.Database.BeginTransactionAsync(); } /// public async Task CommitAsync() { await _databaseContext.Database.CommitTransactionAsync(); } /// public async Task RollbackTransactionAsync() { await _databaseContext.Database.RollbackTransactionAsync(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the allocated resources for this context. /// /// Indicates if method should perform dispose. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { _databaseContext.Dispose(); } _disposed = true; } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/EntityFrameworkRepository.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; namespace Liquid.Repository.EntityFramework { /// /// Implements the EntityFramework repository. /// /// The type of the entity. /// The type of the identifier. /// The type of the . /// #pragma warning disable S2436 // Types and methods should not have too many generic parameters public class EntityFrameworkRepository : ILiquidRepository where TEntity : LiquidEntity, new() where TContext : DbContext #pragma warning restore S2436 // Types and methods should not have too many generic parameters { /// public IEntityFrameworkDataContext EntityDataContext { get; } /// public ILiquidDataContext DataContext => EntityDataContext; private readonly TContext _dbClient; private readonly DbSet _dbSet; private readonly IQueryable _queryableReadOnly; /// /// Initializes a new instance of the class. /// /// The data context. /// /// telemetryFactory /// or /// dataContext /// public EntityFrameworkRepository(IEntityFrameworkDataContext dataContext) { EntityDataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); _dbClient = dataContext.DbClient; _dbSet = _dbClient.Set(); _queryableReadOnly = _dbSet.AsNoTracking(); } /// public async Task AddAsync(TEntity entity) { await _dbSet.AddAsync(entity); await _dbClient.SaveChangesAsync(); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously /// public async Task> FindAllAsync() { IEnumerable returnValue = _queryableReadOnly; return returnValue; } /// public async Task FindByIdAsync(TIdentifier id) { var returnValue = _queryableReadOnly.FirstOrDefault(o => o.Id.Equals(id)); return returnValue; } /// public async Task RemoveByIdAsync(TIdentifier id) { var obj = await _dbSet.FirstOrDefaultAsync(o => o.Id.Equals(id)); if (obj == null) return; _dbSet.Remove(obj); await _dbClient.SaveChangesAsync(); } /// public async Task UpdateAsync(TEntity entity) { _dbClient.Detach(o => o.Id.Equals(entity.Id)); _dbClient.Update(entity); await _dbClient.SaveChangesAsync(); } /// public async Task> WhereAsync(Expression> whereClause) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { var result = _queryableReadOnly.Where(whereClause); var returnValue = result.AsEnumerable(); return returnValue; } /// /// Gets the list of entities that matches the where clause, /// including the related entities. /// /// The where clause. /// The include clause. /// public IEnumerable WhereInclude(Expression> whereClause, Expression> include = null) { var result = _queryableReadOnly.Where(whereClause).Include(include); return result.AsEnumerable(); } /// /// Gets the list of entities that matches the where clause, /// including the related entities. /// /// where clause. /// Entities to include. /// public IEnumerable WhereInclude(Expression> whereClause, string[] includes) { var query = _queryableReadOnly.Where(whereClause); if (includes != null) { foreach (var include in includes) { query = query.Include(include); } } return query.AsEnumerable(); } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/Exceptions/DatabaseDoesNotExistException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.EntityFramework.Exceptions { /// /// Occurs when the database is not found in Sql Server. /// /// [Serializable] [ExcludeFromCodeCoverage] public class DatabaseDoesNotExistException : LiquidException { /// public DatabaseDoesNotExistException() { } /// /// Initializes a new instance of the class. /// /// Name of the database. public DatabaseDoesNotExistException(string databaseName) : base($"Database {databaseName} does not exist. Please check name or create a new database.") { } /// public DatabaseDoesNotExistException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/Extensions/DbContextExtensions.cs ================================================ using System; using System.Linq; namespace Microsoft.EntityFrameworkCore { /// /// Entity Framework extension methods. /// public static class DbContextExtensions { /// /// Untrack entities onto . /// /// type of entity. /// The database context. /// Entities filter. public static void Detach(this DbContext dbContext, Func predicate) where TEntity : class { var entities = dbContext.Set().Local.Where(predicate); foreach (var entity in entities) { dbContext.Entry(entity).State = EntityState.Detached; } } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/Extensions/ILiquidRepositoryExtensions.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Microsoft.EntityFrameworkCore; using System; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Liquid.Repository.EntityFramework.Extensions { /// /// Extension methods for . /// [ExcludeFromCodeCoverage] public static class ILiquidRepositoryExtensions { /// /// Gets the list of entities that matches the where clause, /// including the related entities. /// /// /// /// /// /// The where clause. /// The include clause. /// public static ILiquidRepository WhereInclude(this ILiquidRepository repository , Expression> whereClause, Expression> include = null) where TEntity : LiquidEntity, new() where TContext : DbContext { var EfRepository = repository as EntityFrameworkRepository; EfRepository.WhereInclude(whereClause, include); return repository; } /// /// Gets the list of entities that matches the where clause, /// including the related entities. /// /// /// /// /// /// where clause. /// Entities to include. /// public static ILiquidRepository WhereInclude(this ILiquidRepository repository , Expression> whereClause, string[] includes) where TEntity : LiquidEntity, new() where TContext : DbContext { var EfRepository = repository as EntityFrameworkRepository; EfRepository.WhereInclude(whereClause, includes); return repository; } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/Extensions/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; namespace Liquid.Repository.EntityFramework.Extensions { /// /// Entity Framework Service Collection Extensions Class. /// public static class IServiceCollectionExtensions { /// /// Registers a service /// for the entity , and /// with /// if not previously registered. /// This method also registers for EntityFrameworkRepository instance. /// /// The services. /// An action to configure the /// for the context. public static IServiceCollection AddLiquidEntityFramework(this IServiceCollection services, Action optionsAction) where TEntity : LiquidEntity, new() where TContext : DbContext { AddLiquidDbContext(services, optionsAction); services.AddScoped>(); services.AddScopedLiquidTelemetry, EntityFrameworkRepository>(); return services; } private static void AddLiquidDbContext(IServiceCollection services, Action optionsAction) where TContext : DbContext { var dbContext = services.FirstOrDefault(x => x.ServiceType == typeof(IEntityFrameworkDataContext)); if(dbContext is null) { services.AddDbContext(optionsAction); services.AddScoped, EntityFrameworkDataContext>(); } } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/IEntityFrameworkDataContext.cs ================================================ using Liquid.Core.Interfaces; using Microsoft.EntityFrameworkCore; namespace Liquid.Repository.EntityFramework { /// /// EntityFramework database context interface. /// /// The type of the . /// public interface IEntityFrameworkDataContext : ILiquidDataContext where TContext : DbContext { /// /// Gets the Entity Framework client. /// /// /// The Entity Framework client. /// TContext DbClient { get; } } } ================================================ FILE: src/Liquid.Repository.EntityFramework/Liquid.Repository.EntityFramework.csproj ================================================  net8.0 Liquid.Repository.EntityFramework 8.0.0 Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 MIT https://github.com/Avanade/Liquid-Application-Framework logo.png true true Full 1701;1702;1584;1658 True ================================================ FILE: src/Liquid.Repository.Mongo/Exceptions/MongoEntitySettingsDoesNotExistException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.Repository.Mongo.Exceptions { /// /// Occurs when the Mongo Entity Settings aren't not found in any configuration provider. Check the entity name and the configuration files. /// /// [ExcludeFromCodeCoverage] [Serializable] public class MongoEntitySettingsDoesNotExistException : LiquidException { /// /// Initializes a new instance of the class. /// /// The entity name. public MongoEntitySettingsDoesNotExistException(string entityName) : base($"The Mongo Entity Settings for entity '{entityName}' does not exist.") { } /// public MongoEntitySettingsDoesNotExistException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Repository.Mongo/Exceptions/MongoException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.Mongo.Exceptions { /// /// Occurs when an exception has occurred in Mongo Db. /// /// [ExcludeFromCodeCoverage] public class MongoException : LiquidException { /// public MongoException() { } /// /// Initializes a new instance of the class. /// /// The inner exception. public MongoException(Exception innerException) : base("An error has occurred in database command. Please see inner exception", innerException) { } /// public MongoException(string message) : base(message) { } /// /// Initializes a new instance of the class. /// /// The message. /// The inner exception. public MongoException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.Repository.Mongo/Extensions/IMongoCollectionExtensions.cs ================================================ using MongoDB.Driver; using System; using System.Linq.Expressions; using System.Threading.Tasks; namespace Liquid.Repository.Mongo.Extensions { /// /// Extends methods. /// public static class IMongoCollectionExtensions { /// /// Inserts a single document. /// /// The type of the document. /// The collection. /// The document. /// The transaction session. public static async Task InsertOneAsync(this IMongoCollection collection, TDocument document, IClientSessionHandle session = null) { if (session is null) { await collection.InsertOneAsync(document); } else { await collection.InsertOneAsync(session, document); } } /// /// Finds the documents matching the filter. /// /// The type of the document. /// The collection. /// The filter. /// The transaction session. public static async Task> FindAsync(this IMongoCollection collection, FilterDefinition filter, IClientSessionHandle session = null) { if (session is null) { return await collection.FindAsync(filter, options: null, cancellationToken: default); } return await collection.FindAsync(session, filter); } /// /// Finds the documents matching the filter. /// /// The type of the document. /// The collection. /// The filter. /// The transaction session. public static async Task> FindAsync(this IMongoCollection collection, Expression> filter, IClientSessionHandle session = null) { if (session is null) { return await collection.FindAsync(filter, options: null, cancellationToken: default); } return await collection.FindAsync(session, filter); } /// /// Deletes a single document. /// /// The type of the document. /// The collection. /// The filter /// The transaction session public static async Task DeleteOneAsync(this IMongoCollection collection, Expression> filter, IClientSessionHandle session = null) { if (session is null) { return await collection.DeleteOneAsync(filter); } return await collection.DeleteOneAsync(session, filter); } /// /// Replaces a single document. /// /// The type of the document. /// The collection. /// The filter. /// The replacement. /// The options. /// The transaction session. public static async Task ReplaceOneAsync(this IMongoCollection collection, Expression> filter, TDocument replacement, ReplaceOptions options = null, IClientSessionHandle session = null) { if (session is null) { return await collection.ReplaceOneAsync(filter, replacement, options); } return await collection.ReplaceOneAsync(session, filter, replacement, options); } } } ================================================ FILE: src/Liquid.Repository.Mongo/Extensions/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Repository.Mongo.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Liquid.Repository.Mongo.Extensions { /// /// Mongo Db Service Collection Extensions Class. /// public static class IServiceCollectionExtensions { /// /// Registers a for the entity , /// and a if not previously registered. /// This method may also registers for MongoRepository instance. /// /// Type of entity that the repository should correspond to /// Entity identifier type. /// Extended ServiceCollection object. /// Name of the configuration section where all entities have their repository settings configured. /// Name of the collection in the database that the repository should correspond to. /// Specifies whether the telemetry should be activated or not for this repository. Default: True. public static IServiceCollection AddLiquidMongoRepository(this IServiceCollection services, string sectionName, string collectionName, bool activateTelemetry = true) where TEntity : LiquidEntity, new() { services.TryAddSingleton(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); services.AddScoped>((provider) => { return ActivatorUtilities.CreateInstance>(provider, collectionName); }); if (activateTelemetry) { services.AddScoped>(); services.AddScopedLiquidTelemetry, MongoRepository>(); } else { services.AddScoped, MongoRepository>(); } return services; } } } ================================================ FILE: src/Liquid.Repository.Mongo/IMongoClientFactory.cs ================================================ using Liquid.Core.Settings; using Liquid.Repository.Mongo.Settings; using MongoDB.Driver; namespace Liquid.Repository.Mongo { /// /// Provide client generator methods. /// public interface IMongoClientFactory { /// /// Provide a new instance of with db conection started. /// /// /// IMongoClient GetClient(string collectionName, out MongoEntitySettings settings); } } ================================================ FILE: src/Liquid.Repository.Mongo/IMongoDataContext.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Repository.Mongo.Settings; using MongoDB.Driver; namespace Liquid.Repository.Mongo { /// /// Mongo database context interface. /// /// public interface IMongoDataContext : ILiquidDataContext { /// /// Gets configurations set from attribute. /// MongoEntitySettings Settings { get; } /// /// Gets the Mongo Database. /// /// /// The client. /// IMongoDatabase Database { get; } /// /// Gets the mongo client. /// /// /// The mongo client. /// IMongoClient MongoClient { get; } /// /// Gets the mongo session handle. /// IClientSessionHandle ClientSessionHandle { get; } /// /// Sets an instance of into de property Database, /// which is obtained from MongoClient by database name.. /// /// The name of database. void SetDatabase(string databaseName); } } ================================================ FILE: src/Liquid.Repository.Mongo/Liquid.Repository.Mongo.csproj ================================================  net8.0 Liquid.Repository.Mongo MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full {D7B0D4F0-756B-4038-9DB2-0A1AD65ED72A} 1701;1702;1584;1658 True ================================================ FILE: src/Liquid.Repository.Mongo/MongoClientFactory.cs ================================================ using Liquid.Repository.Mongo.Exceptions; using Liquid.Repository.Mongo.Settings; using Microsoft.Extensions.Options; using MongoDB.Driver; using System; using System.Collections.Generic; using System.Linq; namespace Liquid.Repository.Mongo { /// public class MongoClientFactory : IMongoClientFactory { private readonly IOptions _settings; private readonly IDictionary _mongoClients; /// /// Initializes a new instance of the class. /// public MongoClientFactory(IOptions settings) { _mongoClients = new Dictionary(); _settings = settings ?? throw new ArgumentNullException(nameof(settings)); } /// public IMongoClient GetClient(string collectionName, out MongoEntitySettings settings) { if (collectionName is null) throw new ArgumentNullException(nameof(collectionName)); settings = _settings.Value.Settings.FirstOrDefault(x => x.CollectionName == collectionName); if (settings is null) throw new MongoEntitySettingsDoesNotExistException(collectionName); // Try to get from the created clients collection, otherwise creates a new client IMongoClient mongoClient = _mongoClients.TryGetValue(collectionName, out mongoClient) ? mongoClient : CreateClient(settings); return mongoClient; } private IMongoClient CreateClient(MongoEntitySettings databaseSettings) { var mongoClient = new MongoClient(databaseSettings.ConnectionString); _mongoClients.Add(databaseSettings.CollectionName, mongoClient); return mongoClient; } } } ================================================ FILE: src/Liquid.Repository.Mongo/MongoDataContext.cs ================================================ using Liquid.Repository.Mongo.Exceptions; using Liquid.Repository.Mongo.Settings; using MongoDB.Driver; using System; using System.Threading.Tasks; namespace Liquid.Repository.Mongo { /// /// Implements the Mongo data context for repositories. /// /// public class MongoDataContext : IMongoDataContext { private bool _disposed = false; private readonly IMongoClient _mongoClient; private IMongoDatabase _database; private IClientSessionHandle _clientSessionHandle; private readonly MongoEntitySettings _settings; /// /// Gets the Mongo Database. /// /// /// The client. /// public IMongoDatabase Database => _database; /// /// Gets the mongo client. /// /// /// The mongo client. /// public IMongoClient MongoClient => _mongoClient; /// /// Gets the mongo session handle. /// public IClientSessionHandle ClientSessionHandle => _clientSessionHandle; /// /// Gets the identifier of data context. /// /// /// The identifier. /// public string Id { get; } /// /// /// public MongoEntitySettings Settings => _settings; /// /// Initializes a new instance of the class. /// /// Mongo client generator. /// /// /// /// clientProvider /// or /// settingsFactory /// public MongoDataContext(IMongoClientFactory clientProvider, string collectionName) { if (clientProvider is null) throw new ArgumentNullException(nameof(clientProvider)); if (collectionName is null) throw new ArgumentNullException(nameof(collectionName)); _mongoClient = clientProvider.GetClient(collectionName, out _settings); if (_settings is null) throw new MongoEntitySettingsDoesNotExistException(nameof(TEntity)); SetDatabase(_settings.DatabaseName); } /// /// Starts the transaction of all data contexts in repositories inside UnitOfWork. /// public async Task StartTransactionAsync() { _clientSessionHandle = await _mongoClient.StartSessionAsync(); _clientSessionHandle.StartTransaction(); } /// /// Commits all commands added to the database context. /// public async Task CommitAsync() { await _clientSessionHandle.CommitTransactionAsync(); } /// /// Rollbacks the transactions. /// public async Task RollbackTransactionAsync() { await _clientSessionHandle.AbortTransactionAsync(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the allocated resources for this context. /// /// Indicates if method should perform dispose. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { if (_clientSessionHandle?.IsInTransaction == true) _clientSessionHandle.AbortTransaction(); _clientSessionHandle?.Dispose(); } _disposed = true; } /// public void SetDatabase(string databaseName) { _database = _mongoClient.GetDatabase(databaseName); } } } ================================================ FILE: src/Liquid.Repository.Mongo/MongoRepository.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Liquid.Repository.Mongo.Extensions; using Liquid.Repository.Mongo.Settings; using MongoDB.Bson; using MongoDB.Driver; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; namespace Liquid.Repository.Mongo { /// /// Mongo database repository class. Implements the interface to provide /// the repository pattern access to a Mongo Db document. Also provides a Mongo data context to extend Mongo client resources. /// /// The type of the entity. /// The type of the identifier. /// public class MongoRepository : ILiquidRepository where TEntity : LiquidEntity, new() { private readonly MongoEntitySettings _settings; /// public IMongoDataContext MongoDataContext { get; } /// public ILiquidDataContext DataContext => MongoDataContext; /// /// Initializes a new instance of the class. /// /// The data context. /// /// telemetryFactory /// or /// dataContext /// public MongoRepository(IMongoDataContext dataContext) { MongoDataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); _settings = dataContext.Settings; } /// public async Task AddAsync(TEntity entity) { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); await collection.InsertOneAsync(entity, MongoDataContext?.ClientSessionHandle); } /// public async Task> FindAllAsync() { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); var response = await collection.FindAsync(new BsonDocument(), MongoDataContext?.ClientSessionHandle); var returnValue = response.ToEnumerable(); return returnValue; } /// public async Task FindByIdAsync(TIdentifier id) { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); var result = await collection.FindAsync(e => e.Id.Equals(id), MongoDataContext?.ClientSessionHandle); var returnValue = result.SingleOrDefault(); return returnValue; } /// public async Task RemoveByIdAsync(TIdentifier id) { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); await collection.DeleteOneAsync(e => e.Id.Equals(id), MongoDataContext?.ClientSessionHandle); } /// public async Task UpdateAsync(TEntity entity) { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); await collection.ReplaceOneAsync(x => x.Id.Equals(entity.Id), entity, new ReplaceOptions { IsUpsert = true }, MongoDataContext?.ClientSessionHandle); } /// public async Task> WhereAsync(Expression> whereClause) { var collection = MongoDataContext.Database.GetCollection(_settings.CollectionName); var returnValue = (await collection.FindAsync(whereClause, MongoDataContext?.ClientSessionHandle)).ToEnumerable(); return returnValue; } } } ================================================ FILE: src/Liquid.Repository.Mongo/Settings/MongoEntitySettings.cs ================================================ using Liquid.Core.Settings; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.Mongo.Settings { /// /// Properties set list of service bus configurations. /// public class MongoDbSettings { /// /// Properties set list of service bus configurations. /// public List Settings { get; set; } } /// /// MongoDB repository data entity settings. /// [ExcludeFromCodeCoverage] public class MongoEntitySettings { /// public string CollectionName { get; set; } /// public string ShardKey { get; set; } /// /// Gets or sets the database connection string. /// /// /// The database connection string. /// public string ConnectionString { get; set; } /// /// Gets or sets the name of the database. /// /// /// The name of the database. /// public string DatabaseName { get; set; } } } ================================================ FILE: src/Liquid.Repository.OData/Extensions/IServiceCollectionExtension.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Repository.OData.Extensions { /// /// Extension methods for IServiceCollection. /// public static class IServiceCollectionExtensions { /// /// Registers a for the entity , /// and a if not previously registered. /// /// Type of entity that the repository should correspond to /// Entity identifier type. /// Extended ServiceCollection object. /// Name of the configuration section where all entities have their repository settings configured. /// Name of the entity in the database that the repository should correspond to. public static IServiceCollection AddLiquidOdataRepository(this IServiceCollection services, string sectionName, string entityName) where TEntity : LiquidEntity, new() { services.TryAddSingleton(); services.TryAddSingleton(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName).Bind(settings); }); services.AddScoped>((provider) => { return ActivatorUtilities.CreateInstance>(provider, entityName); }); return services; } } } ================================================ FILE: src/Liquid.Repository.OData/IODataClientFactory.cs ================================================ using Simple.OData.Client; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Repository.OData { /// /// Defines an object with the ability to create an OData client. /// public interface IODataClientFactory { /// /// Create an OData client. /// /// The entity name. IODataClient CreateODataClientAsync(string entityName); } } ================================================ FILE: src/Liquid.Repository.OData/Liquid.Repository.OData.csproj ================================================  net8.0 enable enable Liquid.Repository.OData enable MIT Avanade Brazil Avanade Inc. Liquid Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full Liquid Repository adapter for Odata apis. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.Repository.OData/ODataClientFactory.cs ================================================ using Liquid.Core.Interfaces; using Microsoft.Extensions.Options; using Simple.OData.Client; namespace Liquid.Repository.OData { /// public class ODataClientFactory : IODataClientFactory { private readonly IOptions _options; private readonly ILiquidContext _context; /// /// Initialize a new instance of /// /// /// public ODataClientFactory(IOptions options, ILiquidContext context) { _options = options ?? throw new ArgumentNullException(nameof(options)); _context = context ?? throw new ArgumentNullException(nameof(context)); } /// public IODataClient CreateODataClientAsync(string entityName) { var hasToken = _context.current.ContainsKey("OdataToken"); var token = _context.Get("OdataToken")?.ToString(); if (!hasToken || string.IsNullOrEmpty(token)) { throw new KeyNotFoundException("Token is required to perform this operation. The 'OdataToken' " + "key was not found in the context."); } var settings = _options?.Value?.Settings.FirstOrDefault(x => x.EntityName == entityName); if (settings == null) throw new ArgumentOutOfRangeException(nameof(entityName)); var client = new ODataClient(GetODataSettings(settings, token)); return client; } /// ///Initialize a new instance of /// /// OData settings. /// Authorization token. /// private static ODataClientSettings GetODataSettings(ODataSettings? settings, string token) { var odataSettings = new ODataClientSettings(new Uri(settings.BaseUrl)); odataSettings.BeforeRequest = (message) => { message.Headers.Add("Authorization", token); }; if (!settings.ValidateCert) { var handler = new HttpClientHandler(); odataSettings.OnApplyClientHandler = (handler) => { handler.ServerCertificateCustomValidationCallback += (sender, certificate, chain, errors) => { return true; }; }; } return odataSettings; } } } ================================================ FILE: src/Liquid.Repository.OData/ODataRepository.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System.Linq.Expressions; namespace Liquid.Repository.OData { /// public class ODataRepository : ILiquidRepository where TEntity : LiquidEntity, new() { /// public ILiquidDataContext DataContext => throw new NotImplementedException(); private readonly IODataClientFactory _clientFactory; private readonly string _entityName; /// /// Initialize a new instance of /// /// Factory to create OData client. /// Name of the entity to be used in the repository. /// public ODataRepository(IODataClientFactory clientFactory, string entityName) { _clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); _entityName = entityName ?? throw new ArgumentNullException(nameof(entityName)); } /// public async Task AddAsync(TEntity entity) { var client = _clientFactory.CreateODataClientAsync(_entityName); await client.For().Set(entity).InsertEntryAsync(); } /// public async Task> FindAllAsync() { var client = _clientFactory.CreateODataClientAsync(_entityName); return await client.For().FindEntriesAsync(); } /// public async Task FindByIdAsync(TIdentifier id) { var client = _clientFactory.CreateODataClientAsync(_entityName); return await client.For().Key(id).FindEntryAsync(); } /// public async Task RemoveByIdAsync(TIdentifier id) { var client = _clientFactory.CreateODataClientAsync(_entityName); await client.For().Key(id).DeleteEntryAsync(); } /// public async Task UpdateAsync(TEntity entity) { var client = _clientFactory.CreateODataClientAsync(_entityName); await client.For().Set(entity).UpdateEntryAsync(); } /// public async Task> WhereAsync(Expression> whereClause) { var client = _clientFactory.CreateODataClientAsync(_entityName); return await client.For().Filter(whereClause).FindEntriesAsync(); } } } ================================================ FILE: src/Liquid.Repository.OData/ODataSettings.cs ================================================ namespace Liquid.Repository.OData { /// /// Odata configurations set. /// public class ODataSettings { /// /// Name of entity to be configured. /// public string EntityName { get; set; } /// /// Base URL of the Odata service. /// public string BaseUrl { get; set; } /// /// Indicates if the Odata service requires certificate validations. /// public bool ValidateCert { get; set; } = false; } /// /// Odata configuration options. /// public class ODataOptions { /// /// List of Odata options set. /// public List Settings { get; set; } } } ================================================ FILE: src/Liquid.Storage.AzureStorage/BlobClientFactory.cs ================================================ using Azure.Storage.Blobs; using Microsoft.Extensions.Options; namespace Liquid.Storage.AzureStorage { /// public class BlobClientFactory : IBlobClientFactory { private readonly StorageSettings _options; private readonly List _clients = new List(); /// public IList Clients => _clients; /// /// Inicialize a new instance of /// /// Configurations set. /// public BlobClientFactory(IOptions? options) { _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); } /// public void SetContainerClients() { if (_options.Containers.Count == 0) throw new ArgumentNullException(nameof(_options)); foreach (var container in _options.Containers) { var client = new BlobContainerClient(container.ConnectionString, container.ContainerName); _clients.Add(client); } } /// public BlobContainerClient GetContainerClient(string containerName) { var client = _clients.FirstOrDefault(x => x.Name == containerName); if (client == null) { throw new ArgumentException($"Container named {containerName} not found."); } return client; } } } ================================================ FILE: src/Liquid.Storage.AzureStorage/Extensions/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Diagnostics.CodeAnalysis; namespace Liquid.Storage.AzureStorage.Extensions { /// /// Extension methods of /// for register Liquid Azure Storage services. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionExtensions { /// /// Registers service, it's dependency /// , and also set configuration /// option . /// /// service collection instance. /// configuration section of storage settings. public static IServiceCollection AddLiquidAzureStorageAdapter(this IServiceCollection services, string configSection) { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(configSection).Bind(settings); }); services.AddSingleton(); services.AddScoped(); return services; } } } ================================================ FILE: src/Liquid.Storage.AzureStorage/IBlobClientFactory.cs ================================================ using Azure.Storage.Blobs; namespace Liquid.Storage.AzureStorage { /// /// instances factory. /// public interface IBlobClientFactory { /// /// List of instances of . /// IList Clients { get; } /// /// Initialize an instance of /// for each container on the and /// add to . /// void SetContainerClients(); /// /// Get an instance of /// by name. /// /// /// BlobContainerClient GetContainerClient(string containerName); } } ================================================ FILE: src/Liquid.Storage.AzureStorage/Liquid.Storage.AzureStorage.csproj ================================================  net8.0 enable enable Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 8.0.0 true true Adapter for Microsoft Azure Storage integrations. This component is part of Liquid Application Framework. logo.png https://github.com/Avanade/Liquid-Application-Framework True \ ================================================ FILE: src/Liquid.Storage.AzureStorage/LiquidStorageAzure.cs ================================================ using Azure; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Azure.Storage.Sas; using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System.Text; namespace Liquid.Storage.AzureStorage { /// public class LiquidStorageAzure : ILiquidStorage { private readonly IBlobClientFactory _factory; /// /// Initialize a new instance of /// /// /// public LiquidStorageAzure(IBlobClientFactory factory) { _factory = factory ?? throw new ArgumentNullException(nameof(factory)); _factory.SetContainerClients(); } /// public async Task DeleteByTags(IDictionary tags, string containerName) { var client = _factory.GetContainerClient(containerName); var stringFilterBd = new StringBuilder(); foreach (var tag in tags) { stringFilterBd.Append(@$"""{tag.Key}"" = '{tag.Value}' AND "); } var stringFilter = stringFilterBd.ToString(); stringFilter = stringFilter.Substring(0, stringFilter.Length - 4); await foreach (TaggedBlobItem blobItem in client.FindBlobsByTagsAsync(stringFilter)) { var blockBlob = client.GetBlockBlobClient(blobItem.BlobName); await blockBlob.DeleteAsync(); } } /// public async Task> GetAllBlobs(string containerName) { var client = _factory.GetContainerClient(containerName); var results = new List(); await foreach (var blobItem in client.GetBlobsAsync()) { var blockBlob = client.GetBlockBlobClient(blobItem.Name); var blob = await blockBlob.DownloadContentAsync(); var blobTags = await blockBlob.GetTagsAsync(); var item = new LiquidBlob { Blob = blob.Value.Content.ToArray(), Tags = blobTags?.Value?.Tags, Name = blobItem.Name, AbsoluteUri = blockBlob.Uri.AbsoluteUri }; results.Add(item); } return results; } /// public async Task Delete(string id, string containerName) { var client = _factory.GetContainerClient(containerName); var blobClient = client.GetBlobClient(id); await blobClient.DeleteAsync(); } /// public async Task> ReadBlobsByTags(IDictionary tags, string containerName) { var client = _factory.GetContainerClient(containerName); var stringFilterBd = new StringBuilder(); foreach (var tag in tags) { stringFilterBd.Append(@$"""{tag.Key}"" = '{tag.Value}' AND "); } var stringFilter = stringFilterBd.ToString(); stringFilter = stringFilter.Substring(0, stringFilter.Length - 4); var results = new List(); await foreach (TaggedBlobItem blobItem in client.FindBlobsByTagsAsync(stringFilter)) { var blockBlob = client.GetBlockBlobClient(blobItem.BlobName); var blob = await blockBlob.DownloadContentAsync(); var blobTags = await blockBlob.GetTagsAsync(); var item = new LiquidBlob { Blob = blob.Value.Content.ToArray(), Tags = blobTags?.Value?.Tags, Name = blobItem.BlobName, AbsoluteUri = blockBlob.Uri.AbsoluteUri }; results.Add(item); } return results; } /// public async Task UploadBlob(byte[] data, string name, string containerName, IDictionary? tags = null) { var client = _factory.GetContainerClient(containerName); var blockBlob = client.GetBlockBlobClient(name); var options = new BlobUploadOptions() { Tags = tags }; await blockBlob.UploadAsync(new MemoryStream(data), options); return blockBlob.Uri.AbsoluteUri; } /// public async Task ReadBlobsByName(string blobName, string containerName) { try { var client = _factory.GetContainerClient(containerName); var blockBlob = client.GetBlockBlobClient(blobName); var blob = await blockBlob.DownloadContentAsync(); var tags = await blockBlob.GetTagsAsync(); var item = new LiquidBlob { Blob = blob.Value.Content.ToArray(), Tags = tags?.Value?.Tags, Name = blobName, AbsoluteUri = blockBlob.Uri.AbsoluteUri }; return item; } catch (RequestFailedException storageRequestFailedException) when (storageRequestFailedException.ErrorCode == BlobErrorCode.BlobNotFound) { return null; } catch (Exception ex) { throw new ArgumentOutOfRangeException("Error reading blob", ex); } } /// public string? GetBlobSasUri(string blobName, string containerName, DateTimeOffset expiresOn, string permissions) { var blobClient = _factory.GetContainerClient(containerName); var blockBlob = blobClient.GetBlockBlobClient(blobName); if (!blobClient.CanGenerateSasUri) { return null; } var sasBuilder = new BlobSasBuilder() { BlobContainerName = blobClient.Name, BlobName = blobName, Resource = "b", }; sasBuilder.ExpiresOn = expiresOn; sasBuilder.SetPermissions(permissions); var sasURI = blockBlob.GenerateSasUri(sasBuilder); return sasURI.AbsoluteUri; } } } ================================================ FILE: src/Liquid.Storage.AzureStorage/StorageSettings.cs ================================================ namespace Liquid.Storage.AzureStorage { /// /// Set of Azure Storage containers configs. /// public class StorageSettings { /// /// List of container settings. /// public List Containers { get; set; } = new List(); } /// /// Set of a container connection configuration. /// public class ContainerSettings { /// /// A connection string includes the authentication information /// required for your application to access data in an Azure Storage /// account at runtime. /// public string ConnectionString { get; set; } /// /// The name of the blob container in the storage account to reference. /// public string ContainerName { get; set; } } } ================================================ FILE: src/Liquid.WebApi.Http/Attributes/SwaggerAuthorizationHeaderAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Attributes { /// /// Swagger authorization header attribute class. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] [ExcludeFromCodeCoverage] public class SwaggerAuthorizationHeaderAttribute : SwaggerBaseHeaderAttribute { /// /// Initializes a new instance of the class. /// public SwaggerAuthorizationHeaderAttribute() : base("Authorization", true, "Example: bearer {your token}") { } } } ================================================ FILE: src/Liquid.WebApi.Http/Attributes/SwaggerBaseHeaderAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Attributes { /// /// Swagger base parameter attribute class. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] [ExcludeFromCodeCoverage] public abstract class SwaggerBaseHeaderAttribute : Attribute { /// /// Gets the name. /// /// /// The name. /// public string Name { get; } /// /// Gets a value indicating whether this is required. /// /// /// true if required; otherwise, false. /// public bool Required { get; } /// /// Gets the description. /// /// /// The description. /// public string Description { get; } /// /// Initializes a new instance of the class. /// /// The name. /// if set to true [required]. /// The description. protected SwaggerBaseHeaderAttribute(string name, bool required = false, string description = "") { Name = name; Required = required; Description = description; } } } ================================================ FILE: src/Liquid.WebApi.Http/Attributes/SwaggerChannelHeaderAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Attributes { /// /// Adds a "Channel" header to Swagger methods. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] [ExcludeFromCodeCoverage] public class SwaggerChannelHeaderAttribute : SwaggerBaseHeaderAttribute { /// /// Initializes a new instance of the class. /// public SwaggerChannelHeaderAttribute() : base("Channel", false, "Example: ios, android, web. default value: web") { } } } ================================================ FILE: src/Liquid.WebApi.Http/Attributes/SwaggerCultureHeaderAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Attributes { /// /// Culture header attribute class. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] [ExcludeFromCodeCoverage] public class SwaggerCultureHeaderAttribute : SwaggerBaseHeaderAttribute { /// /// Initializes a new instance of the class. /// public SwaggerCultureHeaderAttribute() : base("Culture", false, "Example: pt-BR, en-US") { } } } ================================================ FILE: src/Liquid.WebApi.Http/Attributes/SwaggerCustomHeaderAttribute.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Attributes { /// /// Swagger header base attribute class. Used to populate custom headers in Swagger. /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] [ExcludeFromCodeCoverage] public class SwaggerCustomHeaderAttribute : SwaggerBaseHeaderAttribute { /// /// Initializes a new instance of the class. /// /// The name. /// if set to true [required]. /// The description. public SwaggerCustomHeaderAttribute(string name, bool required = false, string description = "") : base(name, required, description) { } } } ================================================ FILE: src/Liquid.WebApi.Http/Entities/LiquidErrorResponse.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; namespace Liquid.WebApi.Http.Entities { /// /// Properties for response request that throws unexpected error. /// [ExcludeFromCodeCoverage] public class LiquidErrorResponse { /// /// Status code of error. /// public int StatusCode { get; set; } /// /// Error message. /// public string Message { get; set; } /// /// Detailed exception. /// public Exception Detailed { get; set; } } } ================================================ FILE: src/Liquid.WebApi.Http/Exceptions/LiquidContextKeysException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.WebApi.Http.Exceptions { /// [ExcludeFromCodeCoverage] [Serializable] public class LiquidContextKeysException : LiquidException { /// public LiquidContextKeysException() { } /// public LiquidContextKeysException(string contextKey) : base($"The value of required context key '{contextKey}' was not found in request.") { } /// public LiquidContextKeysException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.WebApi.Http/Exceptions/LiquidScopedKeysException.cs ================================================ using Liquid.Core.Exceptions; using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; namespace Liquid.WebApi.Http.Exceptions { /// [ExcludeFromCodeCoverage] [Serializable] public class LiquidScopedtKeysException : LiquidException { /// public LiquidScopedtKeysException() { } /// public LiquidScopedtKeysException(string contextKey) : base($"The value of required logging scoped key '{contextKey}' was not found in request.") { } /// public LiquidScopedtKeysException(string message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: src/Liquid.WebApi.Http/Extensions/DependencyInjection/IApplicationBuilderExtensions.cs ================================================ using Liquid.WebApi.Http.Middlewares; using Liquid.WebApi.Http.Settings; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Extensions.DependencyInjection { /// /// .Net application builder extensions class. /// [ExcludeFromCodeCoverage] public static class IApplicationBuilderExtensions { /// /// Adds to the application builder. /// /// Extended application builder. public static IApplicationBuilder UseLiquidCulture(this IApplicationBuilder builder) { return builder.UseMiddleware(); } /// /// Adds to the application builder. /// /// Extended application builder. public static IApplicationBuilder UseLiquidException(this IApplicationBuilder builder) { return builder.UseMiddleware(); } /// /// Adds to the application builder. /// /// Extended application builder. public static IApplicationBuilder UseLiquidContext(this IApplicationBuilder builder) { return builder.UseMiddleware(); } /// /// Adds to the application builder. /// /// Extended application builder. public static IApplicationBuilder UseLiquidScopedLogging(this IApplicationBuilder builder) { return builder.UseMiddleware(); } /// /// Register the Swagger middleware with Liquid Configuration settings. /// /// Extended application builder. public static IApplicationBuilder UseLiquidSwagger(this IApplicationBuilder builder) { var configuration = builder.ApplicationServices.GetService>(); var swaggerSettings = configuration.Value; builder.UseSwagger().UseSwaggerUI(options => { options.SwaggerEndpoint(swaggerSettings.SwaggerEndpoint.Url, swaggerSettings.SwaggerEndpoint.Name); }); return builder; } /// /// Groups the execution of , /// , , /// and /// in this particular order, to add all Liquid functionality to the.net pipeline. /// /// Extended application builder. public static IApplicationBuilder UseLiquidConfigure(this IApplicationBuilder builder) { builder.UseLiquidCulture(); builder.UseLiquidScopedLogging(); builder.UseLiquidContext(); builder.UseLiquidSwagger(); builder.UseLiquidException(); return builder; } } } ================================================ FILE: src/Liquid.WebApi.Http/Extensions/DependencyInjection/IServiceCollectionExtensions.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Extensions; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.WebApi.Http.Filters.Swagger; using Liquid.WebApi.Http.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; namespace Liquid.WebApi.Http.Extensions.DependencyInjection { /// /// Startup extension methods. Used to configure the startup application. /// [ExcludeFromCodeCoverage] public static class IServiceCollectionExtensions { /// /// Registers a service and execute registration methods /// set mapping , /// register domain handlers , /// and swagger /// /// Extended service collection instance. /// Array of assemblies that the domain handlers are implemented. /// Swagger configuration section name. /// Indicates if middlewares options must be binded. public static IServiceCollection AddLiquidHttp(this IServiceCollection services, string sectionName, bool middlewares = false, params Assembly[] assemblies) { if (middlewares) { services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName + ":ScopedContext").Bind(settings); }); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName + ":Culture").Bind(settings); }); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName + ":ScopedLogging").Bind(settings); }); } services.AddScoped(); services.AddLiquidSerializers(); services.AddOptions() .Configure((settings, configuration) => { configuration.GetSection(sectionName + ":Swagger").Bind(settings); }); services.LiquidAddAutoMapper(assemblies); services.AddLiquidHandlers(true, true, assemblies); services.AddLiquidSwagger(); return services; } /// /// Adds swagger with liquid configuration and /// filters , /// and . /// /// Extended service collection instance. public static IServiceCollection AddLiquidSwagger(this IServiceCollection services) { var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetService>(); if (configuration?.Value == null) throw new LiquidException("'swagger' settings does not exist in appsettings.json file. Please check the file."); var swaggerSettings = configuration.Value; services.AddSwaggerGen(options => { options.SwaggerDoc(swaggerSettings.Name, new OpenApiInfo { Title = swaggerSettings.Title, Version = swaggerSettings.Version, Description = swaggerSettings.Description }); options.OperationFilter(); options.OperationFilter(); options.OperationFilter(); Directory.GetFiles(AppContext.BaseDirectory, "*.xml").AsEnumerable().Each(file => options.IncludeXmlComments(file)); options.CustomSchemaIds(x => x.FullName); }); return services; } } } ================================================ FILE: src/Liquid.WebApi.Http/Extensions/HttpContextExtensions.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Http; namespace Liquid.WebApi.Http.Extensions { /// /// Http context extensions class. /// [ExcludeFromCodeCoverage] public static class HttpContextExtensions { /// /// Gets the header value from request. /// /// The http context. /// The header key. /// Header value from http request, otherwise . public static string GetValueFromHeader(this HttpContext context, string key) { IHeaderDictionary headerDictionary = null; if (context != null) { headerDictionary = context.Request?.Headers; } if (headerDictionary == null) { return string.Empty; } var stringValues = headerDictionary.FirstOrDefault(m => string.Equals(m.Key, key, StringComparison.InvariantCultureIgnoreCase)).Value; if (string.IsNullOrEmpty(stringValues)) { return string.Empty; } return stringValues; } /// /// Gets the value from querystring. /// /// The request. /// The key. /// public static string GetValueFromQuery(this HttpContext context, string key) { var queryCollection = context.Request?.Query; if (queryCollection == null) return string.Empty; var stringValues = queryCollection.FirstOrDefault(m => string.Equals(m.Key, key, StringComparison.InvariantCultureIgnoreCase)).Value; if (string.IsNullOrEmpty(stringValues)) return string.Empty; return stringValues; } } } ================================================ FILE: src/Liquid.WebApi.Http/Filters/Swagger/AddHeaderParameterFilter.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Liquid.WebApi.Http.Attributes; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Liquid.WebApi.Http.Filters.Swagger { /// /// Adds headers to swagger document if the action has custom swagger attributes. /// /// [ExcludeFromCodeCoverage] internal class AddHeaderParameterFilter : IOperationFilter { /// /// Applies the specified operation. /// /// The operation. /// The context. public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (!context.ApiDescription.TryGetMethodInfo(out var methodInfo)) return; var controllerAttributes = methodInfo?.DeclaringType?.GetCustomAttributes().ToList(); var actionAttributes = methodInfo?.GetCustomAttributes().ToList(); controllerAttributes?.AddRange(actionAttributes); var swaggerAttributes = controllerAttributes?.Distinct(); if (swaggerAttributes == null) return; foreach (var swaggerAttribute in swaggerAttributes) { if (operation.Parameters == null) { operation.Parameters = new List(); } if (!operation.Parameters.Any(p => p.Name.Equals(swaggerAttribute.Name, StringComparison.InvariantCultureIgnoreCase))) { operation.Parameters.Add(new OpenApiParameter { Name = swaggerAttribute.Name, In = ParameterLocation.Header, Required = swaggerAttribute.Required, Description = swaggerAttribute.Description }); } } } } } ================================================ FILE: src/Liquid.WebApi.Http/Filters/Swagger/DefaultResponseFilter.cs ================================================ using System.Diagnostics.CodeAnalysis; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Liquid.WebApi.Http.Filters.Swagger { /// /// Sets the default responses for each action call. /// /// [ExcludeFromCodeCoverage] internal sealed class DefaultResponseFilter : IOperationFilter { /// /// Adds the defaults headers to all responses. /// /// The operation. /// The context. public void Apply(OpenApiOperation operation, OperationFilterContext context) { operation.Responses.Add("400", new OpenApiResponse { Description = "The request is invalid." }); operation.Responses.Add("404", new OpenApiResponse { Description = "Resource not found." }); operation.Responses.Add("500", new OpenApiResponse { Description = "An internal server error has occurred." }); } } } ================================================ FILE: src/Liquid.WebApi.Http/Filters/Swagger/DocumentSortFilter.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Linq; using Liquid.Core.Extensions; using Liquid.Core.Utils; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Liquid.WebApi.Http.Filters.Swagger { /// /// The Swagger document custom sort filter class. /// /// [ExcludeFromCodeCoverage] internal sealed class DocumentSortFilter : IDocumentFilter { /// /// Applies the specified swagger document. /// /// The swagger document. /// The context. public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var paths = swaggerDoc.Paths.OrderBy(e => e.Key); swaggerDoc.Paths = new OpenApiPaths(); paths.AsEnumerable().Each(path => swaggerDoc.Paths.Add(path.Key, path.Value)); } } } ================================================ FILE: src/Liquid.WebApi.Http/Filters/Swagger/OverloadMethodsSameVerb.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text; using Liquid.Core.Extensions; using Liquid.Core.Utils; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; namespace Liquid.WebApi.Http.Filters.Swagger { /// /// Adds the parameters to the method to avoid same method name. /// /// [ExcludeFromCodeCoverage] internal sealed class OverloadMethodsSameVerb : IOperationFilter { /// /// Changes the verbs by concatenating the parameters and verbs. /// /// The operation. /// The context. public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (operation.Parameters == null) return; var builder = new StringBuilder($"{context?.ApiDescription?.HttpMethod}_{context?.MethodInfo.Name}_{context?.ApiDescription?.RelativePath?.Replace("/", "_")}_{operation.OperationId}By"); operation.Parameters.AsEnumerable().Each(parameter => builder.Append(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(parameter.Name))); operation.OperationId = builder.ToString(); } } } ================================================ FILE: src/Liquid.WebApi.Http/Implementations/LiquidControllerBase.cs ================================================ using MediatR; using Microsoft.AspNetCore.Mvc; using System; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Threading.Tasks; namespace Liquid.WebApi.Http.Controllers { /// /// Base Controller Class. /// /// public abstract class LiquidControllerBase : ControllerBase { /// /// Gets or sets the mediator service. /// /// /// The mediator service. /// [ExcludeFromCodeCoverage] protected IMediator Mediator { get; } /// /// Initializes a new instance of the class. /// /// The mediator service. protected LiquidControllerBase(IMediator mediator) { Mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); } /// /// Executes the action . /// /// The request command or query. protected virtual async Task ExecuteAsync(IRequest request) { return await Mediator.Send(request); } /// /// Executes the action and returns the response using a custom http response code. /// /// The type of the request. /// The request command or query. /// The http response code. protected virtual async Task ExecuteAsync(IRequest request, HttpStatusCode responseCode) { var response = await Mediator.Send(request); return StatusCode((int)responseCode, response); } } } ================================================ FILE: src/Liquid.WebApi.Http/Implementations/LiquidNotificationHelper.cs ================================================ using Liquid.Core.Interfaces; using Liquid.WebApi.Http.Interfaces; using System; namespace Liquid.WebApi.Http.Implementations { /// public class LiquidNotificationHelper : ILiquidNotificationHelper { private readonly ILiquidContextNotifications _contextNotifications; /// /// Inicialize a new instace of /// /// public LiquidNotificationHelper(ILiquidContextNotifications contextNotifications) { _contextNotifications = contextNotifications ?? throw new ArgumentNullException(nameof(contextNotifications)); } /// public object IncludeMessages(TResponse response) { var messages = _contextNotifications.GetNotifications(); if (messages is null) return response; return new { response, messages }; } } } ================================================ FILE: src/Liquid.WebApi.Http/Interfaces/ILiquidNotificationHelper.cs ================================================ namespace Liquid.WebApi.Http.Interfaces { /// /// Abstracts the management of context notifications. /// public interface ILiquidNotificationHelper { /// /// Add context notification messages to the response object. /// /// Type of response object obtained upon return of a request. /// Object obtained upon return of a request. object IncludeMessages(TResponse response); } } ================================================ FILE: src/Liquid.WebApi.Http/Liquid.WebApi.Http.csproj ================================================  net8.0 Liquid.WebApi.Http MIT Avanade Brazil Avanade Inc. Liquid - Modern Application Framework Avanade 2019 https://github.com/Avanade/Liquid-Application-Framework logo.png 8.0.0 true true Full {25D05D84-4DFB-4F3D-92A2-B06DFA244BA4} The Liquid.WebApi.Http contains modules and functionalities to provide a better way to configure and use a web api project using asp.net core 3.1. This components already register some dependencies, sets custom middlewares, configure swagger and so on. This component is part of Liquid Application Framework. True ================================================ FILE: src/Liquid.WebApi.Http/Middlewares/LiquidContextMiddleware.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.WebApi.Http.Exceptions; using Liquid.WebApi.Http.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading.Tasks; namespace Liquid.WebApi.Http.Middlewares { /// /// Inserts configured context keys in LiquidContext service. /// Includes its behavior in netcore pipelines before request execution. /// [ExcludeFromCodeCoverage] public class LiquidContextMiddleware { private readonly RequestDelegate _next; private readonly IOptions _options; /// /// Initialize a instance of /// /// Invoked request. /// Context keys configuration. public LiquidContextMiddleware(RequestDelegate next, IOptions options) { _next = next; _options = options; } /// /// Obtains the value of configured keys from HttpContext and inserts them in LiquidContext service. /// Includes its behavior in netcore pipelines before request execution. /// /// HTTP-specific information about an individual HTTP request. public async Task InvokeAsync(HttpContext context) { ILiquidContext liquidContext = context.RequestServices.GetRequiredService(); var value = string.Empty; foreach (var key in _options.Value.Keys) { value = context.GetValueFromHeader(key.KeyName); if (string.IsNullOrEmpty(value)) value = context.GetValueFromQuery(key.KeyName); if (string.IsNullOrEmpty(value) && key.Required) throw new LiquidContextKeysException(key.KeyName); liquidContext.Upsert(key.KeyName, value); } if (_options.Value.Culture) { liquidContext.Upsert("culture", CultureInfo.CurrentCulture.Name); } await _next(context); } } } ================================================ FILE: src/Liquid.WebApi.Http/Middlewares/LiquidCultureMiddleware.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.WebApi.Http.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading.Tasks; namespace Liquid.WebApi.Http.Middlewares { /// /// Configures the culture in the current thread. /// Includes its behavior in netcore pipelines before request execution. /// [ExcludeFromCodeCoverage] public sealed class LiquidCultureMiddleware { private const string _culture = "culture"; private readonly IOptions _options; private readonly RequestDelegate _next; /// /// Initializes a new instance of the class. /// /// The next. /// public LiquidCultureMiddleware(RequestDelegate next, IOptions options) { _next = next; _options = options; } /// /// Configures the culture in the current thread as set on request or in appsettings, prioritizing request informations. /// Includes its behavior in netcore pipelines before request execution. /// /// HTTP-specific information about an individual HTTP request. public async Task InvokeAsync(HttpContext context) { var cultureCode = context.GetValueFromHeader(_culture).ToString(); if (string.IsNullOrEmpty(cultureCode)) { cultureCode = context.GetValueFromQuery(_culture).ToString(); } if (string.IsNullOrEmpty(cultureCode) && !string.IsNullOrEmpty(_options.Value.DefaultCulture)) { cultureCode = _options.Value.DefaultCulture; } if (!string.IsNullOrEmpty(cultureCode)) { CultureInfo.CurrentCulture = new CultureInfo(cultureCode); CultureInfo.CurrentUICulture = new CultureInfo(cultureCode); } await _next(context); } } } ================================================ FILE: src/Liquid.WebApi.Http/Middlewares/LiquidExceptionMiddleware.cs ================================================ using FluentValidation; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.WebApi.Http.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Threading.Tasks; namespace Liquid.WebApi.Http.Middlewares { /// /// Generates a log and serialize a formated Json response object for generic exceptions. /// Includes its behavior in netcore pipelines after request execution when thows error. /// [ExcludeFromCodeCoverage] public class LiquidExceptionMiddleware { private readonly ILogger _logger; private readonly RequestDelegate _next; private readonly ILiquidSerializerProvider _serializerProvider; /// /// Initialize a new instance of /// /// /// /// public LiquidExceptionMiddleware(RequestDelegate next , ILogger logger , ILiquidSerializerProvider serializerProvider) { _logger = logger; _next = next; _serializerProvider = serializerProvider; } /// /// Generates a log and serialize a formated Json response object for exceptions. /// Includes its behavior in netcore pipelines after request execution when thows error. /// /// HTTP-specific information about an individual HTTP request. /// public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (ValidationException ex) { _logger.LogError(ex, $"Liquid request validation error: {ex}"); await HandleExceptionAsync(context, ex, HttpStatusCode.BadRequest); } catch (Exception ex) { _logger.LogError(ex, $"Unexpected error: {ex}"); await HandleExceptionAsync(context, ex, HttpStatusCode.InternalServerError); } } private Task HandleExceptionAsync(HttpContext context, Exception exception, HttpStatusCode statusCode) { context.Response.ContentType = context.Request.ContentType; context.Response.StatusCode = (int)statusCode; var response = new LiquidErrorResponse() { StatusCode = context.Response.StatusCode, Message = "An error occurred whilst processing your request.", Detailed = exception }; var serializer = GetSerializer(context.Request.ContentType); return context.Response.WriteAsync(serializer.Serialize(response)); } private ILiquidSerializer GetSerializer(string contentType) { ILiquidSerializer serializer; switch (contentType) { case "application/json": serializer = _serializerProvider.GetSerializerByType(typeof(LiquidJsonSerializer)); break; case "application/xml": serializer = _serializerProvider.GetSerializerByType(typeof(LiquidXmlSerializer)); break; default: serializer = _serializerProvider.GetSerializerByType(typeof(LiquidJsonSerializer)); break; } return serializer; } } } ================================================ FILE: src/Liquid.WebApi.Http/Middlewares/LiquidScopedLoggingMiddleware.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.WebApi.Http.Exceptions; using Liquid.WebApi.Http.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Liquid.WebApi.Http.Middlewares { /// /// Inserts configured context keys in ILogger service scope. /// Includes its behavior in netcore pipelines before request execution. /// [ExcludeFromCodeCoverage] public class LiquidScopedLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IOptions _options; /// /// Initialize a new instance of /// /// Invoked request. /// Logger service instance. /// Context keys set. public LiquidScopedLoggingMiddleware(RequestDelegate next, ILogger logger, IOptions options) { _next = next; _logger = logger; _options = options; } /// /// Obtains the value of configured keys from HttpContext and inserts them in ILogger service scope. /// Includes its behavior in netcore pipelines before request execution. /// /// HTTP-specific information about an individual HTTP request. public async Task InvokeAsync(HttpContext context) { var scope = new List>(); foreach (var key in _options.Value.Keys) { var value = context.GetValueFromHeader(key.KeyName); if (string.IsNullOrEmpty(value)) value = context.GetValueFromQuery(key.KeyName); if (string.IsNullOrEmpty(value) && key.Required) throw new LiquidScopedtKeysException(key.KeyName); scope.Add(new KeyValuePair(key.KeyName, value)); } using (_logger.BeginScope(scope.ToArray())) { await _next(context); } } } } ================================================ FILE: src/Liquid.WebApi.Http/Settings/SwaggerSettings.cs ================================================ using Liquid.Core.Attributes; using System.Text.Json.Serialization; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.Settings { /// /// Swagger Configuration Settings Class. /// [ExcludeFromCodeCoverage] [LiquidSectionName("liquid:swagger")] public class SwaggerSettings { /// /// Gets or sets the name. /// /// /// The name. /// [JsonPropertyName("name")] public string Name { get; set; } /// /// Gets or sets the host. /// /// /// The host. /// [JsonPropertyName("host")] public string Host { get; set; } /// /// Gets or sets schemes. /// /// /// Schemes. /// [JsonPropertyName("schemes")] public string[] Schemes { get; set; } /// /// Gets or sets the title. /// /// /// The title. /// [JsonPropertyName("title")] public string Title { get; set; } /// /// Gets or sets the version. /// /// /// The version. /// [JsonPropertyName("version")] public string Version { get; set; } /// /// Gets or sets the description. /// /// /// The description. /// [JsonPropertyName("description")] public string Description { get; set; } /// /// Gets or sets the swagger endpoint. /// /// /// The swagger endpoint. /// [JsonPropertyName("endpoint")] public SwaggerEndpoint SwaggerEndpoint { get; set; } } /// /// Swagger Endpoint Class. /// [ExcludeFromCodeCoverage] public class SwaggerEndpoint { /// /// Gets or sets the URL. /// /// /// The URL. /// [JsonPropertyName("url")] public string Url { get; set; } /// /// Gets or sets the name. /// /// /// The name. /// [JsonPropertyName("name")] public string Name { get; set; } } } ================================================ FILE: templates/Liquid.Templates.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.32106.194 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Templates", "src\Liquid.Templates\Liquid.Templates.csproj", "{7320B7F9-FC40-4636-AC7B-DF23D295875F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7320B7F9-FC40-4636-AC7B-DF23D295875F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7320B7F9-FC40-4636-AC7B-DF23D295875F}.Debug|Any CPU.Build.0 = Debug|Any CPU {7320B7F9-FC40-4636-AC7B-DF23D295875F}.Release|Any CPU.ActiveCfg = Release|Any CPU {7320B7F9-FC40-4636-AC7B-DF23D295875F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {127F401F-B8DD-4005-B309-6FB8D52B9752} EndGlobalSection EndGlobal ================================================ FILE: templates/src/Liquid.Templates/Liquid.Templates.csproj ================================================  Template 8.0.0-beta-01 Liquid.Templates Liquid Templates MIT Avanade Brazil Avanade Brazil Inc. Liquid - Modern Application Framework Avanade logo.png Templates to create an application by using Liquid Application Framework. dotnet-new;templates netstandard2.1 true false content logo.png MIT Avanade Inc. Liquid Application Framework True ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Web", "ASP.NET" ], "identity": "Liquid.Crud.AddEntity", "name": "Liquid entity class, CRUD mediator handlers and CRUD controller", "shortName": "liquidcrudaddentity", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public CreateENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(CreateENTITYNAMERequest request, CancellationToken cancellationToken) { await _repository.AddAsync(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public CreateENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMEValidator : AbstractValidator { public CreateENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public ListENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(ListENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindAllAsync(); return new ListENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMERequest.cs ================================================ using MediatR; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMERequest : IRequest { public ListENTITYNAMERequest() { } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; using System.Collections.Generic; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMEResponse { public IEnumerable Data { get; set; } public ListENTITYNAMEResponse(IEnumerable data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public ReadENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(ReadENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Id); return new ReadENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMERequest.cs ================================================ using MediatR; using System; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMERequest : IRequest { public ENTITYIDTYPE Id { get; set; } public ReadENTITYNAMERequest(ENTITYIDTYPE id) { Id = id; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public ReadENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public RemoveENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(RemoveENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Id); if (data != null) { await _repository.RemoveByIdAsync(request.Id); //await _mediator.Publish(new GenericEntityRemovedNotification(entity)); } return new RemoveENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMERequest.cs ================================================ using MediatR; using System; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMERequest : IRequest { public ENTITYIDTYPE Id { get; set; } public RemoveENTITYNAMERequest(ENTITYIDTYPE id) { Id = id; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public RemoveENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public UpdateENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(UpdateENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Body.Id); if (data != null) { await _repository.UpdateAsync(request.Body); } return new UpdateENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public UpdateENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public UpdateENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEValidator : AbstractValidator { public UpdateENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.AddEntity/PROJECTNAME.WebApi/Controllers/ENTITYNAMEController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using PROJECTNAME.Domain.Entities; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System; using PROJECTNAME.Domain.Handlers; namespace PROJECTNAME.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class ENTITYNAMEController : LiquidControllerBase { public ENTITYNAMEController(IMediator mediator) : base(mediator) { } [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetById([FromRoute] ENTITYIDTYPE id) { var response = await ExecuteAsync(new ReadENTITYNAMERequest(id)); if (response.Data == null) return NotFound(); return Ok(response.Data); } [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public async Task Get() { var response = await ExecuteAsync(new ListENTITYNAMERequest()); if (response.Data == null) return NotFound(); return Ok(response.Data); } [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] public async Task Post([FromBody] ENTITYNAME entity) { await Mediator.Send(new CreateENTITYNAMERequest(entity)); return CreatedAtAction(nameof(GetById), new { id = entity.Id }, entity); } [HttpPut] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task Put([FromBody] ENTITYNAME entity) { var response = await ExecuteAsync(new UpdateENTITYNAMERequest(entity)); if (response.Data == null) return NotFound(); return NoContent(); } [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task Delete([FromRoute] ENTITYIDTYPE id) { var response = await ExecuteAsync(new RemoveENTITYNAMERequest(id)); if (response.Data == null) return NotFound(); return NoContent(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "WebApi", "Solution" ], "identity": "Liquid.Crud.Solution", "name": "Liquid WebAPI CRUD Solution (Domain and WebAPI projects)", "shortName": "liquidcrudsolution", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public CreateENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(CreateENTITYNAMERequest request, CancellationToken cancellationToken) { await _repository.AddAsync(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public CreateENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/CreateENTITYNAME/CreateENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class CreateENTITYNAMEValidator : AbstractValidator { public CreateENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public ListENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(ListENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindAllAsync(); return new ListENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMERequest.cs ================================================ using MediatR; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMERequest : IRequest { public ListENTITYNAMERequest() { } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ListENTITYNAME/ListENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; using System.Collections.Generic; namespace PROJECTNAME.Domain.Handlers { public class ListENTITYNAMEResponse { public IEnumerable Data { get; set; } public ListENTITYNAMEResponse(IEnumerable data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public ReadENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(ReadENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Id); return new ReadENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMERequest.cs ================================================ using MediatR; using System; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMERequest : IRequest { public ENTITYIDTYPE Id { get; set; } public ReadENTITYNAMERequest(ENTITYIDTYPE id) { Id = id; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/ReadENTITYNAME/ReadENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class ReadENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public ReadENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public RemoveENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } /// public async Task Handle(RemoveENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Id); if (data != null) { await _repository.RemoveByIdAsync(request.Id); //await _mediator.Publish(new GenericEntityRemovedNotification(entity)); } return new RemoveENTITYNAMEResponse(data); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMERequest.cs ================================================ using MediatR; using System; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMERequest : IRequest { public ENTITYIDTYPE Id { get; set; } public RemoveENTITYNAMERequest(ENTITYIDTYPE id) { Id = id; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/RemoveENTITYNAME/RemoveENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class RemoveENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public RemoveENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public UpdateENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(UpdateENTITYNAMERequest request, CancellationToken cancellationToken) { var data = await _repository.FindByIdAsync(request.Body.Id); if (data != null) { await _repository.UpdateAsync(request.Body); } return new UpdateENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public UpdateENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public UpdateENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/Handlers/UpdateENTITYNAME/UpdateENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class UpdateENTITYNAMEValidator : AbstractValidator { public UpdateENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/IDomainInjection.cs ================================================ namespace PROJECTNAME.Domain { public interface IDomainInjection { } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Domain/PROJECTNAME.Domain.csproj ================================================ net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.Microservice.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PROJECTNAME.Domain", "PROJECTNAME.Domain\PROJECTNAME.Domain.csproj", "{31F60967-56CC-4B1C-B79F-620DD7DD7031}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PROJECTNAME.WebApi", "PROJECTNAME.WebApi\PROJECTNAME.WebApi.csproj", "{11436432-DC12-4D60-8A84-848A3DC65967}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {31F60967-56CC-4B1C-B79F-620DD7DD7031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31F60967-56CC-4B1C-B79F-620DD7DD7031}.Debug|Any CPU.Build.0 = Debug|Any CPU {31F60967-56CC-4B1C-B79F-620DD7DD7031}.Release|Any CPU.ActiveCfg = Release|Any CPU {31F60967-56CC-4B1C-B79F-620DD7DD7031}.Release|Any CPU.Build.0 = Release|Any CPU {11436432-DC12-4D60-8A84-848A3DC65967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {11436432-DC12-4D60-8A84-848A3DC65967}.Debug|Any CPU.Build.0 = Debug|Any CPU {11436432-DC12-4D60-8A84-848A3DC65967}.Release|Any CPU.ActiveCfg = Release|Any CPU {11436432-DC12-4D60-8A84-848A3DC65967}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F34B1DC-5775-449B-8CEC-CBA30C2AB958} EndGlobalSection EndGlobal ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/Controllers/ENTITYNAMEController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using PROJECTNAME.Domain.Entities; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System; using PROJECTNAME.Domain.Handlers; namespace PROJECTNAME.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class ENTITYNAMEController : LiquidControllerBase { public ENTITYNAMEController(IMediator mediator) : base(mediator) { } [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetById([FromRoute] ENTITYIDTYPE id) { var response = await ExecuteAsync(new ReadENTITYNAMERequest(id)); if (response.Data == null) return NotFound(); return Ok(response.Data); } [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public async Task Get() { var response = await ExecuteAsync(new ListENTITYNAMERequest()); if (response.Data == null) return NotFound(); return Ok(response.Data); } [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] public async Task Post([FromBody] ENTITYNAME entity) { await Mediator.Send(new CreateENTITYNAMERequest(entity)); return CreatedAtAction(nameof(GetById), new { id = entity.Id }, entity); } [HttpPut] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task Put([FromBody] ENTITYNAME entity) { var response = await ExecuteAsync(new UpdateENTITYNAMERequest(entity)); if (response.Data == null) return NotFound(); return NoContent(); } [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task Delete([FromRoute] ENTITYIDTYPE id) { var response = await ExecuteAsync(new RemoveENTITYNAMERequest(id)); if (response.Data == null) return NotFound(); return NoContent(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/PROJECTNAME.WebApi.csproj ================================================ net8.0 enable enable true ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/PROJECTNAME.WebApi.http ================================================ @PROJECTNAME.WebApi_HostAddress = http://localhost:5003 GET {{PROJECTNAME.WebApi_HostAddress}}/ENTITYNAME/ Accept: application/json ### ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/Program.cs ================================================ using Liquid.WebApi.Http.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PROJECTNAME.Domain; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System; var builder = WebApplication.CreateBuilder(args); // Add services to the container. //TODO: Register and configure repository Liquid Cartridge // // Examples: // // [Mongo Cartridge] // 1. add Liquid Cartridge using CLI : dotnet add package Liquid.Repository.Mongo --version 6.* // 3. import liquid cartridge reference here: using Liquid.Repository.Mongo.Extensions; // 4. call cartridge DI method here : builder.Services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings:Entities"); // 5. edit appsettings.json file to include database configurations. // //[EntityFramework Cartridge] // 1. add DbContext using CLI command: dotnet new liquiddbcontextproject --projectName PROJECTNAME --entityName ENTITYNAME // 2. add PROJECTNAME.Repository to solution: dotnet sln add PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 3. add PROJECTNAME.Repository project reference to PROJECTNAME.WebApi project: dotnet add reference ../PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 4. add database dependency in this project using CLI command: dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 6.* // 5. import EntityFrameworkCore reference here: using Microsoft.EntityFrameworkCore; // 6. set database options here: Action options = (opt) => opt.UseInMemoryDatabase("CRUD"); // 7. add Liquid Cartridge in this project using CLI command: dotnet add package Liquid.Repository.EntityFramework --version 6.* // 8. import liquid cartridge reference here: using Liquid.Repository.EntityFramework.Extensions; // 9. import PROJECTNAME.Repository here: using PROJECTNAME.Repository; // 9. call cartridge DI method here: builder.Services.AddLiquidEntityFramework(options); // 10. edit appsettings.json file to include database configurations if necessary (for InMemory it's not necessary). builder.Services.AddLiquidHttp("Liquid", false, typeof(IDomainInjection).Assembly); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Crud.Solution/src/PROJECTNAME.WebApi/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Trace", "Microsoft": "Trace", "Microsoft.Hosting.Lifetime": "Trace" }, "Console": { "IncludeScopes": true, "FormatterName": "json" } }, "AllowedHosts": "*", "Liquid": { "swagger": { "name": "v1", "host": "", "schemes": [ "http", "https" ], "title": "PROJECTNAME.WebApi", "version": "v1", "description": "PROJECTNAME APIs", "SwaggerEndpoint": { "url": "/swagger/v1/swagger.json", "name": "PROJECTNAMEWebApi" } }, "culture": { "defaultCulture": "pt-BR" }, "MyMongoDbSettings": { "Settings": [ { "connectionString": "", "databaseName": "MySampleDb", "CollectionName": "SampleCollection", "ShardKey": "id" } ] } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.AddEntity/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Class" ], "identity": "Liquid.DbContext.AddEntity", "name": "Liquid DbContext entity configuration class (for Entity Framework)", "shortName": "liquidbcontextaddentity", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.AddEntity/PROJECTNAME.Repository/Configurations/ENTITYNAMEConfiguration.cs ================================================ using PROJECTNAME.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace PROJECTNAME.Repository.Configurations { public class ENTITYNAMEConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.Property(o => o.Id).ValueGeneratedOnAdd(); builder.HasKey(o => o.Id); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.Project/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Library", "Project" ], "identity": "Liquid.Repository.DbContext.Project", "name": "Liquid Repository project (EntityFramework DbContext configurations)", "shortName": "liquiddbcontextproject", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.Project/PROJECTNAME.Repository/Configurations/ENTITYNAMEConfiguration.cs ================================================ using PROJECTNAME.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace PROJECTNAME.Repository.Configurations { public class ENTITYNAMEConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.Property(o => o.Id).ValueGeneratedOnAdd(); builder.HasKey(o => o.Id); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.Project/PROJECTNAME.Repository/LiquidDbContext.cs ================================================ using Microsoft.EntityFrameworkCore; using System; namespace PROJECTNAME.Repository { public class LiquidDbContext : DbContext { public LiquidDbContext() : base() { } public LiquidDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.DbContext.Project/PROJECTNAME.Repository/PROJECTNAME.Repository.csproj ================================================  net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.AddHandler/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Library" ], "identity": "Liquid.Domain.AddHandler", "name": "Liquid mediator command handler", "shortName": "liquiddomainaddhandler", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.AddHandler/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public COMMANDNAMEENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(COMMANDNAMEENTITYNAMERequest request, CancellationToken cancellationToken) { //TODO: implement handler operation. return new COMMANDNAMEENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.AddHandler/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public COMMANDNAMEENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.AddHandler/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public COMMANDNAMEENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.AddHandler/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEValidator : AbstractValidator { public COMMANDNAMEENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Library", "Project" ], "identity": "Liquid.Domain.Project", "name": "Liquid Domain project (mediator command handler)", "shortName": "liquiddomainproject", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public COMMANDNAMEENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(COMMANDNAMEENTITYNAMERequest request, CancellationToken cancellationToken) { //TODO: implement handler operation. return new COMMANDNAMEENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public COMMANDNAMEENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public COMMANDNAMEENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEValidator : AbstractValidator { public COMMANDNAMEENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.Domain.Project/PROJECTNAME.Domain/PROJECTNAME.Domain.csproj ================================================ net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Web", "ASP.NET" ], "identity": "Liquid.WebApi.AddEntity", "name": "Liquid entity class, mediator command handler and CRUD controller", "shortName": "liquidwebapiaddentity", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public COMMANDNAMEENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(COMMANDNAMEENTITYNAMERequest request, CancellationToken cancellationToken) { //TODO: implement handler operation. return new COMMANDNAMEENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public COMMANDNAMEENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public COMMANDNAMEENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEValidator : AbstractValidator { public COMMANDNAMEENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.AddEntity/PROJECTNAME.WebApi/Controllers/ENTITYNAMEController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using PROJECTNAME.Domain.Entities; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System; using PROJECTNAME.Domain.Handlers; namespace PROJECTNAME.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class ENTITYNAMEController : LiquidControllerBase { public ENTITYNAMEController(IMediator mediator) : base(mediator) { } [HttpPost] public async Task COMMANDNAME([FromBody] ENTITYNAME entity) => await ExecuteAsync(new COMMANDNAMEENTITYNAMERequest(entity), HttpStatusCode.OK); } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Web", "WebApi" ], "identity": "Liquid.WebApi.Project", "name": "Liquid WebAPI project", "shortName": "liquidwebapiproject", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/Controllers/ENTITYNAMEController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using PROJECTNAME.Domain.Entities; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System; using PROJECTNAME.Domain.Handlers; namespace PROJECTNAME.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class ENTITYNAMEController : LiquidControllerBase { public ENTITYNAMEController(IMediator mediator) : base(mediator) { } [HttpPost] public async Task COMMANDNAME([FromBody] ENTITYNAME entity) => await ExecuteAsync(new COMMANDNAMEENTITYNAMERequest(entity), HttpStatusCode.OK); } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/PROJECTNAME.WebApi.csproj ================================================ net8.0 enable enable true ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/PROJECTNAME.WebApi.http ================================================ @PROJECTNAME.WebApi_HostAddress = http://localhost:5003 GET {{PROJECTNAME.WebApi_HostAddress}}/ENTITYNAME/ Accept: application/json ### ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/Program.cs ================================================ using Liquid.WebApi.Http.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PROJECTNAME.Domain; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System; var builder = WebApplication.CreateBuilder(args); // Add services to the container. //TODO: Register and configure repository Liquid Cartridge // // Examples: // // [Mongo Cartridge] // 1. add Liquid Cartridge using CLI : dotnet add package Liquid.Repository.Mongo --version 6.* // 3. import liquid cartridge reference here: using Liquid.Repository.Mongo.Extensions; // 4. call cartridge DI method here : builder.Services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings:Entities"); // 5. edit appsettings.json file to include database configurations. // //[EntityFramework Cartridge] // 1. add DbContext using CLI command: dotnet new liquiddbcontextproject --projectName PROJECTNAME --entityName ENTITYNAME // 2. add PROJECTNAME.Repository to solution: dotnet sln add PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 3. add PROJECTNAME.Repository project reference to PROJECTNAME.WebApi project: dotnet add reference ../PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 4. add database dependency in this project using CLI command: dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 6.* // 5. import EntityFrameworkCore reference here: using Microsoft.EntityFrameworkCore; // 6. set database options here: Action options = (opt) => opt.UseInMemoryDatabase("CRUD"); // 7. add Liquid Cartridge in this project using CLI command: dotnet add package Liquid.Repository.EntityFramework --version 6.* // 8. import liquid cartridge reference here: using Liquid.Repository.EntityFramework.Extensions; // 9. import PROJECTNAME.Repository here: using PROJECTNAME.Repository; // 9. call cartridge DI method here: builder.Services.AddLiquidEntityFramework(options); // 10. edit appsettings.json file to include database configurations if necessary (for InMemory it's not necessary). builder.Services.AddLiquidHttp("Liquid", false, typeof(COMMANDNAMEENTITYNAMERequest).Assembly); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Project/PROJECTNAME.WebApi/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Trace", "Microsoft": "Trace", "Microsoft.Hosting.Lifetime": "Trace" }, "Console": { "IncludeScopes": true, "FormatterName": "json" } }, "AllowedHosts": "*", "Liquid": { "swagger": { "name": "v1", "host": "", "schemes": [ "http", "https" ], "title": "PROJECTNAME.WebApi", "version": "v1", "description": "PROJECTNAME APIs", "SwaggerEndpoint": { "url": "/swagger/v1/swagger.json", "name": "PROJECTNAMEWebApi" } }, "culture": { "defaultCulture": "pt-BR" }, "MyMongoDbSettings": { "Settings": [ { "connectionString": "", "databaseName": "MySampleDb", "CollectionName": "SampleCollection", "ShardKey": "id" } ] } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "WebApi", "Solution" ], "identity": "Liquid.WebApi.Solution", "name": "Liquid WebAPI solution (Domain and WebAPI projects)", "shortName": "liquidwebapisolution", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public COMMANDNAMEENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(COMMANDNAMEENTITYNAMERequest request, CancellationToken cancellationToken) { //TODO: implement handler operation. return new COMMANDNAMEENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public COMMANDNAMEENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public COMMANDNAMEENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEValidator : AbstractValidator { public COMMANDNAMEENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Domain/PROJECTNAME.Domain.csproj ================================================ net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.Microservice.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PROJECTNAME.Domain", "PROJECTNAME.Domain\PROJECTNAME.Domain.csproj", "{C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PROJECTNAME.WebApi", "PROJECTNAME.WebApi\PROJECTNAME.WebApi.csproj", "{D5B3CA90-0B90-4CE5-A108-AF53BEE9EB44}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Debug|Any CPU.Build.0 = Debug|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Release|Any CPU.ActiveCfg = Release|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Release|Any CPU.Build.0 = Release|Any CPU {D5B3CA90-0B90-4CE5-A108-AF53BEE9EB44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D5B3CA90-0B90-4CE5-A108-AF53BEE9EB44}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5B3CA90-0B90-4CE5-A108-AF53BEE9EB44}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5B3CA90-0B90-4CE5-A108-AF53BEE9EB44}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F64CB676-D776-4B6A-B54B-59EA1BD87241} EndGlobalSection EndGlobal ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/Controllers/ENTITYNAMEController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using PROJECTNAME.Domain.Entities; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System; using PROJECTNAME.Domain.Handlers; namespace PROJECTNAME.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class ENTITYNAMEController : LiquidControllerBase { public ENTITYNAMEController(IMediator mediator) : base(mediator) { } [HttpPost] public async Task COMMANDNAME([FromBody] ENTITYNAME entity) => await ExecuteAsync(new COMMANDNAMEENTITYNAMERequest(entity), HttpStatusCode.OK); } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/PROJECTNAME.WebApi.csproj ================================================ net8.0 enable enable true ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/PROJECTNAME.WebApi.http ================================================ @PROJECTNAME.WebApi_HostAddress = http://localhost:5003 GET {{PROJECTNAME.WebApi_HostAddress}}/ENTITYNAME/ Accept: application/json ### ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/Program.cs ================================================ using Liquid.WebApi.Http.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PROJECTNAME.Domain; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System; var builder = WebApplication.CreateBuilder(args); // Add services to the container. //TODO: Register and configure repository Liquid Cartridge // // Examples: // // [Mongo Cartridge] // 1. add Liquid Cartridge using CLI : dotnet add package Liquid.Repository.Mongo --version 6.* // 3. import liquid cartridge reference here: using Liquid.Repository.Mongo.Extensions; // 4. call cartridge DI method here : builder.Services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings:Entities"); // 5. edit appsettings.json file to include database configurations. // //[EntityFramework Cartridge] // 1. add DbContext using CLI command: dotnet new liquiddbcontextproject --projectName PROJECTNAME --entityName ENTITYNAME // 2. add PROJECTNAME.Repository to solution: dotnet sln add PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 3. add PROJECTNAME.Repository project reference to PROJECTNAME.WebApi project: dotnet add reference ../PROJECTNAME.Repository/PROJECTNAME.Repository.csproj // 4. add database dependency in this project using CLI command: dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 6.* // 5. import EntityFrameworkCore reference here: using Microsoft.EntityFrameworkCore; // 6. set database options here: Action options = (opt) => opt.UseInMemoryDatabase("CRUD"); // 7. add Liquid Cartridge in this project using CLI command: dotnet add package Liquid.Repository.EntityFramework --version 6.* // 8. import liquid cartridge reference here: using Liquid.Repository.EntityFramework.Extensions; // 9. import PROJECTNAME.Repository here: using PROJECTNAME.Repository; // 9. call cartridge DI method here: builder.Services.AddLiquidEntityFramework(options); // 10. edit appsettings.json file to include database configurations if necessary (for InMemory it's not necessary). builder.Services.AddLiquidHttp("Liquid", false, typeof(COMMANDNAMEENTITYNAMERequest).Assembly); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WebApi.Solution/src/PROJECTNAME.WebApi/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Trace", "Microsoft": "Trace", "Microsoft.Hosting.Lifetime": "Trace" }, "Console": { "IncludeScopes": true, "FormatterName": "json" } }, "AllowedHosts": "*", "Liquid": { "swagger": { "name": "v1", "host": "", "schemes": [ "http", "https" ], "title": "PROJECTNAME.WebApi", "version": "v1", "description": "PROJECTNAME APIs", "SwaggerEndpoint": { "url": "/swagger/v1/swagger.json", "name": "PROJECTNAMEWebApi" } }, "culture": { "defaultCulture": "pt-BR" }, "MyMongoDbSettings": { "Settings": [ { "connectionString": "", "databaseName": "MySampleDb", "CollectionName": "SampleCollection", "ShardKey": "id" } ] } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Worker", "Web" ], "identity": "Liquid.WorkerService.Project", "name": "Liquid WorkerService project", "shortName": "liquidworkerproject", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/PROJECTNAME.WorkerService/PROJECTNAME.WorkerService.csproj ================================================  net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/PROJECTNAME.WorkerService/Program.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System; namespace PROJECTNAME.WorkerService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //TODO: Register and configure messaging consumer and repository Liquid Cartridge // // Example: // // [ServiceBus and Mongo Cartridges] // 1. add Liquid Cartridge using CLI : dotnet add package Liquid.Messaging.ServiceBus --version 6.X.X // 2. add Liquid Cartridge using CLI : dotnet add package Liquid.Repository.Mongo --version 6.X.X // 3. import liquid cartridge reference here: using Liquid.Repository.Mongo.Extensions; // 4. import liquid cartridge reference here: using Liquid.Messaging.ServiceBus.Extensions.DependencyInjection; // 5. call repository cartridge DI method : // services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings", "SampleCollection"); // 6. call messaging cartridge DI method : // services.AddLiquidServiceBusConsumer("Liquid:ServiceBus", "liquidinput", false, typeof(COMMANDNAMEENTITYNAMERequest).Assembly); // 7. edit appsettings.json file to include database and message queue configurations. }); } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/PROJECTNAME.WorkerService/Worker.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using MediatR; using Microsoft.Extensions.Logging; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System.Threading; using System.Threading.Tasks; namespace PROJECTNAME.WorkerService { public class Worker : ILiquidWorker { private readonly ILogger _logger; private readonly IMediator _mediator; public Worker(ILogger logger, IMediator mediator) { _logger = logger; _mediator = mediator; } public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { await _mediator.Send(new COMMANDNAMEENTITYNAMERequest(args.Data)); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/PROJECTNAME.WorkerService/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Project/PROJECTNAME.WorkerService/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Liquid": { "culture": { "defaultCulture": "pt-BR" }, "ScopedContext": { "keys": [ { "keyName": "Connection", "required": false } ] }, "ScopedLogging": { "keys": [ { "keyName": "Connection", "required": false } ] }, "MyMongoDbSettings": { "Settings": [ { "connectionString": "", "databaseName": "MySampleDb", "CollectionName": "SampleCollection", "ShardKey": "id" } ] }, "ServiceBus": { "Settings": [ { "ConnectionString": "", "EntityPath": "liquidinput" } ] } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/.template.config/template.json ================================================ { "$schema": "http://json.schemastore.org/template", "author": "Avanade Brazil", "classifications": [ "LAF", "Worker", "Web", "Solution" ], "identity": "Liquid.WorkerService.Solution", "name": "Liquid WorkerService solution (Domain and WorkerService projects)", "description": "Create a worker service solution with worker and domain projects using Liquid Framework.", "shortName": "liquidworkersolution", "tags": { "language": "C#", "type": "project" }, "symbols": { "projectName": { "type": "parameter", "replaces": "PROJECTNAME", "FileRename": "PROJECTNAME", "defaultValue": "MyProject" }, "entityName": { "type": "parameter", "replaces": "ENTITYNAME", "FileRename": "ENTITYNAME", "defaultValue": "MyObject" }, "command": { "type": "parameter", "replaces": "COMMANDNAME", "FileRename": "COMMANDNAME", "defaultValue": "MyCommand" }, "entityIdType": { "type": "parameter", "replaces": "ENTITYIDTYPE", "defaultValue": "int" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/Entities/ENTITYNAME.cs ================================================ using Liquid.Core.Entities; using System; namespace PROJECTNAME.Domain.Entities { public class ENTITYNAME : LiquidEntity { //TODO: declare entity properties. } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEHandler.cs ================================================ using Liquid.Core.Interfaces; using MediatR; using PROJECTNAME.Domain.Entities; using System.Threading; using System.Threading.Tasks; using System; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEHandler : IRequestHandler { private readonly ILiquidRepository _repository; public COMMANDNAMEENTITYNAMEHandler(ILiquidRepository repository) { _repository = repository; } public async Task Handle(COMMANDNAMEENTITYNAMERequest request, CancellationToken cancellationToken) { //TODO: implement handler operation. return new COMMANDNAMEENTITYNAMEResponse(request.Body); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMERequest.cs ================================================ using MediatR; using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMERequest : IRequest { public ENTITYNAME Body { get; set; } public COMMANDNAMEENTITYNAMERequest(ENTITYNAME body) { Body = body; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEResponse.cs ================================================ using PROJECTNAME.Domain.Entities; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEResponse { public ENTITYNAME Data { get; set; } public COMMANDNAMEENTITYNAMEResponse(ENTITYNAME data) { Data = data; } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/Handlers/COMMANDNAMEENTITYNAME/COMMANDNAMEENTITYNAMEValidator.cs ================================================ using FluentValidation; namespace PROJECTNAME.Domain.Handlers { public class COMMANDNAMEENTITYNAMEValidator : AbstractValidator { public COMMANDNAMEENTITYNAMEValidator() { RuleFor(request => request.Body.Id).NotEmpty().NotNull(); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Domain/PROJECTNAME.Domain.csproj ================================================ net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.Microservice.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.32106.194 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PROJECTNAME.WorkerService", "PROJECTNAME.WorkerService\PROJECTNAME.WorkerService.csproj", "{BF0E153B-F391-431B-AD2D-BB81799F39A6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PROJECTNAME.Domain", "PROJECTNAME.Domain\PROJECTNAME.Domain.csproj", "{C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {BF0E153B-F391-431B-AD2D-BB81799F39A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BF0E153B-F391-431B-AD2D-BB81799F39A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF0E153B-F391-431B-AD2D-BB81799F39A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF0E153B-F391-431B-AD2D-BB81799F39A6}.Release|Any CPU.Build.0 = Release|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Debug|Any CPU.Build.0 = Debug|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Release|Any CPU.ActiveCfg = Release|Any CPU {C45FC20A-6DAD-4BDE-BC3D-EBECF4058565}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F64CB676-D776-4B6A-B54B-59EA1BD87241} EndGlobalSection EndGlobal ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.WorkerService/PROJECTNAME.WorkerService.csproj ================================================  net8.0 ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.WorkerService/Program.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System; namespace PROJECTNAME.WorkerService { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { //TODO: Register and configure messaging consumer and repository Liquid Cartridge // // Example: // // [ServiceBus and Mongo Cartridges] // 1. add Liquid Cartridge using CLI : dotnet add package Liquid.Messaging.ServiceBus --version 6.X.X // 2. add Liquid Cartridge using CLI : dotnet add package Liquid.Repository.Mongo --version 6.X.X // 3. import liquid cartridge reference here: using Liquid.Repository.Mongo.Extensions; // 4. import liquid cartridge reference here: using Liquid.Messaging.ServiceBus.Extensions.DependencyInjection; // 5. call repository cartridge DI method : // services.AddLiquidMongoRepository("Liquid:MyMongoDbSettings", "SampleCollection"); // 6. call messaging cartridge DI method : // services.AddLiquidServiceBusConsumer("Liquid:ServiceBus", "liquidinput", false, typeof(COMMANDNAMEENTITYNAMERequest).Assembly); // 7. edit appsettings.json file to include database and message queue configurations. }); } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.WorkerService/Worker.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using MediatR; using Microsoft.Extensions.Logging; using PROJECTNAME.Domain.Entities; using PROJECTNAME.Domain.Handlers; using System.Threading; using System.Threading.Tasks; namespace PROJECTNAME.WorkerService { public class Worker : ILiquidWorker { private readonly ILogger _logger; private readonly IMediator _mediator; public Worker(ILogger logger, IMediator mediator) { _logger = logger; _mediator = mediator; } public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { await _mediator.Send(new COMMANDNAMEENTITYNAMERequest(args.Data)); } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.WorkerService/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } } ================================================ FILE: templates/src/Liquid.Templates/Templates/Liquid.WorkerService.Solution/src/PROJECTNAME.WorkerService/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "Liquid": { "culture": { "defaultCulture": "pt-BR" }, "ScopedContext": { "keys": [ { "keyName": "Connection", "required": false } ] }, "ScopedLogging": { "keys": [ { "keyName": "Connection", "required": false } ] }, "MyMongoDbSettings": { "Settings": [ { "connectionString": "", "databaseName": "MySampleDb", "CollectionName": "SampleCollection", "ShardKey": "id" } ] }, "ServiceBus": { "Settings": [ { "ConnectionString": "", "EntityPath": "liquidinput" } ] } } } ================================================ FILE: test/CodeCoverage.runsettings ================================================  .*nunit.* .*microsoft.* .*Polly.* ^System\.Diagnostics\.DebuggerHiddenAttribute$ ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ ^System\.CodeDom\.Compiler.GeneratedCodeAttribute$ ^System\.Diagnostics\.CodeAnalysis.ExcludeFromCodeCoverageAttribute$ .*\\atlmfc\\.* .*\\vctools\\.* .*\\public\\sdk\\.* .*\\microsoft sdks\\.* .*\\vc\\include\\.* True True True False ================================================ FILE: test/Liquid.Cache.Memory.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Cache.Memory.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Cache.Memory.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidMemoryDistributedCache_WhenWithTelemetryTrue_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLogging(); _sut.AddLiquidMemoryDistributedCache(options => { options.SizeLimit = 3000; }, true); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(MemoryDistributedCache))); } [Fact] public void AddLiquidMemoryDistributedCache_WhenWithTelemetryfalse_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLiquidMemoryDistributedCache(options => { options.SizeLimit = 3000; }, false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(MemoryDistributedCache))); } } } ================================================ FILE: test/Liquid.Cache.Memory.Tests/Liquid.Cache.Memory.Tests.csproj ================================================ net8.0 enable false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Cache.NCache.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Cache.NCache.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Xunit; namespace Liquid.Cache.NCache.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidNCacheDistributedCache_WhenWithTelemetryTrue_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLogging(); _sut.AddLiquidNCacheDistributedCache(configuration => { configuration.CacheName = "myCache"; configuration.EnableLogs = false; configuration.ExceptionsEnabled = true; }, true); var provider = _sut.BuildServiceProvider(); //Assert.NotNull(provider.GetService()); //Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); } [Fact] public void AddLiquidNCacheDistributedCache_WhenWithTelemetryfalse_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLiquidNCacheDistributedCache(configuration => { configuration.CacheName = "myCache"; configuration.EnableLogs = false; configuration.ExceptionsEnabled = true; }, false); var provider = _sut.BuildServiceProvider(); //Assert.NotNull(provider.GetService()); //Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); } } } ================================================ FILE: test/Liquid.Cache.NCache.Tests/Liquid.Cache.NCache.Tests.csproj ================================================ net8.0 enable enable false True False runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all Always Always Always ================================================ FILE: test/Liquid.Cache.NCache.Tests/client.ncconf ================================================ ================================================ FILE: test/Liquid.Cache.NCache.Tests/config.ncconf ================================================ ================================================ FILE: test/Liquid.Cache.NCache.Tests/tls.ncconf ================================================ certificate-name your-thumbprint false false false ================================================ FILE: test/Liquid.Cache.Redis.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Cache.Redis.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Cache.Redis.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidRedisDistributedCache_WhenWithTelemetryTrue_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLogging(); _sut.AddLiquidRedisDistributedCache(options => { options.Configuration = _configProvider.GetConnectionString("Test"); options.InstanceName = "TestInstance"; }, true); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(LiquidCache))); } [Fact] public void AddLiquidRedisDistributedCache_WhenWithTelemetryfalse_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLiquidRedisDistributedCache(options => { options.Configuration = _configProvider.GetConnectionString("Test"); options.InstanceName = "TestInstance"; }, false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(LiquidCache))); } } } ================================================ FILE: test/Liquid.Cache.Redis.Tests/Liquid.Cache.Redis.Tests.csproj ================================================ net8.0 enable false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Cache.SqlServer.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Cache.SqlServer.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Microsoft.Extensions.Caching.SqlServer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using Xunit; namespace Liquid.Cache.SqlServer.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidSqlServerDistributedCache_WhenWithTelemetryTrue_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLogging(); _sut.AddLiquidSqlServerDistributedCache(options => { options.ConnectionString = "DistCache_ConnectionString"; options.SchemaName = "dbo"; options.TableName = "TestCache"; }, true); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(SqlServerCache))); } [Fact] public void AddLiquidSqlServerDistributedCache_WhenWithTelemetryfalse_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddLiquidSqlServerDistributedCache(options => { options.ConnectionString = "DistCache_ConnectionString"; options.SchemaName = "dbo"; options.TableName = "TestCache"; }, false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); Assert.NotNull(_sut.FirstOrDefault(x => x.ImplementationType == typeof(SqlServerCache))); } } } ================================================ FILE: test/Liquid.Cache.SqlServer.Tests/Liquid.Cache.SqlServer.Tests.csproj ================================================ net8.0 enable enable false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Extensions/IServiceCollectionExtension.cs ================================================ using System.IO; using System.Text; using Liquid.Core.Telemetry.ElasticApm.Extensions.DependencyInjection; using Liquid.Core.Telemetry.ElasticApm.Tests.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Extensions { internal static class IServiceCollectionExtension { public static IServiceCollection AddElasticApmByConfiguration(this IServiceCollection services, bool enable) { var sb = new StringBuilder("{ \"ElasticApm\": {"); sb.Append(" \"ServerUrl\": \"http://elasticapm:8200\","); sb.Append(" \"SecretToken\": \"apm-server-secret-token\","); sb.Append(" \"TransactionSampleRate\": 1.0,"); sb.Append(" \"CloudProvider\": \"none\","); sb.Append(" \"Enabled\": "); sb.Append(enable.ToString().ToLower()); sb.Append(" } }"); using MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(sb.ToString())); var config = new ConfigurationBuilder() .AddJsonStream(stream) .Build(); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); return services; } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/IApplicationBuilderExtensionsTests.cs ================================================ using System; using Liquid.Core.Telemetry.ElasticApm.Extensions.DependencyInjection; using Liquid.Core.Telemetry.ElasticApm.Tests.Settings; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using NSubstitute; using Xunit; namespace Liquid.Core.Telemetry.ElasticApm.Tests { public sealed class IApplicationBuilderExtensionsTests { private readonly IApplicationBuilder _builder; public IApplicationBuilderExtensionsTests() { _builder = Substitute.For(); } [Fact] public void UseElasticApmTelemetry_WhenConfigured() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var config = new ConfigurationSettings().AddElasticApm(); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(2); } [Fact] public void UseElasticApmTelemetry_WhenNotConfigured() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var config = new ConfigurationBuilder().Build(); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(1); } [Fact] public void UseElasticApmTelemetry_WhenConfigured_Enabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var config = new ConfigurationSettings().AddElasticApm(enable: true); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(2); } [Fact] public void UseElasticApmTelemetry_WhenConfigured_NotEnabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var config = new ConfigurationSettings().AddElasticApm(enable: false); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(2); } [Fact] public void UseElasticApmTelemetry_WhenEnvironement_Enabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", "true"); var config = new ConfigurationSettings().AddElasticApm(); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(2); } [Fact] public void UseElasticApmTelemetry_WhenEnvironement_NotEnabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", "false"); var config = new ConfigurationSettings().AddElasticApm(); // Act _builder.UseLiquidElasticApm(config); // Assert _builder.Received(1); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/IServiceCollectionExtensionsTests.cs ================================================ using System; using Liquid.Core.Telemetry.ElasticApm.Extensions.DependencyInjection; using Liquid.Core.Telemetry.ElasticApm.Tests.Mocks; using Liquid.Core.Telemetry.ElasticApm.Tests.Settings; using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Liquid.Core.Telemetry.ElasticApm.Tests { public sealed class IServiceCollectionExtensionsTests { [Fact] public void AddElasticApmTelemetry_WhenConfigured() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationSettings().AddElasticApm(); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert Assert.NotNull(behaviour); } [Fact] public void AddElasticApmTelemetry_WhenNotConfigured() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationBuilder().Build(); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert Assert.Null(behaviour); } [Fact] public void AddElasticApmTelemetry_WhenConfigured_Enabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationSettings().AddElasticApm(enable: true); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert Assert.NotNull(behaviour); } [Fact] public void AddElasticApmTelemetry_WhenConfigured_NotEnabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", null); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationSettings().AddElasticApm(enable: false); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert Assert.Null(behaviour); } [Fact] public void AddElasticApmTelemetry_WhenEnvironement_Enabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", "true"); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationSettings().AddElasticApm(enable: false); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert //Assert.NotNull(behaviour); } [Fact] public void AddElasticApmTelemetry_WhenEnvironement_NotEnabled() { // Arrange Environment.SetEnvironmentVariable("ELASTIC_APM_ENABLED", "false"); var services = new ServiceCollection().AddLogging(); var config = new ConfigurationSettings().AddElasticApm(); services.Configure(config.GetSection(nameof(ElasticApmSettings))); services.AddLiquidElasticApmTelemetry(config); // Act using ServiceProvider serviceprovider = services.BuildServiceProvider(); var behaviour = serviceprovider.GetService>(); // Assert //Assert.Null(behaviour); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Liquid.Core.Telemetry.ElasticApm.Tests.csproj ================================================  net8.0 false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/LiquidElasticApmInterceptorTests.cs ================================================ using System; using System.Threading.Tasks; using Castle.DynamicProxy; using Elastic.Apm.Api; using Liquid.Core.Telemetry.ElasticApm.Implementations; using Liquid.Core.Telemetry.ElasticApm.Tests.Mocks; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace Liquid.Core.Telemetry.ElasticApm.Tests { public class LiquidElasticApmInterceptorTests { private readonly LiquidElasticApmInterceptor _sut; private readonly IMockService _service; private readonly ILogger _logger; private readonly ITracer _tracer; public LiquidElasticApmInterceptorTests() { _logger = Substitute.For>(); _tracer = Substitute.For(); _sut = new LiquidElasticApmInterceptor(_logger, _tracer); var generator = new ProxyGenerator(); var logger = Substitute.For>(); IMockService service = new MockService(logger); _service = generator.CreateInterfaceProxyWithTarget(service, _sut); } [Fact] public async Task Intercept_WhenMethodExecutionIsSucessfull_TracerStarts() { await _service.Get(); _logger.Received(0); _tracer.Received(1); } [Fact] public async Task Intercept_WhenMethodExecutionThrowsException_TracerStartsLogsException() { await Assert.ThrowsAsync(() => _service.GetError()); _logger.Received(1); _tracer.Received(1); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/LiquidElasticApmTelemetryBehaviorTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Elastic.Apm.Api; using Liquid.Core.Telemetry.ElasticApm.MediatR; using Liquid.Core.Telemetry.ElasticApm.Tests.Mocks; using MediatR; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace Liquid.Core.Telemetry.ElasticApm.Tests { public class LiquidElasticApmTelemetryBehavior { private readonly ILogger> _logger; private readonly ITracer _tracer; public LiquidElasticApmTelemetryBehavior() { _logger = Substitute.For>>(); _tracer = Substitute.For(); } [Fact] public async Task Handle_WhenMethodExecutionIsSucessfull_LogsStartEnd() { // Arrange var handler = Substitute.For>(); var pipelineBehavior = new LiquidElasticApmTelemetryBehavior(_logger, _tracer); // Act await pipelineBehavior.Handle(new RequestMock(), () => { return handler.Handle(new RequestMock(), CancellationToken.None); }, CancellationToken.None); // Assert _logger.Received(2); _tracer.Received(1); } [Fact] public async Task Handle_WhenMethodExecutionThrowsException_LogsException() { // Arrange var handler = new CommandHandlerMock(); var pipelineBehavior = new LiquidElasticApmTelemetryBehavior(_logger, _tracer); // Act await Assert.ThrowsAsync(() => pipelineBehavior.Handle(new RequestMock(), () => { return handler.Handle(new RequestMock(), CancellationToken.None); }, CancellationToken.None) ); // Assert _logger.Received(3); _tracer.Received(1); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Mocks/CommandHandlerMock.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using MediatR; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Mocks { public sealed class CommandHandlerMock : IRequestHandler { public Task Handle(RequestMock request, CancellationToken cancellationToken) { throw new NotImplementedException(); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Mocks/IMockService.cs ================================================ using System.Threading.Tasks; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Mocks { public interface IMockService { Task Get(); Task GetError(); } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Mocks/MockService.cs ================================================ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Mocks { public class MockService : IMockService { private readonly ILogger _logger; public MockService(ILogger logger) { _logger = logger; } public MockService() { } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task Get() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { _logger.LogInformation("sucess"); return "Test"; } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task GetError() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { throw new NotImplementedException(); } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Mocks/RequestMock.cs ================================================ using MediatR; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Mocks { public sealed class RequestMock : IRequest { } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Mocks/ResponseMock.cs ================================================ namespace Liquid.Core.Telemetry.ElasticApm.Tests.Mocks { public sealed class ResponseMock { } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Settings/ConfigurationSettings.cs ================================================ using System.IO; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Configuration; namespace Liquid.Core.Telemetry.ElasticApm.Tests.Settings { internal class ConfigurationSettings { internal IConfiguration AddElasticApm(bool? enable = null) { var elasticApmSettings = new ElasticApmSettings { ServerUrl = "http://elasticapm:8200", SecretToken = "apm-server-secret-token", TransactionSampleRate = 1.0, CloudProvider = "none" }; if (enable.HasValue) { elasticApmSettings.Enabled = enable.Value; } var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; var sb = new StringBuilder("{ \"ElasticApm\": "); sb.Append(JsonSerializer.Serialize(elasticApmSettings, options)); sb.Append(" }"); using MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(sb.ToString())); var config = new ConfigurationBuilder() .AddJsonStream(stream) .Build(); return config; } } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Settings/ElasticApmSettings.cs ================================================ namespace Liquid.Core.Telemetry.ElasticApm.Tests.Settings { internal sealed class ElasticApmSettings : IElasticApmSettings { public string ServerUrl { get; set; } public string SecretToken { get; set; } public double TransactionSampleRate { get; set; } public string CloudProvider { get; set; } public bool? Enabled { get; set; } = null; } } ================================================ FILE: test/Liquid.Core.Telemetry.ElasticApm.Tests/Settings/IElasticApmSettings.cs ================================================ namespace Liquid.Core.Telemetry.ElasticApm.Tests.Settings { internal interface IElasticApmSettings { string ServerUrl { get; set; } string SecretToken { get; set; } double TransactionSampleRate { get; set; } string CloudProvider { get; set; } bool? Enabled { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Cache/IServiceCollectionExtensionTests.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Core.Tests.Cache { public class IServiceCollectionExtensionTests { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private readonly IDistributedCache _distributedCache = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidDistributedCache_WhenWithTelemetryTrue_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddSingleton(_distributedCache); _sut.AddLogging(); _sut.AddLiquidDistributedCache(true); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); } [Fact] public void AddLiquidDistributedCache_WhenWithTelemetryfalse_GetServicesReturnLiqudCache() { SetCollection(); _sut.AddSingleton(_distributedCache); _sut.AddLiquidDistributedCache(false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidCache) && x.Lifetime == ServiceLifetime.Scoped)); } } } ================================================ FILE: test/Liquid.Core.Tests/Cache/LiquidCacheTests.cs ================================================ using Liquid.Core.Extensions; using Liquid.Core.Implementations; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.Caching.Distributed; using NSubstitute; using System; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Cache { public class LiquidCacheTests { private readonly IDistributedCache _distributedCache = Substitute.For(); private readonly LiquidCache _sut; public LiquidCacheTests() { _sut = new LiquidCache(_distributedCache); } [Fact] public void Ctor_WhenIDistributedCacheIsNull_ThrowException() { Assert.Throws(() => new LiquidCache(null)); } [Fact] public void GetByteArray_WhenKeyExists_ThenReturnByteArray() { //Arrange var bytes = new byte[] { 1, 2, 3, 4, 5 }; _distributedCache.Get(Arg.Any()).Returns(bytes); //Act var result = _sut.Get("test"); //Assert Assert.Equal(bytes, result); } [Fact] public async Task GetAsyncByteArray_WhenKeyExists_ThenReturnByteArray() { //Arrange var bytes = new byte[] { 1, 2, 3, 4, 5 }; _distributedCache.GetAsync(Arg.Any()).Returns(bytes); //Act var result = await _sut.GetAsync("test"); //Assert Assert.Equal(bytes, result); } [Fact] public void GetComplexType_WhenKeyExists_ThenReturnType() { //Arrange var values = new MockType(); _distributedCache.Get(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = _sut.Get("test"); //Assert Assert.IsType(result); } [Fact] public async Task GetAsyncComplexType_WhenKeyExists_ThenReturnType() { //Arrange var values = new MockType(); _distributedCache.GetAsync(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = await _sut.GetAsync("test"); //Assert Assert.IsType(result); } [Fact] public void GetPrimitiveType_WhenKeyExists_ThenReturnPrimitive() { //Arrange var values = false; _distributedCache.Get(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = _sut.Get("test"); //Assert Assert.IsType(result); Assert.Equal(values, result); } [Fact] public async Task GetAsyncPrimitiveType_WhenKeyExists_ThenReturnPrimitive() { //Arrange var values = true; _distributedCache.GetAsync(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = await _sut.GetAsync("test"); //Assert Assert.IsType(result); Assert.Equal(values, result); } [Fact] public void GetGuid_WhenKeyExists_ThenReturnGuid() { //Arrange var values = new Guid(); _distributedCache.Get(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = _sut.Get("test"); //Assert Assert.IsType(result); Assert.Equal(values, result); } [Fact] public async Task GetAsyncGuid_WhenKeyExists_ThenReturnGuid() { //Arrange var values = new Guid(); _distributedCache.GetAsync(Arg.Any()).Returns(values.ToJsonBytes()); //Act var result = await _sut.GetAsync("test"); //Assert Assert.IsType(result); Assert.Equal(values, result); } [Fact] public void SetByteArray_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var bytes = new byte[] { 1, 2, 3, 4, 5 }; //Act _sut.Set("test", bytes, default); //Assert _distributedCache.Received(1).Set("test", bytes, Arg.Any()); } [Fact] public async Task SetAsyncByteArray_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var bytes = new byte[] { 1, 2, 3, 4, 5 }; //Act await _sut.SetAsync("test", bytes, default); //Assert await _distributedCache.Received(1).SetAsync("test", bytes, Arg.Any()); } [Fact] public void SetComplexType_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = new MockType(); //Act _sut.Set("test", values, default); //Assert _distributedCache.Received(1).Set("test", Arg.Any(), Arg.Any()); } [Fact] public async Task SetAsyncComplexType_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = new MockType(); //Act await _sut.SetAsync("test", values, default); //Assert await _distributedCache.Received(1).SetAsync("test", Arg.Any(), Arg.Any()); } [Fact] public void SetPrimitiveType_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = "Test value"; //Act _sut.Set("test", values, default); //Assert _distributedCache.Received(1).Set("test", Arg.Any(), Arg.Any()); } [Fact] public async Task SetAsyncPrimitiveType_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = "Test value"; //Act await _sut.SetAsync("test", values, default); //Assert await _distributedCache.Received(1).SetAsync("test", Arg.Any(), Arg.Any()); } [Fact] public void SetGuid_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = new Guid(); //Act _sut.Set("test", values, default); //Assert _distributedCache.Received(1).Set("test", Arg.Any(), Arg.Any()); } [Fact] public async Task SetAsyncGuid_WhenSucessfullySet_ThenDistributeCacheSetReceivedCall() { //Arrange var values = new Guid(); //Act await _sut.SetAsync("test", values, default); //Assert await _distributedCache.Received(1).SetAsync("test", Arg.Any(), Arg.Any()); } [Fact] public void Refresh_WhenSucessfull_ThenDistributeCacheRefreshReceivedCall() { //Arrange //Act _sut.Refresh("test"); //Assert _distributedCache.Received(1).Refresh("test"); } [Fact] public async Task RefreshAsync_WhenSucessfull_ThenDistributeCacheRefreshAsyncReceivedCall() { //Arrange //Act await _sut.RefreshAsync("test"); //Assert await _distributedCache.Received(1).RefreshAsync("test"); } [Fact] public void Remove_WhenSucessfull_ThenDistributeCacheRemoveReceivedCall() { //Arrange //Act _sut.Remove("test"); //Assert _distributedCache.Received(1).Remove("test"); } [Fact] public async Task RemoveAsync_WhenSucessfull_ThenDistributeCacheRemoveAsyncReceivedCall() { //Arrange //Act await _sut.RemoveAsync("test"); //Assert await _distributedCache.Received(1).RemoveAsync("test"); } } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test1/Test1Command.cs ================================================ using MediatR; namespace Liquid.Domain.Tests.CommandHandlers.Test1 { public class Test1Command : IRequest { } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test1/Test1CommandHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; using AutoMapper; using MediatR; namespace Liquid.Domain.Tests.CommandHandlers.Test1 { public class Test1CommandHandler : IRequestHandler { public async Task Handle(Test1Command request, CancellationToken cancellationToken) { return await Task.FromResult(new Test1Response()); } } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test1/Test1Response.cs ================================================ namespace Liquid.Domain.Tests.CommandHandlers.Test1 { public class Test1Response { } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test2/Test2Command.cs ================================================ using System.Text.Json.Serialization; using MediatR; namespace Liquid.Domain.Tests.CommandHandlers.Test2 { public class Test2Command : IRequest { [JsonPropertyName("id")] public int Id { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test2/Test2CommandHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; using AutoMapper; using MediatR; namespace Liquid.Domain.Tests.CommandHandlers.Test2 { public class Test2CommandHandler : IRequestHandler { public async Task Handle(Test2Command request, CancellationToken cancellationToken) { return await Task.FromResult(new Test2Response()); } } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test2/Test2CommandValidator.cs ================================================ using FluentValidation; namespace Liquid.Domain.Tests.CommandHandlers.Test2 { public class Test2CommandValidator : AbstractValidator { public Test2CommandValidator() { RuleFor(command => command.Id).GreaterThan(0); } } } ================================================ FILE: test/Liquid.Core.Tests/CommandHandlers/Test2/Test2Response.cs ================================================ namespace Liquid.Domain.Tests.CommandHandlers.Test2 { public class Test2Response { } } ================================================ FILE: test/Liquid.Core.Tests/Core/IServiceCollectionLiquidExtensionTest.cs ================================================ using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Implementations; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Core.Tests.Core { public class IServiceCollectionLiquidExtensionTest { private IServiceCollection _sut; [Fact] [System.Obsolete("The extension method AddLiquidTelemetryInterceptor() is obsolete, so is this!")] public void AddLiquidTelemetryInterceptor_WhenSuccessfullyInjectsInterceptor_GetServiceSuccessfully() { SetCollection(); _sut.AddLiquidTelemetryInterceptor(); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IMockService) && x.Lifetime == ServiceLifetime.Transient)); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); } [Fact] public void AddScopedLiquidTelemetry_WhenSuccessfullyInjectsInterceptor_GetServiceSuccessfully() { SetCollection(); _sut.AddScopedLiquidTelemetry(); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IMockService) && x.Lifetime == ServiceLifetime.Scoped)); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); } private void SetCollection() { _sut = new ServiceCollection(); _sut.AddSingleton(); _sut.AddSingleton(Substitute.For>()); } [Fact] public void AddSingletonLiquidTelemetry_WhenSuccessfullyInjectsInterceptor_GetServiceSuccessfully() { SetCollection(); _sut.AddSingletonLiquidTelemetry(); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IMockService) && x.Lifetime == ServiceLifetime.Singleton)); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); } [Fact] public void AddTransientLiquidTelemetry_WhenSuccessfullyInjectsInterceptor_GetServiceSuccessfully() { SetCollection(); _sut.AddTransientLiquidTelemetry(); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IMockService) && x.Lifetime == ServiceLifetime.Transient)); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidContextNotificationsTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidContextNotificationsTest { private ILiquidContextNotifications _sut; public LiquidContextNotificationsTest() { _sut = new LiquidContextNotifications(new LiquidContext()); } private void InitializeNotifications() { _sut.InsertNotification("initialize notifications"); } [Fact] public void InsertNotificaton_WhenContextHasNoNotifications_Inserted() { _sut.InsertNotification("test case"); var result = _sut.GetNotifications(); Assert.True(result.Count == 1); Assert.True(result.Contains("test case")); } [Fact] public void InsertNotification_WhenContextHasNotifications_Inserted() { InitializeNotifications(); _sut.InsertNotification("test case 2"); var result = _sut.GetNotifications(); Assert.True(result.Count > 1); Assert.True(result.Contains("test case 2")); } [Fact] public void UpdateNotification_WhenNotificationTextAlredyExists_Inserted() { InitializeNotifications(); _sut.InsertNotification("test case"); var result = _sut.GetNotifications(); Assert.True(result.Count > 1); Assert.True(result.Contains("test case")); } [Fact] public void GetNotifications_WhenContexthasNone_ReturnNull() { var result = _sut.GetNotifications(); Assert.Null(result); } [Fact] public void GetNotifications_WhenContexthasNotifications_ReturnNotifications() { InitializeNotifications(); var result = _sut.GetNotifications(); Assert.True(result.Count >= 1); Assert.NotNull(result); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidContextTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidContextTest { private ILiquidContext _sut; public LiquidContextTest() { _sut = new LiquidContext(); _sut.Upsert("teste", 123); } [Fact] public void UpsertKey_WhenContextIsEmpty_KeyInserted() { var sut = new LiquidContext(); sut.Upsert("teste", 123); Assert.True((int)sut.current["teste"] == 123); } [Fact] public void UpsertKey_WhenInsertNewKey_NewKeyInserted() { _sut.Upsert("case2", 456); Assert.True((int)_sut.current["teste"] == 123); Assert.True((int)_sut.current["case2"] == 456); Assert.True(_sut.current.Count == 2); } [Fact] public void UpsertKey_WhenUpdateKey_KeyUpdated() { _sut.Upsert("teste", 456); Assert.True((int)_sut.current["teste"] == 456); Assert.True(_sut.current.Count == 1); } [Fact] public void Get_WhenKeyExists_ReturnValue() { var result = _sut.Get("teste"); Assert.NotNull(result); } [Fact] public void Get_WhenKeyDoesntExist_ReturnNull() { var result = _sut.Get("case3"); Assert.Null(result); } [Fact] public void Get_WhenCurrentHasNoItens_ReturnNull() { var sut = new LiquidContext(); var result = sut.Get("teste"); Assert.Null(result); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidJsonSerializerTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidJsonSerializerTest { private ILiquidSerializer _sut; public LiquidJsonSerializerTest() { _sut = new LiquidJsonSerializer(); } [Fact] public void Serialize_WhenSerializeObject_ReturnJsonString() { var content = new { stringProperty = "1", intPropery = 2 }; var result = _sut.Serialize(content); Assert.NotNull(result); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidSerializerProviderTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using System; using System.Collections.Generic; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidSerializerProviderTest { private ILiquidSerializerProvider _sut; private List _serializers; public LiquidSerializerProviderTest() { _serializers = new List(); _serializers.Add(new LiquidJsonSerializer()); _sut = new LiquidSerializerProvider(_serializers); } [Fact] public void GetSerializerByType_WhenServiceTypeExists_ReturnService() { var result = _sut.GetSerializerByType(typeof(LiquidJsonSerializer)); Assert.NotNull(result); Assert.Equal(typeof(LiquidJsonSerializer), result.GetType()); } [Fact] public void GetSerializerByType_WhenServiceTypeDoesntExists_ReturnNull() { var result = _sut.GetSerializerByType(typeof(LiquidXmlSerializer)); Assert.Null(result); } [Fact] public void Ctor_WhenArgumentIsNull_ThrowException() { IEnumerable serializers = default; Assert.Throws(() => new LiquidSerializerProvider(serializers)); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidTelemetryInterceptorTest.cs ================================================ using Castle.DynamicProxy; using Liquid.Core.Implementations; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.Logging; using NSubstitute; using System; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidTelemetryInterceptorTest { private LiquidTelemetryInterceptor _sut; private IMockService _input; private ILogger _logger; public LiquidTelemetryInterceptorTest() { var generator = new ProxyGenerator(); var logger = Substitute.For>(); IMockService service = new MockService(logger); _logger = Substitute.For>(); _sut = new LiquidTelemetryInterceptor(_logger); _input = generator.CreateInterfaceProxyWithTarget(service, _sut); } [Fact] public async Task Intercept_WhenMethodExecutionIsSucessfull_LogStarEnd() { await _input.Get(); _logger.Received(2); } [Fact] public async Task Intercept_WhenMethodExecutionThrowsException_LogStarExceptionAndEnd() { await Assert.ThrowsAsync(() => _input.GetError()); _logger.Received(3); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LiquidXmlSerializerTest.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Tests.Mocks; using Xunit; namespace Liquid.Core.Tests.Core { public class LiquidXmlSerializerTest { private ILiquidSerializer _sut; public LiquidXmlSerializerTest() { _sut = new LiquidXmlSerializer(); } [Fact] public void Serialize_WhenContentTyped_ReturnXmlString() { var content = new MockSerializeObject(1, "2"); var result = _sut.Serialize(content); Assert.NotNull(result); } [Fact] public void Serialize_WhenContentIsAnonymous_ThrowException() { var content = new { stringProperty = "1", intPropery = 2 }; Assert.Throws(() => _sut.Serialize(content)); } } } ================================================ FILE: test/Liquid.Core.Tests/Core/LocalizationTest.cs ================================================ namespace Liquid.Core.Tests.Core { public class LocalizationTest { //private const string StringValueKey = "stringValueKey"; //private ILocalization _subjectUnderTest; //private IServiceProvider _serviceProvider; //public LocalizationTest() //{ // IServiceCollection services = new ServiceCollection(); // var builder = new ConfigurationBuilder(); // builder.AddJsonFile("appsettings.json"); // var config = builder.Build(); // services.AddSingleton(config); // services.AddLiquidConfiguration(); // services.AddLocalizationService(); // _serviceProvider = services.BuildServiceProvider(); // var cultureInfo = new CultureInfo("pt-BR"); // Thread.CurrentThread.CurrentCulture = cultureInfo; // Thread.CurrentThread.CurrentUICulture = cultureInfo; // _subjectUnderTest = _serviceProvider.GetService(); //} ///// ///// Asserts if can read string from cache. ///// //[Fact] //public void Verify_if_can_read_string_from_file() //{ // var stringValue = _subjectUnderTest.Get(StringValueKey); // Assert.Equal("Texto em português", stringValue); // stringValue = _subjectUnderTest.Get(StringValueKey, "android"); // Assert.Equal("Texto em português", stringValue); // stringValue = _subjectUnderTest.Get(StringValueKey, new CultureInfo("en-US")); // Assert.Equal("English text", stringValue); // stringValue = _subjectUnderTest.Get(StringValueKey, new CultureInfo("es-ES"), "iphone"); // Assert.Equal("texto en español", stringValue); // stringValue = _subjectUnderTest.Get("InexistentKey"); // Assert.Equal("InexistentKey", stringValue); //} ///// ///// Verifies exceptions. ///// //[Fact] //public void Verify_Exceptions() //{ // Assert.Throws(() => { _serviceProvider.GetService().Get(StringValueKey, (CultureInfo)null); }); // Assert.Throws(() => { _serviceProvider.GetService().Get(null); }); //} } } ================================================ FILE: test/Liquid.Core.Tests/Domain/RequestHandlerTest.cs ================================================ using FluentValidation; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.PipelineBehaviors; using Liquid.Domain.Tests.CommandHandlers.Test1; using Liquid.Domain.Tests.CommandHandlers.Test2; using MediatR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Logging.Console; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Domain { /// /// Base Command Handler Test Class. /// /// /// Liquid.Test.Base.TestTemplateContext{Liquid.Domain.Tests.TestEntities.TestCommandHandler} /// [ExcludeFromCodeCoverage] public class RequestHandlerTest { private IServiceProvider _serviceProvider; private ILogger> _logger = Substitute.For>>(); private ILogger> _logger2 = Substitute.For>>(); public RequestHandlerTest() { var services = new ServiceCollection(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); LoggerProviderOptions.RegisterProviderOptions(services); #pragma warning disable CS0618 services.Configure(new Action(options => options.DisableColors = false)); #pragma warning restore CS0618 services.AddSingleton(LoggerFactory.Create(builder => { builder.AddConsole(); })); services.AddTransient((s) => _logger); services.AddTransient((s) => _logger2); services.AddLiquidHandlers(true, true, GetType().Assembly); _serviceProvider = services.AddLogging().BuildServiceProvider(); } [Fact] public async Task Test_WhenCommandHasntValidator_Sucess() { var mediator = _serviceProvider.GetRequiredService(); using var scopedTransaction = _serviceProvider.CreateScope(); var response = await mediator.Send(new Test1Command()); _logger.Received(2); Assert.NotNull(response); } [Fact] public async Task Test_WhenValidatorPassed_Sucess() { var mediator = _serviceProvider.GetRequiredService(); using var scopedTransaction2 = _serviceProvider.CreateScope(); var response2 = await mediator.Send(new Test2Command { Id = 1 }); Assert.NotNull(response2); _logger2.Received(2); } [Fact] public async Task Test_WhenValidatorThrowError_ThowException() { await Assert.ThrowsAsync(async () => { var mediator = _serviceProvider.GetRequiredService(); using var scopedTransaction = _serviceProvider.CreateScope(); await mediator.Send(new Test2Command { Id = -1 }); }); _logger2.Received(3); } } } ================================================ FILE: test/Liquid.Core.Tests/Liquid.Core.Tests.csproj ================================================  net8.0 false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all Always Always Always Always ================================================ FILE: test/Liquid.Core.Tests/Messaging/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Core.Decorators; using Liquid.Core.Extensions.DependencyInjection; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.Core.Tests.Mocks; using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSubstitute; using System; using Xunit; namespace Liquid.Core.Tests.Messaging { public class IServiceCollectionExtensionTest { private IServiceCollection _services; private IServiceProvider _serviceProvider; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); public IServiceCollectionExtensionTest() { _services = new ServiceCollection(); } [Fact] public void AddLiquidWorkerService_WhenAdded_ServiceProvideCanResolveHostedService() { _services.AddSingleton(Substitute.For>()); _services.AddLiquidWorkerService(); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService()); } [Fact] public void AddLiquidDomain_WhenAdded_ServiceProviderCanResolveMediatorService() { _services.AddLiquidDomain(typeof(CommandRequestMock).Assembly); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService()); } [Fact] public void AddLiquidPipeline_WhenAdded_ServiceProviderCanResolveLiquidWorkerService() { ConfigureServices(); _services.AddSingleton, WorkerMock>(); _services.AddSingleton(Substitute.For>()); _services.AddLiquidPipeline(); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService>()); } [Fact] public void AddLiquidMessageConsumer_WhenAdded_ServiceProviderCanResolveLiquidMessagingConsumerServices() { ConfigureServices(); _services.AddSingleton(Substitute.For>()); _services.AddLiquidMessageConsumer(typeof(CommandRequestMock).Assembly); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService()); Assert.NotNull(_serviceProvider.GetService()); } private void ConfigureServices() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _services.AddSingleton(_configProvider); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>()); _services.AddSingleton(Substitute.For>>()); } } } ================================================ FILE: test/Liquid.Core.Tests/Messaging/LiquidBackgroundServiceTest.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Messaging { public class LiquidBackgroundServiceTest : LiquidBackgroundService { private static ILiquidConsumer _consumer = Substitute.For>(); private static IServiceProvider _serviceProvider = Substitute.For(); public LiquidBackgroundServiceTest() : base(_serviceProvider, _consumer) { } [Fact] public void ExecuteAsync_WhenStart_ConsumerReceivedStartCall() { var task = base.ExecuteAsync(new CancellationToken()); _consumer.Received().RegisterMessageHandler(); } [Fact] public async Task ExecuteAsync_WhenStartFail_ThrowException() { _consumer.When(x => x.RegisterMessageHandler()) .Do((call) => throw new Exception()); var task = ExecuteAsync(new CancellationToken()); await Assert.ThrowsAsync(() => task); } [Fact] public async Task ProcessMessageAsync_WhenMessageProcessedSuccessfuly_WorkerReceiveCall() { var worker = Substitute.For>(); var sut = new LiquidBackgroundService(GetProvider(worker), _consumer); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken()); await worker.Received(1).ProcessMessageAsync(Arg.Any>(), Arg.Any()); } [Fact] public async Task ProcessMessageAsync_WhenMessageProcessedWithError_ThrowException() { var worker = Substitute.For>(); worker.When(x => x.ProcessMessageAsync(Arg.Any>(), Arg.Any())) .Do((call) => throw new Exception()); var sut = new LiquidBackgroundService(GetProvider(worker), _consumer); await Assert.ThrowsAsync(() => sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken())); } private static ServiceProvider GetProvider(ILiquidWorker worker) { var services = new ServiceCollection(); services.AddSingleton(worker); return services.BuildServiceProvider(); } } } ================================================ FILE: test/Liquid.Core.Tests/Messaging/LiquidContextDecoratorTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.Core.Decorators; using Liquid.Core.Exceptions; using NSubstitute; using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Xunit; using Liquid.Core.Tests.Mocks; using Liquid.Core.Entities; using Microsoft.Extensions.Options; namespace Liquid.Core.Tests.Messaging { public class LiquidContextDecoratorTest { private readonly ILiquidWorker _inner; private readonly ILiquidContext _context; private readonly IOptions _options; public LiquidContextDecoratorTest() { _inner = Substitute.For>(); _context = new LiquidContext(); _options = Substitute.For>(); var settings = new ScopedContextSettings(); settings.Keys.Add(new ScopedKey() { KeyName = "test", Required = true }); settings.Culture = true; _options.Value.Returns(settings); } [Fact] public async Task ProcessMessageAsync_WhenHeaderHasRequiredContexKey_ContextKeyInserted() { var headers = new Dictionary(); headers.Add("test", "sucess"); var sut = new LiquidContextDecorator(_inner, _context, _options); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs() { Headers = headers }, new CancellationToken()); Assert.Equal(headers["test"].ToString(), _context.current["test"]); } [Fact] public async Task ProcessMessageAsync_WhenHeaderHasntRequiredContexKey_ContextKeyInserted() { var sut = new LiquidContextDecorator(_inner, _context, _options); await Assert.ThrowsAsync(() => sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken())); } [Fact] public async Task ProcessMessageAsync_WhenCultureTrue_ContextCultureCreated() { var headers = new Dictionary(); headers.Add("test", "sucess"); var sut = new LiquidContextDecorator(_inner, _context, _options); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs() { Headers = headers }, new CancellationToken()); Assert.Equal(_context.current["culture"].ToString(), CultureInfo.CurrentCulture.Name); } } } ================================================ FILE: test/Liquid.Core.Tests/Messaging/LiquidCultureDecoratorTest.cs ================================================ using Liquid.Core.Decorators; using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.Options; using NSubstitute; using System.Globalization; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Messaging { public class LiquidCultureDecoratorTest { private readonly IOptions _options; private readonly ILiquidWorker _inner; public LiquidCultureDecoratorTest() { _inner = Substitute.For>(); _options = Substitute.For>(); } [Fact] public async Task ProcessMessageAsync_CultureSettingsIsNull_CurrentCultureNotChanged() { var settings = new CultureSettings(); _options.Value.Returns(settings); var currentculture = CultureInfo.CurrentCulture.Name; var sut = new LiquidCultureDecorator(_inner, _options); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken()); Assert.Equal(currentculture, CultureInfo.CurrentCulture.Name); } [Fact] public async Task ProcessMessageAsync_CultureSettingsIsNotNull_CurrentCultureChanged() { var settings = new CultureSettings() { DefaultCulture = "pt-BR" }; _options.Value.Returns(settings); var currentculture = CultureInfo.CurrentCulture.Name; var sut = new LiquidCultureDecorator(_inner, _options); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken()); Assert.NotEqual(currentculture, CultureInfo.CurrentCulture.Name); } } } ================================================ FILE: test/Liquid.Core.Tests/Messaging/LiquidScopedLoggingDecoratorTest.cs ================================================ using Liquid.Core.Decorators; using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSubstitute; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Messaging { public class LiquidScopedLoggingDecoratorTest { private readonly ILogger> _logger; private readonly IOptions _options; private readonly ILiquidWorker _inner; public LiquidScopedLoggingDecoratorTest() { _logger = Substitute.For>>(); _options = Substitute.For>(); _inner = Substitute.For>(); var settings = new ScopedLoggingSettings(); settings.Keys.Add(new ScopedKey() { KeyName = "test", Required = true }); _options.Value.Returns(settings); } [Fact] public async Task ProcessMessageAsync_WhenHeaderHasRequiredScopedLoggingKey_ScopeCreated() { var headers = new Dictionary(); headers.Add("test", "sucess"); var sut = new LiquidScopedLoggingDecorator(_inner, _options, _logger); await sut.ProcessMessageAsync(new ConsumerMessageEventArgs() { Headers = headers }, new CancellationToken()); _logger.Received().BeginScope(Arg.Any()); } [Fact] public async Task ProcessMessageAsync_WhenHeaderHasntRequiredScopedLoggingKey_ThrowMessagingException() { var sut = new LiquidScopedLoggingDecorator(_inner, _options, _logger); await Assert.ThrowsAsync(() => sut.ProcessMessageAsync(new ConsumerMessageEventArgs(), new CancellationToken())); } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/AnotherTestEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Tests.Mocks { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class AnotherTestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/CommandHandlerMock.cs ================================================ using MediatR; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { public class CommandHandlerMock : IRequestHandler { public async Task Handle(CommandRequestMock request, CancellationToken cancellationToken) { await Task.CompletedTask; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/CommandRequestMock.cs ================================================ using MediatR; namespace Liquid.Core.Tests.Mocks { public class CommandRequestMock : IRequest { public EntityMock Entity { get; set; } public CommandRequestMock(EntityMock entity) { Entity = entity; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/EntityMock.cs ================================================ namespace Liquid.Core.Tests.Mocks { public class EntityMock { public int Property1 { get; set; } public string Property2 { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/IMockService.cs ================================================ using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { public interface IMockService { Task Get(); Task GetError(); } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/InMemoryRepository.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { [ExcludeFromCodeCoverage] public class InMemoryRepository : ILiquidRepository, ILiquidDataContext where TEntity : LiquidEntity, new() { public ILiquidDataContext DataContext { get { return this; } } public string Id { get; } private Dictionary _inMemoryRepository; private Dictionary _inMemoryTempTransactionRepository; private bool disposedValue; public InMemoryRepository() { _inMemoryRepository = new Dictionary(); } public async Task AddAsync(TEntity entity) { await Task.FromResult(_inMemoryRepository.TryAdd(entity.Id, entity)); } public async Task> FindAllAsync() { var entities = await Task.FromResult>(_inMemoryRepository.Values); return entities; } public async Task FindByIdAsync(TIdentifier id) { var entity = await Task.FromResult(_inMemoryRepository.GetValueOrDefault(id)); return entity; } public async Task RemoveByIdAsync(TIdentifier id) { await Task.FromResult(_inMemoryRepository.Remove(id)); } public async Task UpdateAsync(TEntity entity) { await Task.FromResult(_inMemoryRepository.Remove(entity.Id)); await Task.FromResult(_inMemoryRepository.TryAdd(entity.Id, entity)); } public async Task> WhereAsync(Expression> whereClause) { var selectableCollection = _inMemoryRepository.Values.AsQueryable(); var entities = await Task.FromResult>(selectableCollection.Where(whereClause)); return entities; } public async Task StartTransactionAsync() { _inMemoryTempTransactionRepository = await Task.FromResult(CloneRepository(_inMemoryRepository)); } public async Task CommitAsync() { await Task.Run(() => { _inMemoryTempTransactionRepository = null; }); } public async Task RollbackTransactionAsync() { _inMemoryRepository = await Task.FromResult(CloneRepository(_inMemoryTempTransactionRepository)); } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { _inMemoryRepository = null; _inMemoryTempTransactionRepository = null; } disposedValue = true; } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private Dictionary CloneRepository(Dictionary repoToClone) { var clone = new Dictionary(); foreach (var key in repoToClone.Keys) { clone.Add(key, repoToClone[key]); } return clone; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/MockInterceptService.cs ================================================ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { public class MockInterceptService : IMockService { public MockInterceptService() { } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task Get() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return "Test"; } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task GetError() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { throw new NotImplementedException(); } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/MockSerializeObject.cs ================================================ namespace Liquid.Core.Tests.Mocks { public class MockSerializeObject { public MockSerializeObject() { } public MockSerializeObject(int intProperty, string stringProperty) { IntProperty = intProperty; StringProperty = stringProperty; } public int IntProperty { get; set; } public string StringProperty { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/MockService.cs ================================================ using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { public class MockService : IMockService { private readonly ILogger _logger; public MockService(ILogger logger) { _logger = logger; } public MockService() { } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task Get() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { _logger.LogInformation("sucess"); return "Test"; } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task GetError() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { throw new NotImplementedException(); } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/MockSettings.cs ================================================ using Liquid.Core.Attributes; namespace Liquid.Core.Tests.Mocks { [LiquidSectionName("MockSettings")] public class MockSettings { public string MyProperty { get; set; } } public class MockNoAttributeSettings { public string MyProperty { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/MockType.cs ================================================ namespace Liquid.Core.Tests.Mocks { internal class MockType { public int PropInt { get; set; } public string PropString { get; set; } public bool PropBool { get; set; } public MockType() { } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/TestEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Core.Tests.Mocks { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class TestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Core.Tests/Mocks/WorkerMock.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System.Threading; using System.Threading.Tasks; namespace Liquid.Core.Tests.Mocks { public class WorkerMock : ILiquidWorker { public WorkerMock() { } public Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { return Task.CompletedTask; } } } ================================================ FILE: test/Liquid.Core.Tests/Repository/LiquidUnitOfWorkTests.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Core.Tests.Mocks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Liquid.Core.Tests.Repository { [ExcludeFromCodeCoverage] public class LiquidUnitOfWorkIntegrationTests { private IServiceProvider _serviceProvider; private ILiquidUnitOfWork _unitOfWork; private ILiquidRepository _sut; private readonly TestEntity _entity = new TestEntity() { CreatedDate = DateTime.Now, Active = true, Id = 1242, MockTitle = "test" }; private readonly TestEntity _updateEntity = new TestEntity() { CreatedDate = DateTime.Now, Active = false, Id = 1242, MockTitle = "test" }; public LiquidUnitOfWorkIntegrationTests() { var services = new ServiceCollection(); services.AddSingleton(Substitute.For>()); services.AddScoped, InMemoryRepository>(); services.AddTransient(); _serviceProvider = services.BuildServiceProvider(); _unitOfWork = _serviceProvider.GetService(); _sut = _unitOfWork.GetRepository, TestEntity, int>(); } [Fact] public void LiquidUnitOfWorkConstructor_WhenServiceProviderDoesntExists_ThrowException() { Assert.Throws(() => new LiquidUnitOfWork(null)); } [Fact] public void GetRepository_WhenRepositoryDoesntExists_ThrowException() { Assert.Throws(() => _unitOfWork.GetRepository, AnotherTestEntity, int>()); } [Fact] public void GetRepository_WhenRepositoryExists_Success() { var serviceProvider = new ServiceCollection() .AddTransient() .AddScoped, InMemoryRepository>() .BuildServiceProvider(); var unitOfWorkWithRepository = new LiquidUnitOfWork(serviceProvider); Assert.IsAssignableFrom>(unitOfWorkWithRepository.GetRepository, TestEntity, int>()); unitOfWorkWithRepository.Dispose(); } [Fact] public async Task AddAsync_WhenCommitTransaction_ItemAdded() { await _unitOfWork.StartTransactionAsync(); await _sut.AddAsync(_entity); await _unitOfWork.CommitAsync(); var result = await _sut.FindByIdAsync(1242); Assert.NotNull(result); } [Fact] public async Task AddAsync_WhenRollbackTransaction_ItemNotInserted() { await _unitOfWork.StartTransactionAsync(); await _sut.AddAsync(_entity); await _unitOfWork.RollbackTransactionAsync(); var result = await _sut.FindByIdAsync(1242); Assert.Null(result); } [Fact] public async Task RemoveByIdAsync_WhenCommitTransaction_ItemDeleted() { await _sut.AddAsync(_entity); await _unitOfWork.StartTransactionAsync(); await _sut.RemoveByIdAsync(_entity.Id); await _unitOfWork.CommitAsync(); var result = await _sut.WhereAsync(e => e.Id.Equals(_entity.Id)); Assert.False(result.Any()); } [Fact] public async Task RemoveByIdAsync_WhenRollbackTransaction_ItemNotDeleted() { await _sut.AddAsync(_entity); await _unitOfWork.StartTransactionAsync(); await _sut.RemoveByIdAsync(_entity.Id); await _unitOfWork.RollbackTransactionAsync(); var result = await _sut.FindByIdAsync(1242); Assert.NotNull(result); } [Fact] public async Task UpdateAsync_WhenCommitTransaction_ItemNotDeleted() { await _sut.AddAsync(_entity); await _unitOfWork.StartTransactionAsync(); await _sut.UpdateAsync(_updateEntity); await _unitOfWork.CommitAsync(); var result = await _sut.FindByIdAsync(1242); Assert.Equal(_updateEntity.Active, result.Active); } [Fact] public async Task UpdateAsync_WhenRollbackTransaction_ItemNotDeleted() { await _sut.AddAsync(_entity); await _unitOfWork.StartTransactionAsync(); await _sut.UpdateAsync(_updateEntity); await _unitOfWork.RollbackTransactionAsync(); var result = await _sut.FindByIdAsync(1242); Assert.Equal(_entity.Active, result.Active); } [Fact] public async Task StartTransactionAsync_WhenDataContextDoesntExists_ThrowException() { var serviceProvider = new ServiceCollection() .AddTransient() .BuildServiceProvider(); var unitOfWorkWithoutRepository = new LiquidUnitOfWork(serviceProvider); await Assert.ThrowsAsync(async () => await unitOfWorkWithoutRepository.StartTransactionAsync()); unitOfWorkWithoutRepository.Dispose(); } [Fact] public async Task CommitAsync_WhenNoTransactionIsStarted_ThrowException() { await Assert.ThrowsAsync(async () => await _unitOfWork.CommitAsync()); } [Fact] public async Task RollbackTransactionAsync_WhenNoTransactionIsStarted_ThrowException() { await Assert.ThrowsAsync(async () => await _unitOfWork.RollbackTransactionAsync()); } } } ================================================ FILE: test/Liquid.Core.Tests/appsettings.json ================================================ { "MockSettings": { "MyProperty": "Liquid" }, "MockNoAttributeSettings": { "MyProperty": "Liquid" }, "liquid": { "culture": { "defaultCulture": "pt-BR" } } } ================================================ FILE: test/Liquid.Core.Tests/client.ncconf ================================================ ================================================ FILE: test/Liquid.Core.Tests/config.ncconf ================================================ ================================================ FILE: test/Liquid.Core.Tests/localization.en-US.json ================================================ { "items": [ { "key": "stringValueKey", "values": [ { "value": "English text" } ] } ] } ================================================ FILE: test/Liquid.Core.Tests/localization.es-ES.json ================================================ { "items": [ { "key": "stringValueKey", "values": [ { "value": "texto en español" }, { "channels": "iphone", "value": "texto en español" } ] } ] } ================================================ FILE: test/Liquid.Core.Tests/localization.json ================================================ { "items": [ { "key": "stringValueKey", "values": [ { "channels": "web;android", "value": "Texto em português" } ] } ] } ================================================ FILE: test/Liquid.Core.Tests/tls.ncconf ================================================ certificate-name your-thumbprint false false false ================================================ FILE: test/Liquid.Dataverse.Tests/DataverseClientFactoryTests.cs ================================================ using Microsoft.Extensions.Options; using NSubstitute; namespace Liquid.Dataverse.Tests { public class DataverseClientFactoryTests { private readonly IDataverseClientFactory _sut; private readonly IOptions _options; public DataverseClientFactoryTests() { _options = Substitute.For>(); _options.Value.ReturnsForAnyArgs(new DataverseSettings() { ClientId = "4erewgewgh", ClientSecret = "greggrbnte", Url = "https://test" }); _sut = new DataverseClientFactory(_options); } [Fact] public void Ctor_WhenOptionsIsNull_ThenReturnArgumentNullException() { Assert.Throws(() => new DataverseClientFactory(null)); } [Fact] public void Ctor_WhenOptionsExists_ThenReturnDataverseClientFactoryInstance() { var result = new DataverseClientFactory(_options); Assert.NotNull(result); Assert.IsType(result); } } } ================================================ FILE: test/Liquid.Dataverse.Tests/Liquid.Dataverse.Tests.csproj ================================================  net8.0 enable enable false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Dataverse.Tests/LiquidDataverseTests.cs ================================================ using Microsoft.Crm.Sdk.Messages; using Microsoft.PowerPlatform.Dataverse.Client; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Query; using NSubstitute; using NSubstitute.ReceivedExtensions; namespace Liquid.Dataverse.Tests { public class LiquidDataverseTests { private readonly IOrganizationServiceAsync _client; private readonly ILiquidDataverse _sut; public LiquidDataverseTests() { var clientFactory = Substitute.For(); _client = Substitute.For(); clientFactory.GetClient().Returns(_client); _sut = new LiquidDataverse(clientFactory); } [Fact] public void Ctor_WhenClientFactoryIsNull_ThrowArgumentNullException() { Assert.Throws(() => new LiquidDataverse(null)); } [Fact] public async Task GetById_WhenClientReturnResults_ReturnEntity() { _client.RetrieveAsync(Arg.Any(), Arg.Any(), Arg.Any()).Returns(new Entity()); var guidId = Guid.NewGuid(); var result = await _sut.GetById(guidId, "entityname"); await _client.Received(1).RetrieveAsync("entityname", guidId, Arg.Any()); Assert.NotNull(result); Assert.IsType(result); } [Fact] public async Task ListByFilter_WhenUseFilterExpression_ReturnListOfEntities() { _client.RetrieveMultipleAsync(Arg.Any()).Returns(new EntityCollection()); var result = await _sut.ListByFilter("entityname", new FilterExpression()); Assert.NotNull(result); Assert.IsType>(result); } [Fact] public async Task ListByFilter_WhenUseQueryExpression_ReturnListOfEntities() { _client.RetrieveMultipleAsync(Arg.Any()).Returns(new EntityCollection()); var result = await _sut.ListByFilter("entityname", new QueryExpression()); Assert.NotNull(result); Assert.IsType>(result); } [Fact] public async Task GetMetadata_WhenEntityExists_ReturnEntityMetadataInstance() { var response = new RetrieveEntityResponse(); response.Results["EntityMetadata"] = new EntityMetadata(); _client.ExecuteAsync(Arg.Any()).Returns(response); var result = await _sut.GetMetadata("entityname"); Assert.NotNull(result); Assert.IsType(result); } [Fact] public async Task SetState_WhenCallResultSucessfully_ExecuteAsyncMethodCalled() { _client.ExecuteAsync(Arg.Any()).Returns(new OrganizationResponse()); var entity = new EntityReference(); await _sut.SetState(entity, "1234", "1212"); await _client.Received(1).ExecuteAsync(Arg.Any()); } [Fact] public async Task Upsert_WhenCallResultSucessfully_ExecuteAsyncMethodCalled() { _client.ExecuteAsync(Arg.Any()).Returns(new OrganizationResponse()); var entity = new Entity(); await _sut.Upsert(entity); await _client.Received(1).ExecuteAsync(Arg.Any()); } [Fact] public async Task Update_WithOptions_UseRightOrganizationRequestType_RequestParameters() { _client.UpdateAsync(Arg.Any()).Returns(Task.CompletedTask); var updatedEntity = new Entity(); await _sut.Update(updatedEntity, true, true, true); await _client.Received(1).ExecuteAsync(Arg.Is(ur => ur.Parameters.ContainsKey("BypassCustomPluginExecution") && ur.Parameters.ContainsKey("SuppressCallbackRegistrationExpanderJob") && ur.Parameters.ContainsKey("SuppressDuplicateDetection") )); } [Fact] public async Task DeleteById_WithOptions_UseRightOrganizationRequestType_RequestParameters() { _client.DeleteAsync(Arg.Any(), Arg.Any()).Returns(Task.CompletedTask); var guidId = Guid.NewGuid(); await _sut.Delete(guidId, "entityname", true, true); await _client.Received(1).ExecuteAsync(Arg.Is(dr => dr.Parameters.ContainsKey("BypassCustomPluginExecution") )); } [Fact] public async Task Create_WithOptions_UseRightOrganizationRequestType_RequestParameters() { _client.Execute(Arg.Any()).Returns(new CreateResponse()); var result = await _sut.Create(new Entity(), false, false, true); _client.Received(1).Execute(Arg.Is(cr => cr.Parameters.ContainsKey("BypassCustomPluginExecution") && cr.Parameters.ContainsKey("SuppressCallbackRegistrationExpanderJob") && cr.Parameters.ContainsKey("SuppressDuplicateDetection") )); Assert.IsType(result); } } } ================================================ FILE: test/Liquid.Dataverse.Tests/Usings.cs ================================================ global using Xunit; ================================================ FILE: test/Liquid.GenAi.OpenAi.Tests/Liquid.GenAi.OpenAi.Tests.csproj ================================================  net8.0 enable enable false true all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: test/Liquid.GenAi.OpenAi.Tests/OpenAiClientFactoryTests.cs ================================================ using Liquid.Core.Entities; using Liquid.GenAi.OpenAi.Settings; using Microsoft.Extensions.Options; using NSubstitute; using OpenAI.Chat; using System.Reflection; namespace Liquid.GenAi.OpenAi.Tests { public class OpenAiClientFactoryTests { private readonly IOptions _mockOptions; private readonly OpenAiClientFactory _factory; public OpenAiClientFactoryTests() { _mockOptions = Substitute.For>(); _factory = new OpenAiClientFactory(_mockOptions); } [Fact] public void Constructor_ShouldThrowArgumentNullException_WhenOptionsIsNull() { // Act & Assert Assert.Throws(() => new OpenAiClientFactory(null)); } [Fact] public void GetOpenAIClient_ShouldThrowKeyNotFoundException_WhenNoSettingsForClientId() { // Arrange _mockOptions.Value.Returns(new OpenAiOptions { Settings = new List() }); // Act & Assert Assert.Throws(() => _factory.GetOpenAIClient("invalid-client-id")); } [Fact] public void GetOpenAIClient_ShouldReturnExistingClient_WhenClientAlreadyExists() { // Arrange var clientId = "test-client"; var chatClient = Substitute.For(); var clientDictionary = new ClientDictionary(clientId, chatClient) { Executions = 0 }; var settings = new List { new OpenAiSettings { ClientId = clientId, Url = "https://example.com", Key = "test-key", DeploymentName = "test-deployment" } }; _mockOptions.Value.Returns(new OpenAiOptions { Settings = settings }); // Simulate an existing client _factory.GetType() .GetField("_openAiClients", BindingFlags.NonPublic | BindingFlags.Instance) ?.SetValue(_factory, new List> { clientDictionary }); // Act var result = _factory.GetOpenAIClient(clientId); // Assert Assert.Equal(chatClient, result); Assert.Equal(1, clientDictionary.Executions); } [Fact] public void GetOpenAIClient_ShouldCreateAndReturnNewClient_WhenClientDoesNotExist() { // Arrange var clientId = "new-client"; var settings = new List { new OpenAiSettings { ClientId = clientId, Url = "https://example.com", Key = "test-key", DeploymentName = "test-deployment", MaxRetries = 3 } }; _mockOptions.Value.Returns(new OpenAiOptions { Settings = settings }); // Act var result = _factory.GetOpenAIClient(clientId); // Assert Assert.NotNull(result); } [Fact] public void CreateClient_ShouldThrowArgumentNullException_WhenSettingsIsNull() { // Act & Assert Assert.Throws(() => _factory.GetType() .GetMethod("CreateClient", BindingFlags.NonPublic | BindingFlags.Instance) ?.Invoke(_factory, new object[] { null, "test-client" })); } [Fact] public void CreateClient_ShouldThrowArgumentNullException_WhenSettingsIsEmpty() { // Act & Assert Assert.Throws(() => _factory.GetType() .GetMethod("CreateClient", BindingFlags.NonPublic | BindingFlags.Instance) ?.Invoke(_factory, [new List(), "test-client"])); } [Fact] public void CreateClient_ShouldCreateClients_WhenValidSettingsProvided() { // Arrange var clientId = "test-client"; var settings = new List { new OpenAiSettings { ClientId = clientId, Url = "https://example.com", Key = "test-key", DeploymentName = "test-deployment", MaxRetries = 3 } }; // Act var result = _factory.GetType() .GetMethod("CreateClient", BindingFlags.NonPublic | BindingFlags.Instance) ?.Invoke(_factory, new object[] { settings, clientId }); // Assert Assert.NotNull(result); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Extensions.DependencyInjection; using Liquid.Messaging.Kafka.Tests.Mock; using Liquid.Messaging.Kafka.Tests.Mock.HandlerMock; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Messaging.Kafka.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidKafkaProducer_WhenSuccessfullyInjectProducer_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidKafkaProducer("test"); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidProducer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidKafkaProducer_WhenSuccessfullyInjectWhitoutTelemetry_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidKafkaProducer("test", false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidProducer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidKafkaConsumer_WhenSuccessfullyInjectConsumer_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidKafkaConsumer("test"); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService>()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IHostedService) && x.ImplementationType == typeof(LiquidBackgroundService))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidConsumer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidKafkaConsumer_WhenSuccessfullyInjectConsumerAndHandlers_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidKafkaConsumer("test", false, typeof(MockRequest).Assembly); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidWorker))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IHostedService) && x.ImplementationType == typeof(LiquidBackgroundService))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidConsumer) && x.Lifetime == ServiceLifetime.Singleton)); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/KafkaFactoryTest.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Messaging.Kafka.Settings; using Xunit; namespace Liquid.Messaging.Kafka.Tests { public class KafkaFactoryTest { private KafkaSettings _settings; private readonly IKafkaFactory _sut; public KafkaFactoryTest() { _sut = new KafkaFactory(); } [Fact] public void GetConsumer_WhenSettingsINull_ThrowLiquidMessagingException() { Assert.Throws(() => _sut.GetConsumer(_settings)); } [Fact] public void GetProducer_WhenSettingsIsNull_ThrowLiquidMessagingException() { Assert.Throws(() => _sut.GetProducer(_settings)); } [Fact] public void GetConsumer_WhenSettingsIsNotValid_ThrowLiquidMessagingException() { _settings = new KafkaSettings() { ConnectionId = "test" }; Assert.Throws(() => _sut.GetConsumer(_settings)); } [Fact] public void GetProducer_WhenSettingsIsValid_ResposeIsNotNull() { _settings = new KafkaSettings(); var producer = _sut.GetProducer(_settings); Assert.NotNull(producer); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/KafkaProducerTest.cs ================================================ using Confluent.Kafka; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Settings; using Liquid.Messaging.Kafka.Tests.Mock; using Microsoft.Extensions.Options; using NSubstitute; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Messaging.Kafka.Tests { public class KafkaProducerTest { private readonly ILiquidProducer _sut; private IKafkaFactory _factory; private IProducer _client; private IOptions _settings; public KafkaProducerTest() { _client = Substitute.For>(); _factory = Substitute.For(); _factory.GetProducer(Arg.Any()).Returns(_client); _settings = Substitute.For>(); _settings.Value.Returns(new KafkaSettings()); _sut = new KafkaProducer(_settings, _factory); } [Fact] public async Task SendMessageAsync_WhenMessageSended_ClientReceivedCall() { var message = new MessageMock(); await _sut.SendMessageAsync(message); await _client.Received(1).ProduceAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Fact] public async Task SendMessageAsync_WhenSendMessageFail_ThrowException() { var message = new MessageMock(); _client.When(x => x.ProduceAsync(Arg.Any(), Arg.Any>(), Arg.Any())) .Do((call) => throw new Exception()); var task = _sut.SendMessageAsync(message); await Assert.ThrowsAsync(() => task); } [Fact] public async Task SendMessagesAsync_WhenMessagesSended_ClientReceivedCall() { var message = new MessageMock(); var messages = new List(); messages.Add(message); messages.Add(message); await _sut.SendMessagesAsync(messages); await _client.Received(2).ProduceAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } [Fact] public async Task SendMessagesAsync_WhenSendMessagesFail_ThrowException() { var message = new MessageMock(); var messages = new List(); messages.Add(message); messages.Add(message); _client.When(x => x.ProduceAsync(Arg.Any(), Arg.Any>(), Arg.Any())) .Do((call) => throw new Exception()); var task = _sut.SendMessagesAsync(messages); await Assert.ThrowsAsync(() => task); } [Fact] public void Ctor_WhenRabbitMqFactoryIsNull_ThrowException() { Assert.Throws(() => new KafkaProducer(_settings, null)); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Liquid.Messaging.Kafka.Tests.csproj ================================================  net8.0 false True runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/HandlerMock/MockCommandHandler.cs ================================================ using MediatR; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.Kafka.Tests.Mock.HandlerMock { public class MockCommandHandler : IRequestHandler { public Task Handle(MockRequest request, CancellationToken cancellationToken) { return Task.CompletedTask; } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/HandlerMock/MockRequest.cs ================================================ using MediatR; namespace Liquid.Messaging.Kafka.Tests.Mock.HandlerMock { public class MockRequest : IRequest { public MessageMock Message { get; set; } public MockRequest(MessageMock message) { Message = message; } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/HandlerMock/MockValidator.cs ================================================ using FluentValidation; namespace Liquid.Messaging.Kafka.Tests.Mock.HandlerMock { public class MockValidator : AbstractValidator { public MockValidator() { RuleFor(request => request.Message.TestMessageId).NotEmpty().NotNull(); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/MessageMock.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.Kafka.Tests.Mock { /// /// Test Message Class. /// [ExcludeFromCodeCoverage] public class MessageMock { /// /// Gets or sets the test message identifier. /// /// /// The test message identifier. /// public int TestMessageId { get; set; } /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } /// /// Gets or sets the amount. /// /// /// The amount. /// public double Amount { get; set; } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/WorkerMediatorMock.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Liquid.Messaging.Kafka.Tests.Mock.HandlerMock; using MediatR; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.Kafka.Tests.Mock { public class WorkerMediatorMock : ILiquidWorker { private readonly IMediator _mediator; public WorkerMediatorMock(IMediator mediator) { _mediator = mediator; } public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { await _mediator.Send(new MockRequest(args.Data)); } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/Mock/WorkerMock .cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.Kafka.Tests.Mock { public class WorkerMock : ILiquidWorker { #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return; } } } ================================================ FILE: test/Liquid.Messaging.Kafka.Tests/kafkaConsumerTest.cs ================================================ using Confluent.Kafka; using Liquid.Core.Extensions; using Liquid.Core.Utils; using Liquid.Core.Exceptions; using Liquid.Messaging.Kafka.Settings; using Liquid.Messaging.Kafka.Tests.Mock; using NSubstitute; using System; using System.Threading; using System.Threading.Tasks; using Xunit; using Liquid.Core.Entities; using Microsoft.Extensions.Options; namespace Liquid.Messaging.Kafka.Tests { public class kafkaConsumerTest : KafkaConsumer { public static readonly IKafkaFactory _factory = Substitute.For(); public static readonly IOptions _settings = GetOptions(); public static IOptions GetOptions() { var settings = Substitute.For>(); settings.Value.Returns(new KafkaSettings()); return settings; } public kafkaConsumerTest() : base(_factory, _settings) { } [Fact] public void RegisterMessageHandler_WhenRegisteredSucessfully_BasicConsumeReceivedCall() { var messageReceiver = Substitute.For>(); _factory.GetConsumer(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; RegisterMessageHandler(); Assert.True(RegisterHandleMock()); } [Fact] public async Task RegisterMessageHandler_WhenRegistereFail_ThrowException() { var messageReceiver = Substitute.For>(); _factory.GetConsumer(Arg.Any()).Returns(messageReceiver); await Assert.ThrowsAsync(() => RegisterMessageHandler(new CancellationToken())); } [Fact] public async Task MessageHandler_WhenProcessExecutedSucessfully() { var message = new ConsumeResult(); var entity = new MessageMock() { TestMessageId = 1 }; var messageObj = new Message(); messageObj.Value = entity.ToJsonString(); message.Message = messageObj; message.Message.Headers = new Headers(); var messageReceiver = Substitute.For>(); messageReceiver.Consume(Arg.Any()).Returns(message); _factory.GetConsumer(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; var sut = RegisterMessageHandler(new CancellationToken()); Assert.NotNull(sut); } [Fact] public async Task MessageHandler_WhenProcessExecutionFail_ThrowException() { var message = new ConsumeResult(); var entity = new MessageMock() { TestMessageId = 2 }; var messageObj = new Message(); messageObj.Value = entity.ToJsonString(); message.Message = messageObj; message.Message.Headers = new Headers(); var messageReceiver = Substitute.For>(); messageReceiver.Consume(Arg.Any()).Returns(message); _factory.GetConsumer(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; ProcessErrorAsync += ProcessErrorAsyncMock; var sut = MessageHandler(new CancellationToken()); await Assert.ThrowsAsync(() => sut); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private async Task ProcessMessageAsyncMock(ConsumerMessageEventArgs args, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (args.Data.TestMessageId == 2) throw new Exception(); } private async Task ProcessErrorAsyncMock(ConsumerErrorEventArgs args) { throw args.Exception; } private bool RegisterHandleMock() { try { RegisterMessageHandler(); return true; } catch { return false; } } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/IServiceCollectionExtensionTest.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Messaging.RabbitMq.Extensions.DependencyInjection; using Liquid.Messaging.RabbitMq.Tests.Mock; using Liquid.Messaging.RabbitMq.Tests.Mock.HandlerMock; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using NSubstitute; using System.Linq; using Xunit; namespace Liquid.Messaging.RabbitMq.Tests { public class IServiceCollectionExtensionTest { private IServiceCollection _sut; private IConfiguration _configProvider = Substitute.For(); private IConfigurationSection _configurationSection = Substitute.For(); private void SetCollection() { _configProvider.GetSection(Arg.Any()).Returns(_configurationSection); _sut = new ServiceCollection(); _sut.AddSingleton(_configProvider); } [Fact] public void AddLiquidRabbitMqProducer_WhenSuccessfullyInjectProducer_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidRabbitMqProducer("test"); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidProducer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidRabbitMqProducer_WhenSuccessfullyInjectWhitoutTelemetry_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidRabbitMqProducer("test", false); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidProducer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidRabbitMqConsumer_WhenSuccessfullyInjectConsumer_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidRabbitMqConsumer("test"); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService>()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IHostedService) && x.ImplementationType == typeof(LiquidBackgroundService))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidConsumer) && x.Lifetime == ServiceLifetime.Singleton)); } [Fact] public void AddLiquidRabbitMqConsumer_WhenSuccessfullyInjectConsumerAndHandlers_GetServicesSucessfully() { SetCollection(); _sut.AddLiquidRabbitMqConsumer("test", false, typeof(MockRequest).Assembly); var provider = _sut.BuildServiceProvider(); Assert.NotNull(provider.GetService()); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidWorker))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(IHostedService) && x.ImplementationType == typeof(LiquidBackgroundService))); Assert.NotNull(_sut.FirstOrDefault(x => x.ServiceType == typeof(ILiquidConsumer) && x.Lifetime == ServiceLifetime.Singleton)); } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Liquid.Messaging.RabbitMq.Tests.csproj ================================================  net8.0 false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/HandlerMock/MockCommandHandler.cs ================================================ using MediatR; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.RabbitMq.Tests.Mock.HandlerMock { public class MockCommandHandler : IRequestHandler { public Task Handle(MockRequest request, CancellationToken cancellationToken) { return Task.CompletedTask; } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/HandlerMock/MockRequest.cs ================================================ using MediatR; namespace Liquid.Messaging.RabbitMq.Tests.Mock.HandlerMock { public class MockRequest : IRequest { public MessageMock Message { get; set; } public MockRequest(MessageMock message) { Message = message; } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/HandlerMock/MockValidator.cs ================================================ using FluentValidation; namespace Liquid.Messaging.RabbitMq.Tests.Mock.HandlerMock { public class MockValidator : AbstractValidator { public MockValidator() { RuleFor(request => request.Message.TestMessageId).NotEmpty().NotNull(); } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/MessageMock.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Messaging.RabbitMq.Tests.Mock { /// /// Test Message Class. /// [ExcludeFromCodeCoverage] public class MessageMock { /// /// Gets or sets the test message identifier. /// /// /// The test message identifier. /// public int TestMessageId { get; set; } /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } /// /// Gets or sets the amount. /// /// /// The amount. /// public double Amount { get; set; } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/WorkerMediatorMock.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using Liquid.Messaging.RabbitMq.Tests.Mock.HandlerMock; using MediatR; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.RabbitMq.Tests.Mock { public class WorkerMediatorMock : ILiquidWorker { private readonly IMediator _mediator; public WorkerMediatorMock(IMediator mediator) { _mediator = mediator; } public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) { await _mediator.Send(new MockRequest(args.Data)); } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/Mock/WorkerMock .cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Interfaces; using System.Threading; using System.Threading.Tasks; namespace Liquid.Messaging.RabbitMq.Tests.Mock { public class WorkerMock : ILiquidWorker { #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task ProcessMessageAsync(ConsumerMessageEventArgs args, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return; } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/RabbitMqConsumerTest.cs ================================================ using Liquid.Core.Entities; using Liquid.Core.Extensions; using Liquid.Messaging.RabbitMq.Settings; using Liquid.Messaging.RabbitMq.Tests.Mock; using Microsoft.Extensions.Options; using NSubstitute; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Messaging.RabbitMq.Tests { public class RabbitMqConsumerTest : RabbitMqConsumer { public static readonly IRabbitMqFactory _factory = Substitute.For(); public static IOptions _settings = GetOptions(); public static RabbitMqConsumerSettings _settingsValue; public static IOptions GetOptions() { var settings = Substitute.For>(); _settingsValue = new RabbitMqConsumerSettings { CompressMessage = true, Exchange = "test", Queue = "test", AdvancedSettings = new AdvancedSettings { AutoAck = false, QueueAckModeSettings = new QueueAckModeSettings() { QueueAckMode = QueueAckModeEnum.BasicAck, Requeue = true } } }; settings.Value.Returns(_settingsValue); return settings; } public RabbitMqConsumerTest() : base(_factory, _settings) { var model = Substitute.For(); _factory.GetReceiver(Arg.Any()).Returns(model); } [Fact] public void Constructor_WhenFactoryIsNull_ThrowArgumentNullException() { Assert.Throws(() => new RabbitMqConsumer(null, _settings)); } [Fact] public void Constructor_WhenSettingsIsNull_ThrowArgumentNullException() { Assert.Throws(() => new RabbitMqConsumer(_factory, null)); } [Fact] public void Constructor_WhenSettingsValueIsNull_ThrowArgumentNullException() { var settings = Substitute.For>(); settings.Value.Returns((RabbitMqConsumerSettings)null); Assert.Throws(() => new RabbitMqConsumer(_factory, settings)); } [Fact] public void RegisterMessageHandler_WhenRegisteredSucessfully_BasicConsumeReceivedCall() { var messageReceiver = Substitute.For(); _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; RegisterMessageHandler(); Assert.True(RegisterHandleMock()); } [Fact] public async Task RegisterMessageHandler_WhenRegistereFail_ThrowException() { var messageReceiver = Substitute.For(); _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); await Assert.ThrowsAsync(() => RegisterMessageHandler()); } [Fact] public async Task MessageHandler_WhenProcessExecutedSucessfully() { var message = new BasicDeliverEventArgs(); var entity = new MessageMock() { TestMessageId = 1 }; message.Body = entity.ToJsonBytes(); var messageReceiver = Substitute.For(); _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; await RegisterMessageHandler(); await MessageHandler(message, new CancellationToken()); } [Fact] public async Task MessageHandler_CallsConsumeMessageAsync_AndAcks_WhenAutoAckIsFalse() { var message = new BasicDeliverEventArgs(); var entity = new MessageMock() { TestMessageId = 1 }; message.Body = entity.ToJsonBytes(); var messageReceiver = Substitute.For(); ConsumeMessageAsync += ProcessMessageAsyncMock; _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); await RegisterMessageHandler(); await MessageHandler(message, new CancellationToken()); messageReceiver.Received(1).BasicAck(message.DeliveryTag, false); } [Fact] public async Task MessageHandler_CallsConsumeMessageAsync_AndNacks_WhenAutoAckIsFalse() { var message = new BasicDeliverEventArgs(); var entity = new MessageMock() { TestMessageId = 2 }; message.Body = entity.ToJsonBytes(); var messageReceiver = Substitute.For(); ConsumeMessageAsync += ProcessMessageAsyncMock; _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); await RegisterMessageHandler(); await MessageHandler(message, new CancellationToken()); messageReceiver.Received(1).BasicNack(message.DeliveryTag, false, true); } [Fact] public async Task MessageHandler_CallsConsumeMessageAsync_AndRejects_WhenAutoAckIsFalse() { var message = new BasicDeliverEventArgs(); var entity = new MessageMock() { TestMessageId = 2 }; message.Body = entity.ToJsonBytes(); var messageReceiver = Substitute.For(); ConsumeMessageAsync += ProcessMessageAsyncMock; _factory.GetReceiver(Arg.Any()).Returns(messageReceiver); _settingsValue = new RabbitMqConsumerSettings { CompressMessage = true, Exchange = "test", Queue = "test", AdvancedSettings = new AdvancedSettings { AutoAck = false, QueueAckModeSettings = new QueueAckModeSettings() { QueueAckMode = QueueAckModeEnum.BasicReject, Requeue = true } } }; _settings.Value.Returns(_settingsValue); await RegisterMessageHandler(); await MessageHandler(message, new CancellationToken()); messageReceiver.Received(1).BasicReject(message.DeliveryTag, true); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private async Task ProcessMessageAsyncMock(ConsumerMessageEventArgs args, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (args.Data.TestMessageId == 2) throw new Exception(); } private bool RegisterHandleMock() { try { RegisterMessageHandler(); return true; } catch { return false; } } } } ================================================ FILE: test/Liquid.Messaging.RabbitMq.Tests/RabbitMqProducerTest.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.RabbitMq.Settings; using Liquid.Messaging.RabbitMq.Tests.Mock; using Microsoft.Extensions.Options; using NSubstitute; using RabbitMQ.Client; using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Liquid.Messaging.RabbitMq.Tests { public class RabbitMqProducerTest { private readonly ILiquidProducer _sut; private IRabbitMqFactory _factory; private IModel _client; private IOptions _settings; public RabbitMqProducerTest() { _client = Substitute.For(); _factory = Substitute.For(); _factory.GetSender(Arg.Any()).Returns(_client); _settings = Substitute.For>(); _settings.Value.Returns(new RabbitMqProducerSettings()); _sut = new RabbitMqProducer(_factory, _settings); } [Fact] public async Task SendMessageAsync_WhenMessageSended_BasicPublishReceivedCall() { var message = new MessageMock(); await _sut.SendMessageAsync(message); _client.Received(1).BasicPublish(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any>()); } [Fact] public async Task SendMessageAsync_WhenSendMessageFail_ThrowException() { var message = new MessageMock(); _client.When(x => x.BasicPublish(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any>())) .Do((call) => throw new Exception()); var task = _sut.SendMessageAsync(message); await Assert.ThrowsAsync(() => task); } [Fact] public async Task SendMessagesAsync_WhenMessagesSended_BasicPublishReceivedCall() { var message = new MessageMock(); var messages = new List(); messages.Add(message); messages.Add(message); await _sut.SendMessagesAsync(messages); _client.Received(2).BasicPublish(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any>()); } [Fact] public async Task SendMessagesAsync_WhenSendMessagesFail_ThrowException() { var message = new MessageMock(); var messages = new List(); messages.Add(message); messages.Add(message); _client.When(x => x.BasicPublish(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any>())) .Do((call) => throw new Exception()); var task = _sut.SendMessagesAsync(messages); await Assert.ThrowsAsync(() => task); } [Fact] public void Ctor_WhenRabbitMqFactoryIsNull_ThrowException() { Assert.Throws(() => new RabbitMqProducer(null, _settings)); } [Fact] public void Ctor_WhenProducerSettingsIsNull_ThrowException() { Assert.Throws(() => new RabbitMqProducer(_factory, null)); } } } ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/Liquid.Messaging.ServiceBus.Tests.csproj ================================================ net8.0 false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/Mock/EntityMock.cs ================================================ using System; namespace Liquid.Messaging.ServiceBus.Tests.Mock { [Serializable] public class EntityMock { public int Id { get; set; } public string MyProperty { get; set; } } } ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/ServiceBusConsumerTest.cs ================================================ using Azure.Core.Amqp; using Azure.Messaging.ServiceBus; using Liquid.Core.Entities; using Liquid.Core.Exceptions; using Liquid.Core.Extensions; using Liquid.Messaging.ServiceBus.Tests.Mock; using NSubstitute; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Liquid.Messaging.ServiceBus.Tests { public class ServiceBusConsumerTest : ServiceBusConsumer { private static readonly IServiceBusFactory _factory = Substitute.For(); private static readonly ServiceBusProcessor _processor = Substitute.For(); private static readonly ServiceBusReceiver _receiver = Substitute.For(); public ServiceBusConsumerTest() : base(_factory, "test") { _factory.GetProcessor(Arg.Any()).Returns(_processor); } [Fact] public async Task RegisterMessageHandler_WhenRegisteredSucessfully_StartProcessingReceivedCall() { var messageReceiver = Substitute.For(); _factory.GetProcessor(Arg.Any()).Returns(messageReceiver); ConsumeMessageAsync += ProcessMessageAsyncMock; await RegisterMessageHandler(); await messageReceiver.Received(1).StartProcessingAsync(); } [Fact] public async Task RegisterMessageHandler_WhenConsumeMessageAssyncIsNull_ThrowNotImplementedException() { var messageReceiver = Substitute.For(); _factory.GetProcessor(Arg.Any()).Returns(messageReceiver); await Assert.ThrowsAsync(() => RegisterMessageHandler()); } [Fact] public async Task MessageHandler_WhenProcessExecutedSucessfully() { var entity = new EntityMock() { Id = 1, MyProperty = "test" }; var readOnly = new ReadOnlyMemory(entity.ToJsonBytes()); var convertEntity = new List>(); convertEntity.Add(readOnly); var amqp = new AmqpAnnotatedMessage(new AmqpMessageBody(convertEntity)); BinaryData binary = default; var message = ServiceBusReceivedMessage.FromAmqpMessage(amqp, binary); var processMessage = new ProcessMessageEventArgs(message, _receiver, new CancellationToken()); ConsumeMessageAsync += ProcessMessageAsyncMock; await MessageHandler(processMessage); } [Fact] public async Task ErrorHandler_WhenProcessErrorExecuted_ThrowsMessagingConsumerException() { var processError = new ProcessErrorEventArgs(new Exception("teste"), ServiceBusErrorSource.ProcessMessageCallback, "teste" , "testqueue", Guid.NewGuid().ToString(), new CancellationToken()); ProcessErrorAsync += ProcessError; await Assert.ThrowsAsync(() => ErrorHandler(processError)); } [Fact] public void Constructor_WhenFactoryIsNull_ThrowsArgumentNullException() { Assert.Throws(() => new ServiceBusConsumer(null, "test")); } [Fact] public void Constructor_WhenSettingsNameIsNull_ThrowsArgumentNullException() { Assert.Throws(() => new ServiceBusConsumer(_factory, null)); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously private async Task ProcessMessageAsyncMock(ConsumerMessageEventArgs args, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { } } } ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/ServiceBusFactoryTest.cs ================================================ using Liquid.Core.Exceptions; using Liquid.Messaging.ServiceBus.Settings; using Microsoft.Extensions.Options; using NSubstitute; using System; using System.Collections.Generic; using Xunit; namespace Liquid.Messaging.ServiceBus.Tests { public class ServiceBusFactoryTest { private readonly IOptions _configuration; private readonly List _settings; private readonly IServiceBusFactory _sut; public ServiceBusFactoryTest() { _configuration = Substitute.For>(); _settings = new List(); _settings.Add(new ServiceBusEntitySettings { EntityPath = "test" }); _configuration.Value.Returns(new ServiceBusSettings() { Settings = _settings }); _sut = new ServiceBusFactory(_configuration); } [Fact] public void Ctor_WhenOptionsValueIsNull_ThrowArgumentNullException() { Assert.Throws(() => new ServiceBusFactory(Substitute.For>())); } [Fact] public void GetProcessor_WhenPeekModeAndConnectionStringIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test" }); Assert.Throws(() => _sut.GetProcessor("test")); } [Fact] public void GetProcessor_WhenReceiveModeAndConnectionStringIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test", PeekLockMode = false }); Assert.Throws(() => _sut.GetProcessor("test")); } [Fact] public void GetReceiver_WhenPeekModeAndConnectionIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test" }); Assert.Throws(() => _sut.GetReceiver("test")); } [Fact] public void GetReceiver_WhenReceiveModeAndConnectionIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test", PeekLockMode = false }); Assert.Throws(() => _sut.GetReceiver("test")); } [Fact] public void GetSender_WhenPeekModeAndConnectionIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test" }); Assert.Throws(() => _sut.GetSender("test")); } [Fact] public void GetSender_WhenReceiveModeAndConnectionIsMissing_ThrowMessagingMissingConfigurationException() { _settings.Add(new ServiceBusEntitySettings { EntityPath = "test", PeekLockMode = false }); Assert.Throws(() => _sut.GetSender("test")); } } } ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/ServiceBusFactoryTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Azure.Messaging.ServiceBus; using Liquid.Core.Exceptions; using Liquid.Messaging.ServiceBus; using Liquid.Messaging.ServiceBus.Settings; using Microsoft.Extensions.Options; using NSubstitute; using Xunit; namespace Liquid.Messaging.ServiceBus.Tests { public class ServiceBusFactoryTests { private readonly ServiceBusSettings _settings; private readonly IOptions _options; public ServiceBusFactoryTests() { _settings = new ServiceBusSettings { Settings = new List { new ServiceBusEntitySettings { EntityPath = "queue1", ConnectionString = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key", PeekLockMode = true, MaxConcurrentCalls = 5, Subscription = null }, new ServiceBusEntitySettings { EntityPath = "topic1", ConnectionString = "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=key", PeekLockMode = false, MaxConcurrentCalls = 2, Subscription = "sub1" } } }; _options = Substitute.For>(); _options.Value.Returns(_settings); } [Fact] public void GetSender_ReturnsSender_WhenConfigExists() { // Arrange var factory = new ServiceBusFactory(_options); // Act var sender = factory.GetSender("queue1"); // Assert Assert.NotNull(sender); Assert.IsType(sender); } [Fact] public void GetSender_ThrowsArgumentOutOfRangeException_WhenConfigMissing() { // Arrange var factory = new ServiceBusFactory(_options); // Act & Assert var ex = Assert.Throws(() => factory.GetSender("notfound")); Assert.Contains("notfound", ex.Message); } [Fact] public void GetProcessor_ReturnsProcessor_WhenQueueConfigExists() { // Arrange var factory = new ServiceBusFactory(_options); // Act var processor = factory.GetProcessor("queue1"); // Assert Assert.NotNull(processor); Assert.IsType(processor); } [Fact] public void GetProcessor_ReturnsProcessor_WhenTopicConfigExists() { // Arrange var factory = new ServiceBusFactory(_options); // Act var processor = factory.GetProcessor("topic1"); // Assert Assert.NotNull(processor); Assert.IsType(processor); } [Fact] public void GetProcessor_ThrowsMessagingMissingConfigurationException_WhenConfigMissing() { // Arrange var factory = new ServiceBusFactory(_options); // Act & Assert var ex = Assert.Throws(() => factory.GetProcessor("notfound")); Assert.Contains("notfound", ex.Message); } [Fact] public void GetReceiver_ReturnsReceiver_WhenConfigExists() { // Arrange var factory = new ServiceBusFactory(_options); // Act var receiver = factory.GetReceiver("queue1"); // Assert Assert.NotNull(receiver); Assert.IsType(receiver); } [Fact] public void GetReceiver_ThrowsArgumentOutOfRangeException_WhenConfigMissing() { // Arrange var factory = new ServiceBusFactory(_options); // Act & Assert var ex = Assert.Throws(() => factory.GetReceiver("notfound")); Assert.Contains("notfound", ex.Message); } } } ================================================ FILE: test/Liquid.Messaging.ServiceBus.Tests/ServiceBusProducerTest.cs ================================================ using Azure.Messaging.ServiceBus; using Liquid.Core.Exceptions; using Liquid.Core.Interfaces; using Liquid.Messaging.ServiceBus.Tests.Mock; using NSubstitute; using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Liquid.Messaging.ServiceBus.Tests { public class ServiceBusProducerTest { private readonly ILiquidProducer _sut; private readonly IServiceBusFactory _serviceBusFactory; private readonly ServiceBusSender _client; private readonly EntityMock _message; public ServiceBusProducerTest() { _client = Substitute.For(); _serviceBusFactory = Substitute.For(); _serviceBusFactory.GetSender(Arg.Any()).Returns(_client); _sut = new ServiceBusProducer(_serviceBusFactory, "test"); _message = new EntityMock() { Id = 1, MyProperty = "test" }; } [Fact] public async Task SendAsync_WhenSingleEntitySendedSuccessfully_ClientReceivedCall() { var customProperties = new Dictionary(); customProperties.Add("test", 123); await _sut.SendMessageAsync(_message, customProperties); await _client.Received(1).SendMessageAsync(Arg.Any()); } [Fact] public async Task SendAsync_WhenListEntitiesSendedSuccessfully_ClientReceivedCall() { var entities = new List() { _message }; await _sut.SendMessagesAsync(entities); await _client.Received(1).SendMessagesAsync(Arg.Any>()); } [Fact] public async Task SendAsync_WhenSingleEntitySendFailed_ThrowError() { _client.When(x => x.SendMessageAsync(Arg.Any())).Do((call) => throw new Exception()); var sut = _sut.SendMessageAsync(_message, new Dictionary()); await Assert.ThrowsAsync(() => sut); } [Fact] public async Task SendAsync_WhenListEntitiesSendFailed_ThrowError() { var entities = new List() { _message }; _client.When(x => x.SendMessagesAsync(Arg.Any>())).Do((call) => throw new Exception()); var sut = _sut.SendMessagesAsync(entities); await Assert.ThrowsAsync(() => sut); } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/Configurations/MockTypeConfiguration.cs ================================================ using Liquid.Repository.EntityFramework.Tests.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Diagnostics.CodeAnalysis; namespace Liquid.Data.EntityFramework.Tests.Configurations { [ExcludeFromCodeCoverage] public class MockTypeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.Property(o => o.Id).ValueGeneratedOnAdd(); builder.Property(o => o.MockTitle).IsRequired().HasMaxLength(50); builder.Property(o => o.CreatedDate).IsRequired(); builder.Property(o => o.Active).IsRequired(); } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/Entities/MockEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.EntityFramework.Tests.Entities { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class MockEntity : LiquidEntity { /// /// Gets or sets the mock identifier. /// /// /// The mock identifier. /// public int MockId { get => Id; set => Id = value; } /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } public MockSubEntity SubEntity { get; set; } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/Entities/MockSubEntity.cs ================================================ namespace Liquid.Repository.EntityFramework.Tests.Entities { public class MockSubEntity { public int Id { get; set; } public string Name { get; set; } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/EntityFrameworkDataContextTests.cs ================================================ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; namespace Liquid.Repository.EntityFramework.Tests { [ExcludeFromCodeCoverage] public class EntityFrameworkDataContextTests { private EntityFrameworkDataContext _sut; private DbContext _client; private DatabaseFacade _database; public EntityFrameworkDataContextTests() { _client = Substitute.For(); _database = Substitute.For(_client); _client.Database.Returns(_database); _sut = new EntityFrameworkDataContext(_client); } [Fact] public async Task StartTransactionAsync_WhenClientExecutedSucessfuly_Success() { await _sut.StartTransactionAsync(); await _client.Database.Received(1).BeginTransactionAsync(); } [Fact] public async Task StartTransactionAsync_WhenClientThrow_ThrowException() { _database.When(o => o.BeginTransactionAsync()).Do((call) => throw new Exception()); var task = _sut.StartTransactionAsync(); await Assert.ThrowsAsync(() => task); } [Fact] public async Task CommitAsync_WhenClientExecutedSucessfuly_Success() { await _sut.CommitAsync(); await _client.Database.Received(1).CommitTransactionAsync(); } [Fact] public async Task CommitAsync_WhenClientExcept_ThrowException() { _database.When(o => o.CommitTransactionAsync()).Do((call) => throw new Exception()); var task = _sut.CommitAsync(); await Assert.ThrowsAsync(() => task); } [Fact] public async Task RollbackTransactionAsync_WhenClientExecutedSucessfuly_Success() { await _sut.RollbackTransactionAsync(); await _client.Database.Received(1).RollbackTransactionAsync(); } [Fact] public async Task RollbackTransactionAsync_WhenClientExcept_ThrowException() { _database.When(o => o.RollbackTransactionAsync()).Do((call) => throw new Exception()); var task = _sut.RollbackTransactionAsync(); await Assert.ThrowsAsync(() => task); } [Fact] public void Verify_Dispose() { _sut.Dispose(); _client.Received(1).Dispose(); // Try to dispose twice to cover all conditions on Dispose method _sut.Dispose(); } [Fact] public void Verify_Dispose_Except() { _client.When(o => o.Dispose()).Do((call) => throw new Exception()); Assert.Throws(() => _sut.Dispose()); } [Fact] public void EntityFrameworkDataContext_WhenCreated_DbContextIsValid() { Assert.NotNull(_sut.DbClient); Assert.IsAssignableFrom(_sut.DbClient); } [Fact] public void EntityFrameworkDataContext_WhenCreatedWithoutDbContext_ThrowException() { Assert.Throws(() => new EntityFrameworkDataContext(null)); } [Fact] public void EntityFrameworkDataContext_IdIsAlwaysNull() { Assert.Null(_sut.Id); } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/EntityFrameworkRepositoryTest.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Repository.EntityFramework.Extensions; using Liquid.Repository.EntityFramework.Tests.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Liquid.Repository.EntityFramework.Tests { [ExcludeFromCodeCoverage] public class EntityFrameworkRepositoryTest { private IServiceProvider _serviceProvider; public EntityFrameworkRepositoryTest() { var services = new ServiceCollection(); var databaseName = $"TEMP_{Guid.NewGuid()}"; services.AddLiquidEntityFramework(options => options.UseInMemoryDatabase(databaseName: databaseName)); _serviceProvider = services.BuildServiceProvider(); SeedDataAsync(_serviceProvider); } private EntityFrameworkRepository GenerateMockRepository() { return _serviceProvider.GetService>(); } private EntityFrameworkRepository GenerateMockRepository(DbSet dbSet) { var dbContext = Substitute.For(); dbContext.Set().Returns(dbSet); var dataContext = Substitute.For>(); dataContext.DbClient.Returns(dbContext); return new EntityFrameworkRepository(dataContext); } [Fact] public async Task Verify_insert() { //Arrange var mockRepository = GenerateMockRepository(); var entity = new MockEntity { MockTitle = "TITLE", Active = true, CreatedDate = DateTime.Now }; //Act await mockRepository.AddAsync(entity); //Assert Assert.NotNull(entity); Assert.NotEqual(default, entity.MockId); } [Fact] public async Task Verify_insert_Except() { //Arrange var dbSet = Substitute.For, IQueryable>(); dbSet.When(o => o.AddAsync(Arg.Any())).Do((call) => throw new Exception()); var mockRepository = GenerateMockRepository(dbSet); var entity = new MockEntity { MockTitle = "TITLE", Active = true, CreatedDate = DateTime.Now }; //Act var task = mockRepository.AddAsync(entity); //Assert await Assert.ThrowsAsync(() => task); } [Fact] public async Task Verify_find_by_id() { //Arrange var mockRepository = GenerateMockRepository(); var mockId = 1; //Act var entity = await mockRepository.FindByIdAsync(mockId); //Assert Assert.NotNull(entity); Assert.Equal(mockId, entity.MockId); } [Fact] public async Task Verify_find_by_id_Except() { //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); var mockId = 1; //Act var task = mockRepository.FindByIdAsync(mockId); //Assert await Assert.ThrowsAsync(() => task); } [Fact] public async Task Verify_where() { //Arrange var mockRepository = GenerateMockRepository(); string mockTitle = "TITLE_002"; //Act var result = await mockRepository.WhereAsync(o => o.MockTitle.Equals(mockTitle)); //Assert Assert.NotNull(result); Assert.NotEmpty(result); Assert.True(result.All(o => o.MockTitle.Equals(mockTitle))); } [Fact] public async Task Verify_where_Except() { //Arrange //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); string mockTitle = "TITLE_002"; //Act var task = mockRepository.WhereAsync(o => o.MockTitle.Equals(mockTitle)); //Assert await Assert.ThrowsAsync(() => task); } [Fact] public async Task Verify_find_all() { //Arrange var mockRepository = GenerateMockRepository(); //Act var result = await mockRepository.FindAllAsync(); //Assert Assert.NotNull(result); Assert.NotEmpty(result); Assert.Equal(100, result.Count()); } [Fact] public async Task Verify_find_all_Except() { //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); //Act var result = await mockRepository.FindAllAsync(); //Assert Assert.Empty(result); } [Fact] public async Task Verify_delete() { //Arrange var mockRepository = GenerateMockRepository(); var mockId = 1; //Act await mockRepository.RemoveByIdAsync(mockId); var anotherEntity = await mockRepository.FindByIdAsync(mockId); //Assert Assert.Null(anotherEntity); } [Fact] public async Task Verify_delete_invalid() { //Arrange var mockRepository = GenerateMockRepository(); var mockId = 101; //Act await mockRepository.RemoveByIdAsync(mockId); var anotherEntity = await mockRepository.FindByIdAsync(mockId); //Assert Assert.Null(anotherEntity); } [Fact] public async Task Verify_delete_Except() { //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); var mockId = 1; //Act var task = mockRepository.RemoveByIdAsync(mockId); //Assert await Assert.ThrowsAsync(() => task); } [Fact] public async Task Verify_updates() { //Arrange var mockRepository = GenerateMockRepository(); var mockId = 1; //Act var entity = await mockRepository.FindByIdAsync(mockId); entity.MockTitle = $"TITLE_001_UPDATED"; await mockRepository.UpdateAsync(entity); var anotherEntity = await mockRepository.FindByIdAsync(mockId); //Assert Assert.NotNull(anotherEntity); Assert.Equal("TITLE_001_UPDATED", anotherEntity.MockTitle); } [Fact] public async Task Verify_updates_Except() { //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); var mockId = 1; //Act var task = mockRepository.UpdateAsync(new MockEntity() { MockId = mockId }); //Assert await Assert.ThrowsAsync(() => task); } private void SeedDataAsync(IServiceProvider serviceProvider) { MockDbContext dbContext = serviceProvider.GetService(); for (int i = 1; i <= 100; i++) { dbContext.AddAsync(new MockEntity() { MockId = i, MockTitle = $"TITLE_{i:000}", Active = true, CreatedDate = DateTime.Now }) .GetAwaiter().GetResult(); } dbContext.SaveChangesAsync().GetAwaiter().GetResult(); } [Fact] public void EntityFrameworkRepository_WhenCreatedWithoutDataContext_ThrowException() { Assert.Throws(() => new EntityFrameworkRepository(null)); } [Fact] public void EntityFrameworkRepository_WhenCreated_DataContextIsValid() { //Arrange var dbSet = Substitute.For, IQueryable>(); var mockRepository = GenerateMockRepository(dbSet); //Act var dataContext = mockRepository.DataContext; var entityFrameworkDataContext = mockRepository.EntityDataContext; //Assert Assert.NotNull(dataContext); Assert.IsAssignableFrom(dataContext); Assert.NotNull(entityFrameworkDataContext); Assert.IsAssignableFrom>(entityFrameworkDataContext); } [Fact] public void WhereInclude_WhenCalled_ReturnsExpected() { //Arrange var mockRepository = GenerateMockRepository(); //Act var result = mockRepository.WhereInclude(o => o.MockTitle.Equals("TITLE_002"), new string[] {"SubEntity" }); //Assert Assert.NotNull(result); Assert.NotEmpty(result); Assert.True(result.All(o => o.MockTitle.Equals("TITLE_002"))); } [Fact] public void WhereInclude_WhenCalledExpressionOverload_ReturnsExpected() { //Arrange var mockRepository = GenerateMockRepository(); //Act var result = mockRepository.WhereInclude(o => o.MockTitle.Equals("TITLE_002"), o => o.SubEntity); //Assert Assert.NotNull(result); Assert.NotEmpty(result); Assert.True(result.All(o => o.MockTitle.Equals("TITLE_002"))); } } } ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/Liquid.Repository.EntityFramework.Tests.csproj ================================================  net8.0 false all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: test/Liquid.Repository.EntityFramework.Tests/MockDbContext.cs ================================================ using Microsoft.EntityFrameworkCore; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.EntityFramework.Tests { [ExcludeFromCodeCoverage] public class MockDbContext : DbContext { public MockDbContext() : base() { } public MockDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly); } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/IServiceCollectionExtensionsTests.cs ================================================ using Liquid.Core.Implementations; using Liquid.Repository.Mongo.Extensions; using Liquid.Repository.Mongo.Tests.Mock; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using EphemeralMongo; using NSubstitute; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Liquid.Core.Interfaces; using Xunit; namespace Liquid.Repository.Mongo.Tests { [ExcludeFromCodeCoverage] public class IServiceCollectionExtensionsTests { internal const string _databaseName = "TestDatabase"; private IServiceCollection _services; private IServiceProvider _serviceProvider; private IConfiguration _configuration; private IMongoRunner _runner; public IServiceCollectionExtensionsTests() { var options = new MongoRunnerOptions { UseSingleNodeReplicaSet = false, AdditionalArguments = "--quiet", }; _runner = MongoRunner.Run(options); _services = new ServiceCollection(); _services.AddSingleton(Substitute.For>()); var mongoEntityConfiguration = new Dictionary { {"MyMongoEntityOptions:Settings:1:DatabaseName", _databaseName}, {"MyMongoEntityOptions:Settings:1:ConnectionString", _runner.ConnectionString}, {"MyMongoEntityOptions:Settings:1:CollectionName", "ATestEntity"}, {"MyMongoEntityOptions:Settings:1:ShardKey", "id"}, {"MyMongoEntityOptions:Settings:2:DatabaseName", _databaseName}, {"MyMongoEntityOptions:Settings:2:ConnectionString", _runner.ConnectionString}, {"MyMongoEntityOptions:Settings:2:CollectionName", "AnotherTestEntity"}, {"MyMongoEntityOptions:Settings:2:ShardKey", "id"}, }; _configuration = new ConfigurationBuilder() .AddInMemoryCollection(mongoEntityConfiguration).Build(); _services.AddSingleton(_configuration); } [Fact] public void AddLiquidMongoRepository_WhenAdded_ServicesIsFilledForTestEntity() { _services.AddLiquidMongoRepository("MyMongoEntityOptions","ATestEntity"); _services.AddLiquidMongoRepository("MyMongoEntityOptions", "AnotherTestEntity"); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService>()); } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/Liquid.Repository.Mongo.Tests.csproj ================================================  net8.0 false all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Repository.Mongo.Tests/Mock/AnotherTestEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.Mongo.Tests.Mock { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class AnotherTestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/Mock/AsyncCursorMock.cs ================================================ using MongoDB.Driver; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Liquid.Repository.Mongo.Tests.Mock { [ExcludeFromCodeCoverage] class AsyncCursorMock : IAsyncCursor { public IEnumerable Current { get; set; } public AsyncCursorMock(IEnumerable documents) { Current = documents; } public void Dispose() { Dispose(true); System.GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { Current.GetEnumerator().Dispose(); } public bool MoveNext(CancellationToken cancellationToken = default) { return Current.GetEnumerator().MoveNext(); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task MoveNextAsync(CancellationToken cancellationToken = default) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { return Current.GetEnumerator().MoveNext(); } public IEnumerable ToEnumerable() { return Current.ToList(); } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/Mock/TestEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.Mongo.Tests.Mock { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class TestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/MongoClientFactoryTests.cs ================================================ using EphemeralMongo; using Liquid.Core.Settings; using Liquid.Repository.Mongo.Settings; using Microsoft.Extensions.Options; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using Xunit; namespace Liquid.Repository.Mongo.Tests { [ExcludeFromCodeCoverage] public class MongoClientFactoryTests { private IMongoClientFactory _sut; internal static IMongoRunner _runner; internal const string _databaseName = "TestDatabase"; private IOptions _options; public MongoClientFactoryTests() { var options = new MongoRunnerOptions { UseSingleNodeReplicaSet = false, AdditionalArguments = "--quiet", }; _runner = MongoRunner.Run(options); _options = Substitute.For>(); var settings = new MongoDbSettings() { Settings = new System.Collections.Generic.List() { new MongoEntitySettings() { CollectionName = "TestEntities", ShardKey = "id", ConnectionString = _runner.ConnectionString, DatabaseName = _databaseName }, new MongoEntitySettings() { CollectionName = "TestEntities2", ShardKey = "id", ConnectionString = "", DatabaseName = $"{_databaseName}-2" } } }; _options.Value.Returns(settings); _sut = new MongoClientFactory(_options); } [Fact] public void MongoClientFactory_WhenSettingsIsNull_ThrowException() { MongoEntitySettings settings = null; Assert.Throws(() => _sut.GetClient(null, out settings)); } [Fact] public void GetClient_WhenDatabaseIdsExists_ClientCreated() { MongoEntitySettings settings = null; var result = _sut.GetClient("TestEntities", out settings); Assert.NotNull(result); } [Fact] public void GetClient_WhenDatabaseSettingsIsWrong_ThrowException() { MongoEntitySettings settings = null; Assert.Throws(() => _sut.GetClient("TestEntities2", out settings)); } [Fact] public void Constructor_WhenSettingsIsNull_ThrowException() { Assert.Throws(() => new MongoClientFactory(null)); } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/MongoDataContextTests.cs ================================================ using Liquid.Core.Settings; using Liquid.Repository.Mongo.Settings; using Liquid.Repository.Mongo.Tests.Mock; using MongoDB.Driver; using NSubstitute; using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; namespace Liquid.Repository.Mongo.Tests { [ExcludeFromCodeCoverage] public class MongoDataContextTests { private MongoDataContext _sut; private IMongoClient _client; private IMongoClientFactory _provider; public MongoDataContextTests() { _client = Substitute.For(); var _options = new MongoEntitySettings() { CollectionName = "TestEntities", ShardKey = "id", ConnectionString = "test connection string", DatabaseName = "TestDatabase" }; _provider = Substitute.For(); _provider.GetClient("TestEntities", out _).Returns(x => { x[1] = _options; return _client; }); _sut = new MongoDataContext(_provider, "TestEntities"); } [Fact] public void MongoDataContext_WhenCreatedWithNullArguments_ThrowsException() { Assert.Throws(() => new MongoDataContext(null, "TestEntities")); Assert.Throws(() => new MongoDataContext(_provider, null)); } [Fact] public async Task StartTransaction_WhenDBInitialized_Sucess() { await _sut.StartTransactionAsync(); await _client.Received(1).StartSessionAsync(); } [Fact] public async Task CommitAsync_WhenTansactionIsStarted_Sucess() { await _sut.StartTransactionAsync(); await _sut.CommitAsync(); await _sut.ClientSessionHandle.Received().CommitTransactionAsync(); } [Fact] public async Task CommitAsync_WhenTansactionIsntStarted_ThrowException() { var task = _sut.CommitAsync(); await Assert.ThrowsAsync(() => task); } [Fact] public async Task RollbackAsync_WhenTansactionIsStarted_Sucess() { await _sut.StartTransactionAsync(); await _sut.RollbackTransactionAsync(); await _sut.ClientSessionHandle.Received().AbortTransactionAsync(); } [Fact] public async Task RollbackAsync_WhenTansactionIsntStarted_ThrowException() { var task = _sut.RollbackTransactionAsync(); await Assert.ThrowsAsync(() => task); } [Fact] public async Task Dispose_WhenTansactionIsStarted_Sucess() { await _sut.StartTransactionAsync(); _sut.ClientSessionHandle.IsInTransaction.Returns(true); _sut.Dispose(); _sut.ClientSessionHandle.Received().AbortTransaction(); _sut.ClientSessionHandle.Received().Dispose(); } [Fact] public async Task Dispose_WhenTansactionIsntStarted_HandlerDisposed() { await _sut.StartTransactionAsync(); _sut.ClientSessionHandle.IsInTransaction.Returns(false); _sut.Dispose(); _sut.ClientSessionHandle.DidNotReceive().AbortTransaction(); _sut.ClientSessionHandle.Received().Dispose(); } } } ================================================ FILE: test/Liquid.Repository.Mongo.Tests/MongoRepositoryTests.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Core.Settings; using Liquid.Repository.Mongo.Settings; using Liquid.Repository.Mongo.Tests.Mock; using MongoDB.Driver; using NSubstitute; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Liquid.Repository.Mongo.Tests { [ExcludeFromCodeCoverage] public class MongoRepositoryTests { private IMongoDataContext _dbDataContext; private ILiquidRepository _sut; private TestEntity _entity; internal static string _databaseName = "IntegrationTest"; internal static string _collectionName = "TestEntities"; private IMongoCollection _collection; public MongoRepositoryTests() { _entity = new TestEntity() { CreatedDate = DateTime.Now, Active = true, Id = 1234, MockTitle = "test" }; var _options = new MongoEntitySettings() { CollectionName = _collectionName, ShardKey = "id", DatabaseName = _databaseName, ConnectionString = "test connection string" }; _dbDataContext = Substitute.For>(); _dbDataContext.Settings.Returns(_options); IClientSessionHandle handle = null; _dbDataContext.ClientSessionHandle.Returns(handle, handle); _collection = GetCollection(); _dbDataContext.Database.GetCollection(_collectionName) .Returns(_collection); _sut = new MongoRepository(_dbDataContext); } private IMongoCollection GetCollection() { var listEntities = new List() { _entity }; var result = Substitute.For>(); var cursor = new AsyncCursorMock(listEntities.AsEnumerable()); result.FindAsync(Arg.Any>()).Returns(cursor); return result; } [Fact] public void MongoRepository_WhenCreatedWithNoDataContext_ThrowException() { Assert.Throws(() => new MongoRepository(null)); } [Fact] public async Task ValidateCollection_WhenCollectionExists_Success() { await _sut.AddAsync(_entity); _dbDataContext.Database.Received(1).GetCollection(_collectionName); } [Fact] public async Task AddAsync_WhenActionIsSuccessful_CallInsertOneMethod() { await _sut.AddAsync(_entity); _dbDataContext.Database.Received(1).GetCollection(_collectionName); await _collection.Received(1).InsertOneAsync(_entity); } [Fact] public async Task AddAsync_WhenClientThrowsError_ThrowException() { _collection.When(o => o.InsertOneAsync(Arg.Any())).Do((call) => throw new Exception()); var test = _sut.AddAsync(_entity); await Assert.ThrowsAsync(() => test); } [Fact] public async Task FindAllAsync_WhenCollectionExists_ReturnItens() { var result = await _sut.FindAllAsync(); _dbDataContext.Database.Received(1).GetCollection(_collectionName); Assert.NotNull(result); Assert.Equal(result.FirstOrDefault(), _entity); } [Fact] public async Task FindAllAsync_WhenClientThrowsError_ThrowException() { _dbDataContext.Database.When(o => o.GetCollection(Arg.Any())).Do((call) => throw new Exception()); var test = _sut.FindAllAsync(); await Assert.ThrowsAsync(() => test); } [Fact] public async Task FindByIdAsync_WhenItemExists_ReturnItem() { var result = await _sut.FindByIdAsync(1234); _dbDataContext.Database.Received(1).GetCollection(_collectionName); Assert.True(result == _entity); } [Fact] public async Task FindByIdAsync_WhenClientThrowsError_ThrowException() { _collection.When(o => o.FindAsync(Arg.Any>())).Do((call) => throw new Exception()); var test = _sut.FindByIdAsync(1234); await Assert.ThrowsAsync(() => test); } [Fact] public async Task RemoveByIdAsync_WhenActionIsSuccessful_CallDeleteOneMethod() { await _sut.RemoveByIdAsync(_entity.Id); _dbDataContext.Database.Received(1).GetCollection(_collectionName); await _collection.Received().DeleteOneAsync(Arg.Any>()); } [Fact] public async Task RemoveByIdAsync_WhenClientThrowsError_ThrowException() { _collection.When(o => o.DeleteOneAsync(Arg.Any>())).Do((call) => throw new Exception()); var test = _sut.RemoveByIdAsync(_entity.Id); await Assert.ThrowsAsync(() => test); } [Fact] public async Task UpdateAsync_WhenActionIsSuccessful_CallReplaceOneMethod() { await _sut.UpdateAsync(_entity); _dbDataContext.Database.Received().GetCollection(_collectionName); await _collection.Received().ReplaceOneAsync(Arg.Any>(), _entity, Arg.Any()); } [Fact] public async Task UpdateAsync_WhenClientThrowsError_ThrowException() { _collection.When(o => o.ReplaceOneAsync(Arg.Any>(), _entity, Arg.Any())).Do((call) => throw new Exception()); var test = _sut.UpdateAsync(_entity); await Assert.ThrowsAsync(() => test); } [Fact] public async Task WhereAsync_WhenItensExists_ReturnItens() { var result = await _sut.WhereAsync(e => e.Id.Equals(_entity.Id)); _dbDataContext.Database.Received().GetCollection(_collectionName); Assert.NotNull(result); Assert.Equal(result.FirstOrDefault(), _entity); } [Fact] public async Task WhereAsync_WhenClientThrowsError_ThrowException() { _collection.When(o => o.FindAsync(Arg.Any>())).Do((call) => throw new Exception()); var test = _sut.WhereAsync(e => e.Id.Equals(_entity.Id)); await Assert.ThrowsAsync(() => test); } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/GlobalUsings.cs ================================================ global using Xunit; ================================================ FILE: test/Liquid.Repository.OData.Tests/IServiceCollectionExtensionTests.cs ================================================ using Liquid.Core.Implementations; using Liquid.Core.Interfaces; using Liquid.Repository.OData.Extensions; using Liquid.Repository.OData.Tests.Mock; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Repository.OData.Tests { public class IServiceCollectionExtensionTests { private IServiceCollection _services; private IServiceProvider _serviceProvider; private IConfiguration _configuration; public IServiceCollectionExtensionTests() { _services = new ServiceCollection(); var odataEntityConfiguration = new Dictionary { {"MyODataEntityOptions:Settings:1:BaseUrl", "http://localhost:5000"}, {"MyODataEntityOptions:Settings:1:EntityName", "TestEntity"}, {"MyODataEntityOptions:Settings:2:BaseUrl", "http://localhost:5000"}, {"MyODataEntityOptions:Settings:2:EntityName", "AnotherTestEntity"}, }; _configuration = new ConfigurationBuilder() .AddInMemoryCollection(odataEntityConfiguration).Build(); _services.AddSingleton(_configuration); } [Fact] public void AddLiquidODataRepository_WhenAdded_ServicesIsFilledForTestEntity() { _services.AddLiquidOdataRepository("MyODataEntityOptions", "TestEntity"); _services.AddLiquidOdataRepository("MyODataEntityOptions", "AnotherTestEntity"); _serviceProvider = _services.BuildServiceProvider(); Assert.NotNull(_serviceProvider.GetService>()); Assert.NotNull(_serviceProvider.GetService>()); } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/Liquid.Repository.OData.Tests.csproj ================================================ net8.0 enable enable false true runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Repository.OData.Tests/Mock/AnotherTestEntity.cs ================================================ using Liquid.Core.Entities; using System; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.OData.Tests.Mock { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class AnotherTestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/Mock/MockPeople.cs ================================================ using Liquid.Core.Entities; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Liquid.Repository.OData.Tests.Mock { public class People : LiquidEntity { public string UserName { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Emails { get; set; } public string AddressInfo { get; set; } public string Gender { get; set; } public string Concurrency { get; set; } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/Mock/MyMockHttpMessageHandler.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Json; using System.Net; using System.Text; using System.Threading.Tasks; namespace Liquid.Repository.OData.Tests.Mock { public class MyMockHttpMessageHandler : HttpMessageHandler { private readonly HttpStatusCode _statusCode; private readonly object? _responseContent; public MyMockHttpMessageHandler(HttpStatusCode statusCode, object? responseContent = null) { _statusCode = statusCode; _responseContent = responseContent; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return await Task.FromResult(new HttpResponseMessage { StatusCode = _statusCode, Content = JsonContent.Create(_responseContent) }); } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/Mock/TestEntity.cs ================================================ using Liquid.Core.Entities; using System.Diagnostics.CodeAnalysis; namespace Liquid.Repository.OData.Tests.Mock { /// /// Mock test entity class. /// /// /// Liquid.Data.Entities.DataMappingBase{System.Int32} /// [ExcludeFromCodeCoverage] public class TestEntity : LiquidEntity { /// /// Gets or sets the mock title. /// /// /// The mock title. /// public string MockTitle { get; set; } /// /// Gets or sets a value indicating whether this is active. /// /// /// true if active; otherwise, false. /// public bool Active { get; set; } /// /// Gets or sets the created date. /// /// /// The created date. /// public DateTime CreatedDate { get; set; } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/ODataClientFactoryTests.cs ================================================ using Liquid.Core.Interfaces; using Microsoft.Extensions.Options; using NSubstitute; namespace Liquid.Repository.OData.Tests { public class ODataClientFactoryTests { private IODataClientFactory _sut; private IOptions _options; private ILiquidContext _context; public ODataClientFactoryTests() { _options = Substitute.For>(); var settings = new ODataOptions() { Settings = new List() { new ODataSettings() { BaseUrl = "http://localhost:5000", EntityName = "TestEntities", } } }; _options.Value.Returns(settings); _context = Substitute.For(); _context.Get("OdataToken").Returns("token"); _context.current.ContainsKey("OdataToken").Returns(true); _sut = new ODataClientFactory(_options, _context); } [Fact] public void ODataClientFactory_WhenEntityNameIsNotFound_ThrowException() { Assert.Throws(() => _sut.CreateODataClientAsync("TestEntities2")); } [Fact] public void ODataClientFactory_WhenEntityNameIsNull_ThrowException() { Assert.Throws(() => _sut.CreateODataClientAsync(null)); } [Fact] public void OdataClientFactory_WhenValidateCertIsFalse_ReturnClient() { var client = _sut.CreateODataClientAsync("TestEntities"); Assert.NotNull(client); } [Fact] public void OdataClientFactory_WhenValidateCertIsTrue_ReturnClient() { var settings = new ODataSettings() { BaseUrl = "http://localhost:5000", EntityName = "TestEntities", ValidateCert = true }; _options.Value.Returns(new ODataOptions() { Settings = new List() { settings } }); var sut = new ODataClientFactory(_options, _context); var client = sut.CreateODataClientAsync("TestEntities"); Assert.NotNull(client); } [Fact] public void OdataClientFactory_WhenTokenIsNotSet_ThrowException() { var context = Substitute.For(); context.Get("OdataToken").Returns(""); context.current.ContainsKey("OdataToken").Returns(true); var sut = new ODataClientFactory(_options, context); Assert.Throws(() => sut.CreateODataClientAsync("TestEntities")); } [Fact] public void OdataClientFactory_WhenTokenIsNotSetInContext_ThrowException() { var context = Substitute.For(); context.Get("OdataToken").Returns(null); context.current.ContainsKey("OdataToken").Returns(false); var sut = new ODataClientFactory(_options, context); Assert.Throws(() => sut.CreateODataClientAsync("TestEntities")); } [Fact] public void OdataClientFactory_WhenOptionsIsNull_ThrowException() { _options = null; Assert.Throws(() => new ODataClientFactory(_options, _context)); } [Fact] public void OdataClientFactory_WhenContextIsNull_ThrowException() { _context = null; Assert.Throws(() => new ODataClientFactory(_options, _context)); } } } ================================================ FILE: test/Liquid.Repository.OData.Tests/ODataRepositoryTests.cs ================================================ using Liquid.Core.Interfaces; using Liquid.Repository.OData.Extensions; using Liquid.Repository.OData.Tests.Mock; using NSubstitute; using Simple.OData.Client; using System.Linq.Expressions; namespace Liquid.Repository.OData.Tests { public class OdataRepositoryTests { private ILiquidRepository _sut; private IODataClient _client; public OdataRepositoryTests() { var factory = Substitute.For(); _client = Substitute.For(); factory.CreateODataClientAsync(Arg.Any()).Returns(_client); _sut = new ODataRepository(factory, "People"); } [Fact] public void ODataRepository_WhenCreatedWithNoClientFactory_ThrowException() { Assert.Throws(() => new ODataRepository(null, "TestEntity")); } [Fact] public void ODataRepository_WhenCreatedWithNoEntityName_ThrowException() { Assert.Throws(() => new ODataRepository(Substitute.For(), null)); } [Fact] public async Task AddAsync_WhenActionIsSuccessful_CallClient() { var entity = new People(); await _sut.AddAsync(entity); await _client.Received(1).For().Set(entity).InsertEntryAsync(); } [Fact] public async Task FindAllAsync_WhenActionIsSuccessful_CallClient() { await _sut.FindAllAsync(); await _client.Received(1).For().FindEntriesAsync(); } [Fact] public async Task FindByIdAsync_WhenActionIsSuccessful_CallClient() { await _sut.FindByIdAsync("id"); await _client.Received(1).For().Key("id").FindEntryAsync(); } [Fact] public async Task RemoveByIdAsync_WhenActionIsSuccessful_CallClient() { await _sut.RemoveByIdAsync("id"); await _client.Received(1).For().Key("id").DeleteEntryAsync(); } [Fact] public async Task UpdateAsync_WhenActionIsSuccessful_CallClient() { var entity = new People(); await _sut.UpdateAsync(entity); await _client.Received(1).For().Set(entity).UpdateEntryAsync(); } [Fact] public async Task WhereAsync_WhenActionIsSuccessful_CallClient() { Expression> expression = e => e.Id.Equals("id"); await _sut.WhereAsync(expression); await _client.Received(1).For().Filter(expression).FindEntriesAsync(); } } } ================================================ FILE: test/Liquid.Storage.AzureStorage.Tests/BlobClientFactoryTests.cs ================================================ using Microsoft.Extensions.Options; using NSubstitute; namespace Liquid.Storage.AzureStorage.Tests { public class BlobClientFactoryTests { private readonly IBlobClientFactory _sut; private readonly IOptions _options; public BlobClientFactoryTests() { _options = Substitute.For>(); var settings = new StorageSettings(); settings.Containers.Add(new ContainerSettings() { ContainerName = "test", ConnectionString = "testestestes" }); _options.Value.ReturnsForAnyArgs(settings); _sut = new BlobClientFactory(_options); } [Fact] public void Ctor_WhenOptionsIsNull_ThenReturnArgumentNullException() { Assert.Throws(() => new BlobClientFactory(null)); } [Fact] public void Ctor_WhenOptionsExists_ThenBlobClientFactoryInstance() { var result = new BlobClientFactory(_options); Assert.NotNull(result); Assert.IsType(result); } [Fact] public void SetContainerClients_WhenOptionsNotSet_ThenThrowArgumentNullException() { var options = Substitute.For>(); options.Value.ReturnsForAnyArgs(new StorageSettings()); var sut = new BlobClientFactory(options); Assert.Throws(() => sut.SetContainerClients()); } [Fact] public void SetContainerClients_WhenContainerNameIsInvalid_ThenThrowFormatException() { Assert.True(_sut.Clients.Count == 0); Assert.Throws(() => _sut.SetContainerClients()); } [Fact] public void GetContainerClient_WhenClientDoesntExists_ThenThrowArgumentException() { Assert.Throws(() => _sut.GetContainerClient("test")); } } } ================================================ FILE: test/Liquid.Storage.AzureStorage.Tests/Liquid.Storage.AzureStorage.Tests.csproj ================================================  net8.0 enable enable false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.Storage.AzureStorage.Tests/LiquidStorageAzureTests.cs ================================================ using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using Azure.Storage.Sas; using Liquid.Core.Entities; using NSubstitute; using System.Text; namespace Liquid.Storage.AzureStorage.Tests { public class LiquidStorageAzureTests { private readonly LiquidStorageAzure _sut; private BlobContainerClient _blobContainerClient = Substitute.For(); private BlockBlobClient _blockBlobClient = Substitute.For(); public LiquidStorageAzureTests() { var clientFactory = Substitute.For(); _blobContainerClient.GetBlockBlobClient(Arg.Any()).Returns(_blockBlobClient); clientFactory.GetContainerClient(Arg.Any()).Returns(_blobContainerClient); _sut = new LiquidStorageAzure(clientFactory); } [Fact] public void Ctor_WhenClientFactoryIsNull_ThrowsArgumentNullException() { // Arrange IBlobClientFactory clientFactory = null; // Act & Assert Assert.Throws(() => new LiquidStorageAzure(clientFactory)); } [Fact] public void Ctor_WhenClientFactoryIsNotNull_DoesNotThrow() { // Arrange var clientFactory = Substitute.For(); // Act & Assert var exception = Record.Exception(() => new LiquidStorageAzure(clientFactory)); Assert.Null(exception); } [Fact] public async void DeleteByTags_WhenTagsAreValid_DoesNotThrow() { // Arrange var tags = new Dictionary { { "tag1", "value1" }, { "tag2", "value2" } }; var containerName = "test-container"; var page = Page.FromValues(new List { BlobsModelFactory.TaggedBlobItem("test-blob","test-container", new Dictionary { { "tag1", "value1" } }), BlobsModelFactory.TaggedBlobItem("test-blob","test-container", new Dictionary { { "tag2", "value2" } }) }, continuationToken: null,Substitute.For()); var pages = AsyncPageable.FromPages(new[] { page }); _blobContainerClient.FindBlobsByTagsAsync(Arg.Any()).Returns(pages); // Act & Assert var exception = await Record.ExceptionAsync(() => _sut.DeleteByTags(tags, containerName)); Assert.Null(exception); } [Fact] public async void GetAllBlobs_WhenContainerNameIsValid_ReturnsListOfLiquidBlob() { // Arrange var containerName = "test-container"; var blobItem = BlobsModelFactory.BlobItem(); _blobContainerClient.GetBlobsAsync().Returns(AsyncPageable.FromPages(new[] { Page.FromValues(new[] { blobItem }, null, Substitute.For()) })); _blockBlobClient.DownloadContentAsync().Returns(Response.FromValue( BlobsModelFactory.BlobDownloadResult(new BinaryData("test-blob")), null )); _blockBlobClient.GetTagsAsync().Returns(Response.FromValue( BlobsModelFactory.GetBlobTagResult(new Dictionary { { "tag1", "value1" } }), null )); _blockBlobClient.Uri.Returns(new Uri("https://test.blob.core.windows.net/test-blob")); // Act var result = await _sut.GetAllBlobs(containerName); // Assert Assert.NotNull(result); Assert.IsType>(result); } [Fact] public async void ReadBlobsByName_WhenBlobNameIsValid_ReturnsLiquidBlob() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; _blockBlobClient.DownloadContentAsync().Returns(Response.FromValue( BlobsModelFactory.BlobDownloadResult(new BinaryData("test-blob")), null )); _blockBlobClient.GetTagsAsync().Returns(Response.FromValue( BlobsModelFactory.GetBlobTagResult(new Dictionary { { "tag1", "value1" } }), null )); _blockBlobClient.Uri.Returns(new Uri("https://test.blob.core.windows.net/test-blob")); // Act var result = await _sut.ReadBlobsByName(blobName, containerName); // Assert Assert.NotNull(result); Assert.IsType(result); } [Fact] public async void ReadBlobsByName_WhenRequestFail_ThrowsException() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; _blockBlobClient.DownloadContentAsync().Returns(Task.FromException>(new Exception("Test exception"))); // Act & Assert await Assert.ThrowsAsync(() => _sut.ReadBlobsByName(blobName, containerName)); } [Fact] public async void ReadBlobsByName_WhenRequestFailedException_ReturnNull() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; _blockBlobClient.DownloadContentAsync().Returns(Task.FromException>(new RequestFailedException(0, "BlobNotFound", "BlobNotFound", new Exception()))); // Act var result = await _sut.ReadBlobsByName(blobName, containerName); // Assert Assert.Null(result); } [Fact] public async void UploadBlob_WhenDataIsValid_ReturnsBlobUri() { // Arrange var data = Encoding.UTF8.GetBytes("test data"); var name = "test-blob"; var containerName = "test-container"; var tags = new Dictionary { { "tag1", "value1" }, { "tag2", "value2" } }; _blockBlobClient.UploadAsync(Arg.Any(), Arg.Any()).Returns(Response.FromValue( BlobsModelFactory.BlobContentInfo(ETag.All, DateTimeOffset.UtcNow, data, "", "", "", data.Length), null)); _blockBlobClient.Uri.Returns(new Uri("https://test.blob.core.windows.net/test-blob")); // Act var result = await _sut.UploadBlob(data, name, containerName, tags); // Assert Assert.NotNull(result); } [Fact] public async void UploadBlob_WhenDataIsNull_ThrowsArgumentNullException() { // Arrange byte[] data = null; var name = "test-blob"; var containerName = "test-container"; // Act & Assert await Assert.ThrowsAsync(() => _sut.UploadBlob(data, name, containerName)); } [Fact] public void GetBlobSasUri_WhenBlobNameIsValid_ReturnsSasUri() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; var expiresOn = DateTimeOffset.UtcNow.AddHours(1); var permissions = "Read"; _blobContainerClient.CanGenerateSasUri.Returns(true); _blockBlobClient.GenerateSasUri(Arg.Any()).Returns(new Uri("https://test.blob.core.windows.net/test-blob?sv=2020-08-04&ss=b&srt=sco&sp=r&se=2023-10-01T00:00:00Z&st=2023-09-01T00:00:00Z&spr=https,http&sig=signature")); // Act var result = _sut.GetBlobSasUri(blobName, containerName, expiresOn, permissions); // Assert Assert.NotNull(result); } [Fact] public void GetBlobSasUri_WhenCantGenerateSasUri_ReturnsNull() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; var expiresOn = DateTimeOffset.UtcNow.AddHours(1); var permissions = "Read"; _blobContainerClient.CanGenerateSasUri.Returns(false); // Act var result = _sut.GetBlobSasUri(blobName, containerName, expiresOn, permissions); // Assert Assert.Null(result); } [Fact] public async void Delete_WhenBlobNameIsValid_DoesNotThrow() { // Arrange var blobName = "test-blob"; var containerName = "test-container"; var blobClient = Substitute.For(); _blobContainerClient.GetBlobClient(Arg.Any()).Returns(blobClient); // Act & Assert var exception = await Record.ExceptionAsync(() => _sut.Delete(blobName, containerName)); Assert.Null(exception); } [Fact] public async void ReadBlobsByTags_WhenTagsAreValid_ReturnsListOfLiquidBlob() { // Arrange var tags = new Dictionary { { "tag1", "value1" }, { "tag2", "value2" } }; var containerName = "test-container"; var page = Page.FromValues(new List { BlobsModelFactory.TaggedBlobItem("test-blob","test-container", new Dictionary { { "tag1", "value1" } }), BlobsModelFactory.TaggedBlobItem("test-blob","test-container", new Dictionary { { "tag2", "value2" } }) }, continuationToken: null, Substitute.For()); var pages = AsyncPageable.FromPages(new[] { page }); _blobContainerClient.FindBlobsByTagsAsync(Arg.Any()).Returns(pages); _blockBlobClient.DownloadContentAsync().Returns(Response.FromValue( BlobsModelFactory.BlobDownloadResult(new BinaryData("test-blob")), null )); _blockBlobClient.GetTagsAsync().Returns(Response.FromValue( BlobsModelFactory.GetBlobTagResult(new Dictionary { { "tag1", "value1" } }), null )); _blockBlobClient.Uri.Returns(new Uri("https://test.blob.core.windows.net/test-blob")); // Act var result = await _sut.ReadBlobsByTags(tags, containerName); // Assert Assert.NotNull(result); Assert.IsType>(result); } } } ================================================ FILE: test/Liquid.Storage.AzureStorage.Tests/Usings.cs ================================================ global using Xunit; ================================================ FILE: test/Liquid.WebApi.Http.Tests/Liquid.WebApi.Http.Tests.csproj ================================================  net8.0 false runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all ================================================ FILE: test/Liquid.WebApi.Http.Tests/LiquidControllerBaseTest.cs ================================================ using Liquid.WebApi.Http.UnitTests.Mocks; using MediatR; using Microsoft.AspNetCore.Mvc; using NSubstitute; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Xunit; namespace Liquid.WebApi.Http.UnitTests { [ExcludeFromCodeCoverage] public class LiquidControllerBaseTest { private TestController _sut; private IMediator _mediator; public LiquidControllerBaseTest() { _mediator = Substitute.For(); _sut = new TestController(_mediator); } [Fact] public async Task ExecuteAsync_WhenIActionResultOverload_Return200() { var response = await _sut.GetCase1(); var result = (ObjectResult)response; await _mediator.Received(1).Send(Arg.Any()); Assert.Equal(200, result.StatusCode); } [Fact] public async Task ExecuteAsync_WhenGenericResultOverload_Return200() { var response = await _sut.GetCase2(); var result = (ObjectResult)response; await _mediator.Received(1).Send(Arg.Any()); Assert.Equal(200, result.StatusCode); } } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/LiquidNotificationHelperTest.cs ================================================ using Liquid.Core.Interfaces; using Liquid.WebApi.Http.Implementations; using Liquid.WebApi.Http.Interfaces; using Liquid.WebApi.Http.UnitTests.Mocks; using NSubstitute; using System.Collections.Generic; using Xunit; namespace Liquid.WebApi.Http.UnitTests { public class LiquidNotificationHelperTest { private ILiquidNotificationHelper _sut; private ILiquidContextNotifications _contextNotifications = Substitute.For(); public LiquidNotificationHelperTest() { } [Fact] public void IncludeMessages_WhenNotificationContextHasMessages_ResponseHasMessagesList() { _contextNotifications.GetNotifications().Returns(new List() { "test", "case" }); _sut = new LiquidNotificationHelper(_contextNotifications); var responseObject = new TestCaseResponse("With Messages"); object response = _sut.IncludeMessages(responseObject); var messages = response.GetType().GetProperty("messages").GetValue(response); Assert.NotNull(response.GetType().GetProperty("messages").GetValue(response)); } [Fact] public void IncludeMessages_WhenNotificationContextHasNoMessages_ResponseWithoutMessages() { IList notifications = default; _contextNotifications.GetNotifications().Returns(notifications); _sut = new LiquidNotificationHelper(_contextNotifications); var responseObject = new TestCaseResponse("With Messages"); object response = _sut.IncludeMessages(responseObject); Assert.Null(response.GetType().GetProperty("messages")?.GetValue(response)); } } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/Mocks/Handlers/TestCaseQueryHandler.cs ================================================ using MediatR; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Liquid.WebApi.Http.UnitTests.Mocks { [ExcludeFromCodeCoverage] public class TestCaseQueryHandler : IRequestHandler { #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async Task Handle(TestCaseRequest request, CancellationToken cancellationToken) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { var result = new TestCaseResponse("Successfull test."); return result; } } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/Mocks/Handlers/TestCaseRequest.cs ================================================ using MediatR; using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.UnitTests.Mocks { [ExcludeFromCodeCoverage] public class TestCaseRequest : IRequest { } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/Mocks/Handlers/TestCaseResponse.cs ================================================ using System.Diagnostics.CodeAnalysis; namespace Liquid.WebApi.Http.UnitTests.Mocks { [ExcludeFromCodeCoverage] public class TestCaseResponse { public string MyProperty { get; set; } public TestCaseResponse(string myProperty) { MyProperty = myProperty; } } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/Mocks/TestController.cs ================================================ using Liquid.WebApi.Http.Controllers; using MediatR; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Text; using System.Threading.Tasks; namespace Liquid.WebApi.Http.UnitTests.Mocks { [ExcludeFromCodeCoverage] public class TestController : LiquidControllerBase { public TestController(IMediator mediator) : base(mediator) { } public async Task GetCase1() => await ExecuteAsync(new TestCaseRequest(), HttpStatusCode.OK); public async Task GetCase2() => Ok(await ExecuteAsync(new TestCaseRequest())); } } ================================================ FILE: test/Liquid.WebApi.Http.Tests/Mocks/TestNotificationController.cs ================================================ using Liquid.WebApi.Http.Controllers; using Liquid.WebApi.Http.Interfaces; using MediatR; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Text; using System.Threading.Tasks; namespace Liquid.WebApi.Http.UnitTests.Mocks { [ExcludeFromCodeCoverage] public class TestNotificationController : LiquidControllerBase { private readonly ILiquidNotificationHelper _liquidNotification; public TestNotificationController(IMediator mediator, ILiquidNotificationHelper liquidNotification) : base(mediator) { _liquidNotification = liquidNotification; } public async Task GetCase2() => Ok(await ExecuteAsync(new TestCaseRequest())); } }