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



[](https://avanade.github.io/code-of-conduct/)
[](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) |  |
| [`Liquid.Repository.Mongo`](https://www.nuget.org/packages/Liquid.Repository.Mongo) |  |
| [`Liquid.Repository.EntityFramework`](https://www.nuget.org/packages/Liquid.Repository.EntityFramework) |  |
| [`Liquid.Repository.OData`](https://www.nuget.org/packages/Liquid.Repository.OData) |  |
| [`Liquid.Cache.Memory`](https://www.nuget.org/packages/Liquid.Cache.Memory) |  |
| [`Liquid.Cache.NCache`](https://www.nuget.org/packages/Liquid.Cache.NCache) |  |
| [`Liquid.Cache.Redis`](https://www.nuget.org/packages/Liquid.Cache.Redis) |  |
| [`Liquid.Cache.SqlServer`](https://www.nuget.org/packages/Liquid.Cache.SqlServer) |  |
| [`Liquid.Messaging.Kafka`](https://www.nuget.org/packages/Liquid.Messaging.Kafka) |  |
| [`Liquid.Messaging.RabbitMq`](https://www.nuget.org/packages/Liquid.Messaging.RabbitMq) |  |
| [`Liquid.Messaging.ServiceBus`](https://www.nuget.org/packages/Liquid.Messaging.ServiceBus) |  |
| [`Liquid.WebApi.Http`](https://www.nuget.org/packages/Liquid.WebApi.Http) |  |
| [`Liquid.Dataverse`](https://www.nuget.org/packages/Liquid.Dataverse) |  |
| [`Liquid.Storage.AzureStorage`](https://www.nuget.org/packages/Liquid.Storage.AzureStorage) |  |
| [`Liquid.GenAi.OpenAi`](https://www.nuget.org/packages/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:

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

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
{
///