From e89dc67707fe4b401a98a41e39ea7e6f054e07d3 Mon Sep 17 00:00:00 2001
From: root <8907060@qq.com>
Date: Sun, 10 Sep 2023 20:09:37 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A7=9F=E6=88=B7=E6=94=AF?=
=?UTF-8?q?=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Util.sln | 95 ++++--
build/version.props | 2 +-
src/Util.Aop.AspectCore/IgnoreAttribute.cs | 2 +-
src/Util.AspNetCore/Helpers/Web.cs | 56 ++++
src/Util.AspNetCore/Http/HttpRequest.cs | 71 ++++-
.../AspNetCoreServiceRegistrar.cs | 9 -
src/Util.AspNetCore/Sessions/UserSession.cs | 21 +-
src/Util.AspNetCore/Usings.cs | 3 +-
.../02-Util.Caching.EasyCaching.csproj | 6 +-
src/Util.Caching/NullCache.cs | 230 ++++++++++++++
src/Util.Core/Dependency/Container.cs | 10 +-
src/Util.Core/DictionaryExtensions.cs | 17 ++
src/Util.Core/Helpers/Ioc.cs | 16 +-
src/Util.Core/Helpers/String.cs | 197 ++++++++----
src/Util.Core/Http/IHttpRequest.cs | 16 +
src/Util.Core/Infrastructure/Bootstrapper.cs | 19 +-
.../Infrastructure/BootstrapperConfig.cs | 2 +-
.../Infrastructure/ServiceContext.cs | 15 +-
src/Util.Core/Properties/R.Designer.cs | 9 +
src/Util.Core/Properties/R.resx | 3 +
src/Util.Core/Sessions/ISession.cs | 14 +-
src/Util.Core/Sessions/NullSession.cs | 23 +-
.../ConnectionStringCollection.cs | 27 ++
.../ConnectionStringNameAttribute.cs | 38 +++
src/Util.Data.Abstractions/Usings.cs | 3 +-
src/Util.Data.Core/Filters/IFilter.cs | 3 +-
src/Util.Data.Core/Filters/IFilterManager.cs | 6 +
.../01-Util.Data.Dapper.Core.csproj | 2 +-
.../MySqlUnitOfWorkBase.cs | 5 +
.../OracleUnitOfWorkBase.cs | 5 +
.../PgSqlUnitOfWorkBase.cs | 5 +
.../SqlServerUnitOfWorkBase.cs | 13 +-
.../SqliteUnitOfWorkBase.cs | 5 +
.../01-Util.Data.EntityFrameworkCore.csproj | 1 +
.../FilterExtensions.cs | 21 +-
.../Filters/DeleteFilter.cs | 13 +-
.../Filters/FilterBase.cs | 15 +-
.../Filters/FilterManager.cs | 47 ++-
.../Filters/TenantFilter.cs | 36 +++
.../EntityFrameworkServiceRegistrar.cs | 3 +-
.../StoreBase.cs | 4 +-
.../UnitOfWorkBase.cs | 195 +++++++++---
src/Util.Data.EntityFrameworkCore/Usings.cs | 4 +
src/Util.Domain/Entities/AggregateRoot.cs | 35 ++-
src/Util.Domain/Events/IDomainEventManager.cs | 25 ++
src/Util.Domain/Usings.cs | 4 +-
.../IIntegrationEventBus.cs | 5 -
.../IIntegrationEventExtend.cs | 19 --
.../03-Util.Events.MediatR.csproj | 37 +++
.../AppBuilderExtensions.cs | 35 +++
src/Util.Events.MediatR/EventBase.cs | 7 +
src/Util.Events.MediatR/EventHandlerBase.cs | 30 ++
.../MediatREventBusServiceRegistrar.cs | 41 +++
.../ServiceRegistrarConfigExtensions.cs | 24 ++
src/Util.Events.MediatR/MediatREventBus.cs | 27 ++
src/Util.Events.MediatR/MediatROptions.cs | 11 +
src/Util.Events.MediatR/Usings.cs | 8 +
.../ILocalEventHandler.cs | 0
src/Util.Events/LocalEventBus.cs | 25 +-
.../Configuration/ProjectOptions.cs | 6 +-
.../Contexts/GeneratorContextBuilder.cs | 1 +
.../Contexts/ProjectContext.cs | 20 +-
.../01-Util.Images.ImageSharp.csproj | 2 +-
.../02-Util.Logging.Serilog.csproj | 2 +-
.../AppBuilderExtensions.cs | 2 +-
.../DaprMicroserviceClient.cs | 4 +-
.../Events/DaprIntegrationEventBus.cs | 56 +---
.../Events/EventLogPubsubCallback.cs | 5 +-
.../Events/IntegrationEventCount.cs | 17 ++
.../Events/IntegrationEventLogStore.cs | 47 ++-
.../Events/IntegrationEventManager.cs | 64 +++-
...DaprStateManage.cs => DaprStateManager.cs} | 14 +-
...Query.cs => DaprStateManagerBase.Query.cs} | 33 +-
...eManageBase.cs => DaprStateManagerBase.cs} | 28 +-
...ateManageOfT.cs => DaprStateManagerOfT.cs} | 14 +-
...xtensions.cs => StateManagerExtensions.cs} | 6 +-
.../Events/IIntegrationEventLogStore.cs | 15 +
.../Events/IIntegrationEventManager.cs | 15 +
.../Events/IntegrationEventLog.cs | 10 +
src/Util.Microservices/IMicroserviceClient.cs | 2 +-
.../{IStateManage.cs => IStateManager.cs} | 24 +-
...tateManageBase.cs => IStateManagerBase.cs} | 42 +--
src/Util.Microservices/PubsubNameAttribute.cs | 38 +++
src/Util.Microservices/TopicNameAttribute.cs | 38 +++
src/Util.Microservices/Usings.cs | 1 +
.../Authorization/DefaultPermissionManager.cs | 11 +-
.../Authorization/IPermissionManager.cs | 12 +-
src/Util.Security/Security/ClaimTypes.cs | 8 -
.../02-Util.Templates.Razor.csproj | 2 +-
.../01-Util.Tenants.Abstractions.csproj | 34 +++
.../ISwitchTenantManager.cs | 20 ++
src/Util.Tenants.Abstractions/ITenant.cs | 11 +
.../ITenantManager.cs | 31 ++
src/Util.Tenants.Abstractions/ITenantStore.cs | 12 +
.../IViewAllTenantManager.cs | 19 ++
.../NullTenantManager.cs | 42 +++
.../NullTenantStore.cs | 20 ++
src/Util.Tenants.Abstractions/TenantInfo.cs | 17 ++
src/Util.Tenants.Abstractions/Usings.cs | 10 +
src/Util.Tenants/02-Util.Tenants.csproj | 34 +++
src/Util.Tenants/AppBuilderExtensions.cs | 36 +++
src/Util.Tenants/ITenantResolver.cs | 16 +
.../Managements/SwitchTenantManager.cs | 30 ++
src/Util.Tenants/Managements/TenantManager.cs | 105 +++++++
.../Managements/ViewAllTenantManager.cs | 33 ++
.../Middlewares/TenantMiddleware.cs | 45 +++
.../Resolvers/CookieTenantResolver.cs | 16 +
.../Resolvers/DefaultTenantResolver.cs | 36 +++
.../Resolvers/DomainTenantResolver.cs | 115 +++++++
.../Resolvers/HeaderTenantResolver.cs | 17 ++
.../Resolvers/IDomainTenantResolver.cs | 14 +
.../Resolvers/QueryStringTenantResolver.cs | 16 +
.../Resolvers/TenantResolverBase.cs | 36 +++
.../TenantMiddlewareExtensions.cs | 16 +
src/Util.Tenants/TenantOptions.cs | 56 ++++
src/Util.Tenants/TenantResolverCollection.cs | 99 ++++++
src/Util.Tenants/Usings.cs | 13 +
.../Util.Aop.AspectCore.Tests.csproj | 4 +-
...ameworkCore.MySql.Tests.Integration.csproj | 4 +-
...rkCore.PostgreSql.Tests.Integration.csproj | 4 +-
...orkCore.SqlServer.Tests.Integration.csproj | 4 +-
.../Util.Application.Tests.csproj | 2 +-
...tion.WebApi.MySql.Tests.Integration.csproj | 4 +-
...WebApi.PostgreSql.Tests.Integration.csproj | 4 +-
....WebApi.SqlServer.Tests.Integration.csproj | 4 +-
...pplication.WebApi.Tests.Integration.csproj | 4 +-
.../Controllers/Test1Controller.cs | 8 +
.../Http/HttpClientServiceTest.Get.cs | 22 ++
.../Util.AspNetCore.Tests.Integration.csproj | 4 +-
...ching.EasyCaching.Tests.Integration.csproj | 4 +-
.../Helpers/FileTest.cs | 54 ++--
.../Util.Core.Tests.Integration.csproj | 2 +-
test/Util.Core.Tests/Helpers/StringTest.cs | 251 +++++++++++++---
test/Util.Core.Tests/Util.Core.Tests.csproj | 2 +-
...Data.Dapper.MySql.Tests.Integration.csproj | 4 +-
...Dapper.PostgreSql.Tests.Integration.csproj | 4 +-
....Dapper.SqlServer.Tests.Integration.csproj | 4 +-
.../Filters/DeleteFilterTest.cs | 81 -----
.../Filters/FilterManagerTest.cs | 100 -------
...ameworkCore.MySql.Tests.Integration.csproj | 4 +-
.../Filters/DeleteFilterTest.cs | 81 -----
.../Filters/FilterManagerTest.cs | 101 -------
...meworkCore.Oracle.Tests.Integration.csproj | 4 +-
.../Filters/DeleteFilterTest.cs | 81 -----
.../Filters/FilterManagerTest.cs | 101 -------
...rkCore.PostgreSql.Tests.Integration.csproj | 4 +-
.../Filters/DeleteFilterTest.cs | 17 +-
.../Filters/FilterManagerTest.cs | 47 ++-
.../Samples/Test.cs | 24 ++
.../Samples/TestFilter.cs | 33 ++
.../Startup.cs | 2 +
.../Tests/EntityEventsTest.cs | 71 ++++-
.../Tests/ProductRepositoryTest.cs | 89 +++++-
.../Tests/UnitOfWorkActionManagerTest.cs | 123 --------
...orkCore.SqlServer.Tests.Integration.csproj | 4 +-
.../Filters/DeleteFilterTest.cs | 81 -----
.../Filters/FilterManagerTest.cs | 101 -------
...meworkCore.Sqlite.Tests.Integration.csproj | 4 +-
.../Util.Data.Sql.Tests.csproj | 2 +-
test/Util.Data.Tests/Util.Data.Tests.csproj | 2 +-
.../Util.Domain.Tests.csproj | 2 +-
.../Local/EventBusExtensionsTest.cs | 38 +++
.../Local/EventBusTest.cs | 31 ++
.../Local/LocalEventBusTest.cs | 43 +++
.../Samples/EventHandlerSample.cs | 50 ++++
.../Samples/EventSample.cs | 35 +++
.../Startup.cs | 27 ++
.../Usings.cs | 1 +
...il.Events.MediatR.Tests.Integration.csproj | 30 ++
test/Util.Events.Tests.Integration/Startup.cs | 2 -
.../Util.Events.Tests.Integration.csproj | 9 +-
.../Samples/TestSession.cs | 2 +
...FileStorage.Minio.Tests.Integration.csproj | 4 +-
....Generators.Razor.Tests.Integration.csproj | 4 +-
.../GeneratorOptionsBuilderTest.cs | 2 +
.../Contexts/GeneratorContextBuilderTest.cs | 11 +
.../Contexts/ProjectContextTest.cs | 7 +-
.../Mocks/MockGeneratorOptionsBuilder.cs | 3 +
.../Util.Generators.Tests.csproj | 2 +-
test/Util.Generators.Tests/appsettings.json | 2 +
...til.Images.Avatar.Tests.Integration.csproj | 4 +-
...Images.ImageSharp.Tests.Integration.csproj | 4 +-
...Util.Localization.Tests.Integration.csproj | 4 +-
...log.Exceptionless.Tests.Integration.csproj | 4 +-
...l.Logging.Serilog.Tests.Integration.csproj | 4 +-
.../Util.Logging.Tests.csproj | 2 +-
.../Controllers/IntegrationEventController.cs | 8 +-
.../Events/PubsubTest.EventLog.cs | 17 +-
.../Events/PubsubTest.cs | 8 +-
.../Samples/CustomerDto.cs | 2 +-
...anageOfTTest.cs => StateManagerOfTTest.cs} | 22 +-
...est.Query.cs => StateManagerTest.Query.cs} | 85 ++++--
...StateManageTest.cs => StateManagerTest.cs} | 114 +++----
...icroservices.Dapr.Tests.Integration.csproj | 4 +-
.../Util.Microservices.Dapr.Tests.csproj | 8 +-
.../Controllers/IntegrationEventController.cs | 8 +-
...croservices.Polly.Tests.Integration.csproj | 4 +-
...Util.ObjectMapping.AutoMapper.Tests.csproj | 4 +-
...heduling.Hangfire.Tests.Integration.csproj | 4 +-
...Scheduling.Quartz.Tests.Integration.csproj | 4 +-
.../Util.Security.Tests.csproj | 2 +-
...plates.Handlebars.Tests.Integration.csproj | 4 +-
...l.Templates.Razor.Tests.Integration.csproj | 4 +-
.../Controllers/TestController.cs | 29 ++
.../Properties/launchSettings.json | 12 +
.../Resolvers/CookieTenantResolverTest.cs | 34 +++
.../Resolvers/DomainTenantResolverTest.cs | 28 ++
.../Resolvers/HeaderTenantResolverTest.cs | 34 +++
.../QueryStringTenantResolverTest.cs | 33 ++
.../Util.Tenants.Tests.Integration/Startup.cs | 43 +++
test/Util.Tenants.Tests.Integration/Usings.cs | 12 +
.../Util.Tenants.Tests.Integration.csproj | 32 ++
.../Managements/TenantManagerTest.cs | 281 ++++++++++++++++++
.../Resolvers/CookieTenantResolverTest.cs | 70 +++++
.../Resolvers/DefaultTenantResolverTest.cs | 130 ++++++++
.../Resolvers/DomainTenantResolverTest.cs | 194 ++++++++++++
.../Resolvers/HeaderTenantResolverTest.cs | 73 +++++
.../QueryStringTenantResolverTest.cs | 73 +++++
.../Resolvers/TenantResolverCollectionTest.cs | 86 ++++++
.../Util.Tenants.Tests/Samples/TestSession.cs | 22 ++
.../Samples/TestTenantResolver.cs | 63 ++++
test/Util.Tenants.Tests/Usings.cs | 12 +
.../Util.Tenants.Tests.csproj | 26 ++
.../ProductConfiguration.cs | 2 +-
.../UnitOfWorks/SqlServerUnitOfWork.cs | 2 +-
.../EventHandlers/TestEventHandler.cs | 33 ++
test/Util.TestShare/Events/TestEvent.cs | 14 +
.../Fakes/ProductFakeService.cs | 5 +-
.../Infrastructure/TestSession.cs | 6 +
test/Util.TestShare/Models/Product.cs | 36 ++-
.../UnitOfWorks/ITestUnitOfWork.cs | 6 +
test/Util.TestShare/Util.TestShare.csproj | 1 +
.../Util.Ui.NgAlain.Tests.csproj | 4 +-
.../Util.Ui.NgZorro.Tests.csproj | 4 +-
test/Util.Ui.Tests/Util.Ui.Tests.csproj | 2 +-
.../Util.Validation.Tests.csproj | 4 +-
236 files changed, 5097 insertions(+), 1596 deletions(-)
create mode 100644 src/Util.Caching/NullCache.cs
create mode 100644 src/Util.Core/DictionaryExtensions.cs
create mode 100644 src/Util.Data.Abstractions/ConnectionStringCollection.cs
create mode 100644 src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs
create mode 100644 src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs
create mode 100644 src/Util.Domain/Events/IDomainEventManager.cs
delete mode 100644 src/Util.Events.Abstractions/IIntegrationEventExtend.cs
create mode 100644 src/Util.Events.MediatR/03-Util.Events.MediatR.csproj
create mode 100644 src/Util.Events.MediatR/AppBuilderExtensions.cs
create mode 100644 src/Util.Events.MediatR/EventBase.cs
create mode 100644 src/Util.Events.MediatR/EventHandlerBase.cs
create mode 100644 src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs
create mode 100644 src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs
create mode 100644 src/Util.Events.MediatR/MediatREventBus.cs
create mode 100644 src/Util.Events.MediatR/MediatROptions.cs
create mode 100644 src/Util.Events.MediatR/Usings.cs
rename src/{Util.Events.Abstractions => Util.Events}/ILocalEventHandler.cs (100%)
create mode 100644 src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs
rename src/Util.Microservices.Dapr/StateManagements/{DaprStateManage.cs => DaprStateManager.cs} (55%)
rename src/Util.Microservices.Dapr/StateManagements/{DaprStateManageBase.Query.cs => DaprStateManagerBase.Query.cs} (84%)
rename src/Util.Microservices.Dapr/StateManagements/{DaprStateManageBase.cs => DaprStateManagerBase.cs} (90%)
rename src/Util.Microservices.Dapr/StateManagements/{DaprStateManageOfT.cs => DaprStateManagerOfT.cs} (55%)
rename src/Util.Microservices.Dapr/{StateManageExtensions.cs => StateManagerExtensions.cs} (70%)
rename src/Util.Microservices/{IStateManage.cs => IStateManager.cs} (60%)
rename src/Util.Microservices/{IStateManageBase.cs => IStateManagerBase.cs} (82%)
create mode 100644 src/Util.Microservices/PubsubNameAttribute.cs
create mode 100644 src/Util.Microservices/TopicNameAttribute.cs
create mode 100644 src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj
create mode 100644 src/Util.Tenants.Abstractions/ISwitchTenantManager.cs
create mode 100644 src/Util.Tenants.Abstractions/ITenant.cs
create mode 100644 src/Util.Tenants.Abstractions/ITenantManager.cs
create mode 100644 src/Util.Tenants.Abstractions/ITenantStore.cs
create mode 100644 src/Util.Tenants.Abstractions/IViewAllTenantManager.cs
create mode 100644 src/Util.Tenants.Abstractions/NullTenantManager.cs
create mode 100644 src/Util.Tenants.Abstractions/NullTenantStore.cs
create mode 100644 src/Util.Tenants.Abstractions/TenantInfo.cs
create mode 100644 src/Util.Tenants.Abstractions/Usings.cs
create mode 100644 src/Util.Tenants/02-Util.Tenants.csproj
create mode 100644 src/Util.Tenants/AppBuilderExtensions.cs
create mode 100644 src/Util.Tenants/ITenantResolver.cs
create mode 100644 src/Util.Tenants/Managements/SwitchTenantManager.cs
create mode 100644 src/Util.Tenants/Managements/TenantManager.cs
create mode 100644 src/Util.Tenants/Managements/ViewAllTenantManager.cs
create mode 100644 src/Util.Tenants/Middlewares/TenantMiddleware.cs
create mode 100644 src/Util.Tenants/Resolvers/CookieTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/DefaultTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/DomainTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/HeaderTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/IDomainTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs
create mode 100644 src/Util.Tenants/Resolvers/TenantResolverBase.cs
create mode 100644 src/Util.Tenants/TenantMiddlewareExtensions.cs
create mode 100644 src/Util.Tenants/TenantOptions.cs
create mode 100644 src/Util.Tenants/TenantResolverCollection.cs
create mode 100644 src/Util.Tenants/Usings.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/DeleteFilterTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.MySql.Tests.Integration/Filters/FilterManagerTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/DeleteFilterTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.Oracle.Tests.Integration/Filters/FilterManagerTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/DeleteFilterTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.PostgreSql.Tests.Integration/Filters/FilterManagerTest.cs
create mode 100644 test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/Test.cs
create mode 100644 test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Samples/TestFilter.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.SqlServer.Tests.Integration/Tests/UnitOfWorkActionManagerTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/DeleteFilterTest.cs
delete mode 100644 test/Util.Data.EntityFrameworkCore.Sqlite.Tests.Integration/Filters/FilterManagerTest.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Local/EventBusExtensionsTest.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Local/EventBusTest.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Local/LocalEventBusTest.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Samples/EventHandlerSample.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Samples/EventSample.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Startup.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Usings.cs
create mode 100644 test/Util.Events.MediatR.Tests.Integration/Util.Events.MediatR.Tests.Integration.csproj
rename test/Util.Microservices.Dapr.Tests.Integration/StateManagements/{StateManageOfTTest.cs => StateManagerOfTTest.cs} (67%)
rename test/Util.Microservices.Dapr.Tests.Integration/StateManagements/{StateManageTest.Query.cs => StateManagerTest.Query.cs} (67%)
rename test/Util.Microservices.Dapr.Tests.Integration/StateManagements/{StateManageTest.cs => StateManagerTest.cs} (62%)
create mode 100644 test/Util.Tenants.Tests.Integration/Controllers/TestController.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Properties/launchSettings.json
create mode 100644 test/Util.Tenants.Tests.Integration/Resolvers/CookieTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Resolvers/DomainTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Resolvers/HeaderTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Resolvers/QueryStringTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Startup.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Usings.cs
create mode 100644 test/Util.Tenants.Tests.Integration/Util.Tenants.Tests.Integration.csproj
create mode 100644 test/Util.Tenants.Tests/Managements/TenantManagerTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/CookieTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/DefaultTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/DomainTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/HeaderTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/QueryStringTenantResolverTest.cs
create mode 100644 test/Util.Tenants.Tests/Resolvers/TenantResolverCollectionTest.cs
create mode 100644 test/Util.Tenants.Tests/Samples/TestSession.cs
create mode 100644 test/Util.Tenants.Tests/Samples/TestTenantResolver.cs
create mode 100644 test/Util.Tenants.Tests/Usings.cs
create mode 100644 test/Util.Tenants.Tests/Util.Tenants.Tests.csproj
create mode 100644 test/Util.TestShare/EventHandlers/TestEventHandler.cs
diff --git a/Util.sln b/Util.sln
index 96dfd5c38..a4842aabe 100644
--- a/Util.sln
+++ b/Util.sln
@@ -21,7 +21,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Core.Tests", "test\Uti
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E6328DC1-C8F0-4DC6-941F-C6CAFFF783DF}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Ui", "14-Ui", "{354B3720-D050-487D-A46A-22BCFEE56D43}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Ui", "15-Ui", "{354B3720-D050-487D-A46A-22BCFEE56D43}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Ui", "src\Util.Ui\01-Util.Ui.csproj", "{5F7ACBE4-A1D8-4680-AA86-B87F8494BAC2}"
EndProject
@@ -31,7 +31,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Ui.NgZorro.Tests", "te
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01-Core", "01-Core", "{9BFB5DBE-8F64-45A9-BDB2-BAD91D44A4C5}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Ui", "15-Ui", "{DE39BB20-AE82-47C3-9141-8CFD4C673217}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Ui", "16-Ui", "{DE39BB20-AE82-47C3-9141-8CFD4C673217}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Ui.Tests", "test\Util.Ui.Tests\Util.Ui.Tests.csproj", "{766CAB38-F064-419F-837B-D6DA8B61AF66}"
EndProject
@@ -49,7 +49,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Aop.AspectCore", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Aop.AspectCore.Tests", "test\Util.Aop.AspectCore.Tests\Util.Aop.AspectCore.Tests.csproj", "{B8825802-81F7-4671-82E8-3A6E3A21F570}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Domain", "10-Domain", "{7AB71D86-D351-4009-A789-24097897E74A}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Domain", "11-Domain", "{7AB71D86-D351-4009-A789-24097897E74A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "04-Util.Validation", "src\Util.Validation\04-Util.Validation.csproj", "{1DE277C2-EE56-4F4D-BB89-71107DE0E4C6}"
EndProject
@@ -57,15 +57,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Validation.Tests", "te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Domain", "src\Util.Domain\01-Util.Domain.csproj", "{61982C8D-5376-45EE-BCC3-D4F9E90AF3B0}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "09-Domain", "09-Domain", "{FC5E00D1-ABA5-4168-8B8F-FA2A26D84068}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Domain", "10-Domain", "{FC5E00D1-ABA5-4168-8B8F-FA2A26D84068}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Domain.Tests", "test\Util.Domain.Tests\Util.Domain.Tests.csproj", "{7CA38535-297B-4DA9-AFF5-3199268EEAF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Events", "src\Util.Events\02-Util.Events.csproj", "{EE426441-DE33-4F3B-B66A-E258AC725B78}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Data", "11-Data", "{66A85767-2877-4B99-84F1-EEABC6DC5247}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Data", "12-Data", "{66A85767-2877-4B99-84F1-EEABC6DC5247}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Data", "12-Data", "{CC492CA2-5D86-4DB8-AB15-B118DF27C344}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Data", "13-Data", "{CC492CA2-5D86-4DB8-AB15-B118DF27C344}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Data.Tests", "test\Util.Data.Tests\Util.Data.Tests.csproj", "{4D40321F-2C3F-4ACB-8AE0-7A228DF0551B}"
EndProject
@@ -99,11 +99,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "05-Util.Data.Dapper.All", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Domain.Biz", "src\Util.Domain.Biz\02-Util.Domain.Biz.csproj", "{AFB629C5-00C2-492C-8787-C66D810A24DB}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Application", "12-Application", "{648CE3E3-CEC4-4F91-8696-6A3FB2F91220}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Application", "13-Application", "{648CE3E3-CEC4-4F91-8696-6A3FB2F91220}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Application", "src\Util.Application\01-Util.Application.csproj", "{9404B5FE-3431-4EC0-BCC6-42525FC5D1B8}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Application", "13-Application", "{BB24822F-F104-4D8E-9CB5-0A94DEF04BAB}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Application", "14-Application", "{BB24822F-F104-4D8E-9CB5-0A94DEF04BAB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Application.Tests", "test\Util.Application.Tests\Util.Application.Tests.csproj", "{EF7BDA1B-22E7-4F3D-97E0-16C9C10AA1B0}"
EndProject
@@ -139,7 +139,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Logging.Serilog", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Logging.Serilog.Exceptionless", "src\Util.Logging.Serilog.Exceptionless\03-Util.Logging.Serilog.Exceptionless.csproj", "{9960FA8A-98C1-45FB-8A09-33CA183AA140}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Share", "10-Share", "{E3414144-C19E-40C0-A73A-2FD41AE514D1}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Share", "11-Share", "{E3414144-C19E-40C0-A73A-2FD41AE514D1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.TestShare", "test\Util.TestShare\Util.TestShare.csproj", "{1855CAB7-6379-47A4-A0A4-F890C6C46403}"
EndProject
@@ -219,7 +219,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.AspNetCore.Tests.Integ
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "06-Scheduling", "06-Scheduling", "{76AA6040-AA3A-43DD-BD4B-7F931271BFD6}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-Scheduling", "11-Scheduling", "{6493D592-7930-4979-A07D-B7CAA751C7BE}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "12-Scheduling", "12-Scheduling", "{6493D592-7930-4979-A07D-B7CAA751C7BE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Scheduling.Quartz", "src\Util.Scheduling.Quartz\02-Util.Scheduling.Quartz.csproj", "{4E12B03E-D607-492E-BB28-96235993A39F}"
EndProject
@@ -257,13 +257,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Images.Avatar", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Images.Avatar.Tests.Integration", "test\Util.Images.Avatar.Tests.Integration\Util.Images.Avatar.Tests.Integration.csproj", "{75A31AAB-B7E6-4F13-9B7A-B74216B2EDDE}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Generators", "15-Generators", "{411247A4-4665-4BE6-8E5C-08F6D0BA2213}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Generators", "16-Generators", "{411247A4-4665-4BE6-8E5C-08F6D0BA2213}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Generators", "src\Util.Generators\01-Util.Generators.csproj", "{36990D32-6F41-44A6-BB88-A3F15E12C35B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Generators.Razor", "src\Util.Generators.Razor\02-Util.Generators.Razor.csproj", "{21831A4F-B3C9-40E7-855B-75F9D679FF55}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "16-Generators", "16-Generators", "{F7D4E7CB-468D-4570-8D82-D065F08BE40D}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "17-Generators", "17-Generators", "{F7D4E7CB-468D-4570-8D82-D065F08BE40D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Generators.Tests", "test\Util.Generators.Tests\Util.Generators.Tests.csproj", "{5996F536-14B9-4F95-A18B-716E5F7EE478}"
EndProject
@@ -285,11 +285,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Generators.Razor.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Events.Tests.Integration", "test\Util.Events.Tests.Integration\Util.Events.Tests.Integration.csproj", "{FB11256C-06C0-4B24-863A-C87D78F8EE2A}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Microservices", "14-Microservices", "{547FD391-A56B-47A6-B20C-5FC04FA15C56}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "15-Microservices", "15-Microservices", "{547FD391-A56B-47A6-B20C-5FC04FA15C56}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Events.Abstractions", "src\Util.Events.Abstractions\01-Util.Events.Abstractions.csproj", "{6233BF36-9636-43BD-9462-2D31A05EE4ED}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "13-Microservices", "13-Microservices", "{1DEC52C8-6692-4DA7-AE34-B76D728F895A}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14-Microservices", "14-Microservices", "{1DEC52C8-6692-4DA7-AE34-B76D728F895A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Microservices", "src\Util.Microservices\01-Util.Microservices.csproj", "{F8417623-6936-43C4-A967-9D6BD002AD16}"
EndProject
@@ -305,7 +305,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "04-Util.Microservices.Healt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.WebApiSample", "test\Util.Microservices.Dapr.WebApiSample\Util.Microservices.Dapr.WebApiSample.csproj", "{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.PubsubSample", "test\Util.Microservices.Dapr.PubsubSample\Util.Microservices.Dapr.PubsubSample.csproj", "{BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.PubsubSample", "test\Util.Microservices.Dapr.PubsubSample\Util.Microservices.Dapr.PubsubSample.csproj", "{5D74BD1D-3142-4478-9062-4543FADA3E91}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Microservices.Dapr.Tests", "test\Util.Microservices.Dapr.Tests\Util.Microservices.Dapr.Tests.csproj", "{B737883C-9450-46CB-B81C-1A3405071D04}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02-Util.Tenants", "src\Util.Tenants\02-Util.Tenants.csproj", "{156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Tenants.Tests", "test\Util.Tenants.Tests\Util.Tenants.Tests.csproj", "{DE51B662-C843-4E99-87F8-F367504D5640}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-Tenant", "10-Tenant", "{98F1FEAD-AFD5-4744-8A55-C57920461EB9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "09-Tenant", "09-Tenant", "{835A8155-411B-4DF2-AE58-D3FDEFB61E63}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01-Util.Tenants.Abstractions", "src\Util.Tenants.Abstractions\01-Util.Tenants.Abstractions.csproj", "{0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03-Util.Events.MediatR", "src\Util.Events.MediatR\03-Util.Events.MediatR.csproj", "{FA6AF0F1-B4EE-4E46-B933-9573861A10FC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Events.MediatR.Tests.Integration", "test\Util.Events.MediatR.Tests.Integration\Util.Events.MediatR.Tests.Integration.csproj", "{4FECE655-7B06-4DED-BED0-B67731A43E0B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Util.Tenants.Tests.Integration", "test\Util.Tenants.Tests.Integration\Util.Tenants.Tests.Integration.csproj", "{6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -749,10 +767,38 @@ Global
{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0}.Release|Any CPU.Build.0 = Release|Any CPU
- {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D74BD1D-3142-4478-9062-4543FADA3E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D74BD1D-3142-4478-9062-4543FADA3E91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D74BD1D-3142-4478-9062-4543FADA3E91}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D74BD1D-3142-4478-9062-4543FADA3E91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B737883C-9450-46CB-B81C-1A3405071D04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B737883C-9450-46CB-B81C-1A3405071D04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B737883C-9450-46CB-B81C-1A3405071D04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B737883C-9450-46CB-B81C-1A3405071D04}.Release|Any CPU.Build.0 = Release|Any CPU
+ {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE51B662-C843-4E99-87F8-F367504D5640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE51B662-C843-4E99-87F8-F367504D5640}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE51B662-C843-4E99-87F8-F367504D5640}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE51B662-C843-4E99-87F8-F367504D5640}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA6AF0F1-B4EE-4E46-B933-9573861A10FC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FECE655-7B06-4DED-BED0-B67731A43E0B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -904,7 +950,16 @@ Global
{DF1E55A5-7EA8-41B2-BEBA-72544D256069} = {547FD391-A56B-47A6-B20C-5FC04FA15C56}
{9C58E50F-0589-4A4E-B94D-2EC0BF02F61F} = {1DEC52C8-6692-4DA7-AE34-B76D728F895A}
{AE19ED93-AFC4-4F91-93BD-B00D343D3BA0} = {547FD391-A56B-47A6-B20C-5FC04FA15C56}
- {BEEA3FA6-D6B6-48F5-AE6D-9625430B7A86} = {547FD391-A56B-47A6-B20C-5FC04FA15C56}
+ {5D74BD1D-3142-4478-9062-4543FADA3E91} = {547FD391-A56B-47A6-B20C-5FC04FA15C56}
+ {B737883C-9450-46CB-B81C-1A3405071D04} = {547FD391-A56B-47A6-B20C-5FC04FA15C56}
+ {156447ED-8515-4FB0-8B5A-D5A3A1E43AC6} = {98F1FEAD-AFD5-4744-8A55-C57920461EB9}
+ {DE51B662-C843-4E99-87F8-F367504D5640} = {835A8155-411B-4DF2-AE58-D3FDEFB61E63}
+ {98F1FEAD-AFD5-4744-8A55-C57920461EB9} = {C01B9930-D67F-41C5-90C9-C87DC53F39CB}
+ {835A8155-411B-4DF2-AE58-D3FDEFB61E63} = {E6328DC1-C8F0-4DC6-941F-C6CAFFF783DF}
+ {0C062400-58D1-4EE3-8EBB-C5B3AAEECF87} = {98F1FEAD-AFD5-4744-8A55-C57920461EB9}
+ {FA6AF0F1-B4EE-4E46-B933-9573861A10FC} = {59F82491-51E3-4A12-B427-9815AEA23D10}
+ {4FECE655-7B06-4DED-BED0-B67731A43E0B} = {3ABD7B4B-37F9-4D49-92D9-384190177286}
+ {6A84E4B0-3A9C-4B40-9B64-AD3A26324DAC} = {835A8155-411B-4DF2-AE58-D3FDEFB61E63}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {94347832-A36D-4C42-9C4D-B848BD4F5DA9}
diff --git a/build/version.props b/build/version.props
index ff0bd05be..c95ef3448 100644
--- a/build/version.props
+++ b/build/version.props
@@ -2,7 +2,7 @@
7
1
- 20
+ 22
$(VersionMajor).$(VersionMinor).$(VersionPatch)
diff --git a/src/Util.Aop.AspectCore/IgnoreAttribute.cs b/src/Util.Aop.AspectCore/IgnoreAttribute.cs
index d08cf4114..0032d80e7 100644
--- a/src/Util.Aop.AspectCore/IgnoreAttribute.cs
+++ b/src/Util.Aop.AspectCore/IgnoreAttribute.cs
@@ -3,5 +3,5 @@
///
/// 忽略拦截
///
-public class IgnoreAttribute : AspectCore.DynamicProxy.NonAspectAttribute {
+public class IgnoreAttribute : NonAspectAttribute {
}
\ No newline at end of file
diff --git a/src/Util.AspNetCore/Helpers/Web.cs b/src/Util.AspNetCore/Helpers/Web.cs
index 84c3abef6..78cf379f2 100644
--- a/src/Util.AspNetCore/Helpers/Web.cs
+++ b/src/Util.AspNetCore/Helpers/Web.cs
@@ -343,4 +343,60 @@ public static async Task DownloadAsync( byte[] bytes, string fileName, Encoding
}
#endregion
+
+ #region GetCookie(获取Cookie)
+
+ ///
+ /// 获取Cookie
+ ///
+ /// cookie键名
+ public static string GetCookie( string key ) {
+ return Request?.Cookies[key];
+ }
+
+ #endregion
+
+ #region SetCookie(设置Cookie)
+
+ ///
+ /// 设置Cookie
+ ///
+ /// cookie键名
+ /// 值
+ public static void SetCookie( string key, string value ) {
+ Response?.Cookies.Append( key, value );
+ }
+
+ ///
+ /// 设置Cookie
+ ///
+ /// cookie键名
+ /// 值
+ /// Cookie配置
+ public static void SetCookie( string key,string value,CookieOptions options ) {
+ Response?.Cookies.Append( key, value, options );
+ }
+
+ #endregion
+
+ #region RemoveCookie(移除Cookie)
+
+ ///
+ /// 移除Cookie
+ ///
+ /// cookie键名
+ public static void RemoveCookie( string key ) {
+ Response?.Cookies.Delete( key );
+ }
+
+ ///
+ /// 移除Cookie
+ ///
+ /// cookie键名
+ /// Cookie配置
+ public static void RemoveCookie( string key, CookieOptions options ) {
+ Response?.Cookies.Delete( key, options );
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Util.AspNetCore/Http/HttpRequest.cs b/src/Util.AspNetCore/Http/HttpRequest.cs
index 44045ebb4..bf16d337d 100644
--- a/src/Util.AspNetCore/Http/HttpRequest.cs
+++ b/src/Util.AspNetCore/Http/HttpRequest.cs
@@ -1,4 +1,5 @@
-using System.Text.Encodings.Web;
+using Microsoft.Net.Http.Headers;
+using System.Text.Encodings.Web;
using System.Text.Unicode;
using Util.Helpers;
using Util.SystemTextJson;
@@ -61,6 +62,7 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient,
HeaderParams = new Dictionary();
QueryParams = new Dictionary();
Params = new Dictionary();
+ Cookies = new Dictionary();
HttpContentType = Util.Http.HttpContentType.Json.Description();
CharacterEncoding = System.Text.Encoding.UTF8;
}
@@ -94,6 +96,10 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient,
///
protected IDictionary HeaderParams { get; }
///
+ /// Cookie参数集合
+ ///
+ protected IDictionary Cookies { get; }
+ ///
/// 查询字符串参数集合
///
protected IDictionary QueryParams { get; }
@@ -106,6 +112,10 @@ public HttpRequest( IHttpClientFactory httpClientFactory, HttpClient httpClient,
///
protected object Param { get; private set; }
///
+ /// 是否自动携带cookie
+ ///
+ protected bool? IsUseCookies { get; private set; }
+ ///
/// 发送前操作
///
protected Func SendBeforeAction { get; private set; }
@@ -299,6 +309,39 @@ public IHttpRequest QueryString( object queryString ) {
#endregion
+ #region UseCookies(设置是否自动携带cookie)
+
+ ///
+ public IHttpRequest UseCookies( bool isUseCookies = true ) {
+ IsUseCookies = isUseCookies;
+ return this;
+ }
+
+ #endregion
+
+ #region Cookie(设置Cookie)
+
+ ///
+ public IHttpRequest Cookie( string key, string value ) {
+ if ( key.IsEmpty() )
+ return this;
+ if ( Cookies.ContainsKey( key ) )
+ Cookies.Remove( key );
+ Cookies.Add( key, value );
+ return this;
+ }
+
+ ///
+ public IHttpRequest Cookie( IDictionary cookies ) {
+ if ( Cookies == null )
+ return this;
+ foreach ( var cookie in Cookies )
+ Cookie( cookie.Key, cookie.Value );
+ return this;
+ }
+
+ #endregion
+
#region Content(添加内容参数)
///
@@ -439,6 +482,7 @@ public IHttpRequest OnComplete( Action act
///
protected virtual HttpRequestMessage CreateMessage() {
var message = new HttpRequestMessage( _httpMethod, GetUrl( _url ) );
+ AddCookies();
AddHeaders( message );
message.Content = CreateHttpContent();
return message;
@@ -452,6 +496,18 @@ protected virtual string GetUrl( string url ) {
return QueryHelpers.AddQueryString( url, QueryParams );
}
+ ///
+ /// 添加Cookie
+ ///
+ protected virtual void AddCookies() {
+ if ( Cookies.Count == 0 )
+ return;
+ var cookieValues = new List();
+ foreach ( var cookie in Cookies )
+ cookieValues.Add( new CookieHeaderValue( cookie.Key, cookie.Value ) );
+ Header( "Cookie", cookieValues.Select( t => t.ToString() ).Join() );
+ }
+
///
/// 添加请求头
///
@@ -582,6 +638,7 @@ protected HttpClient GetClient() {
return _httpClient;
var clientHandler = CreateHttpClientHandler();
InitHttpClientHandler( clientHandler );
+ InitUseCookies( clientHandler );
return _httpClientName.IsEmpty() ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient( _httpClientName );
}
@@ -624,6 +681,18 @@ protected virtual void InitCertificate( HttpClientHandler handler ) {
#endregion
+ #region InitUseCookies(初始化是否携带Cookie)
+
+ ///
+ /// 初始化是否携带Cookie
+ ///
+ protected virtual void InitUseCookies( HttpClientHandler handler ) {
+ if ( IsUseCookies == null )
+ handler.UseCookies = false;
+ }
+
+ #endregion
+
#region InitHttpClient(初始化Http客户端)
///
diff --git a/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs b/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs
index 8937c661f..d30dc9146 100644
--- a/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs
+++ b/src/Util.AspNetCore/Infrastructure/AspNetCoreServiceRegistrar.cs
@@ -1,6 +1,5 @@
using Util.Helpers;
using Util.Http;
-using Util.Sessions;
using Util.SystemTextJson;
namespace Util.Infrastructure;
@@ -33,7 +32,6 @@ public Action Register( ServiceContext serviceContext ) {
RegisterHttpContextAccessor( services );
services.AddHttpClient();
RegisterServiceLocator();
- RegisterSession( services );
RegisterHttpClient( services );
ConfigJsonOptions( services );
} );
@@ -56,13 +54,6 @@ private void RegisterServiceLocator() {
Ioc.SetServiceProviderAction( () => Web.ServiceProvider );
}
- ///
- /// 注册用户会话
- ///
- private void RegisterSession( IServiceCollection services ) {
- services.TryAddSingleton();
- }
-
///
/// 注册Http客户端
///
diff --git a/src/Util.AspNetCore/Sessions/UserSession.cs b/src/Util.AspNetCore/Sessions/UserSession.cs
index 2ad2c7391..c5bacbbd4 100644
--- a/src/Util.AspNetCore/Sessions/UserSession.cs
+++ b/src/Util.AspNetCore/Sessions/UserSession.cs
@@ -1,5 +1,4 @@
using Util.Helpers;
-using Util.Security;
namespace Util.Sessions;
@@ -13,22 +12,24 @@ public class UserSession : ISession {
public static readonly ISession Null = NullSession.Instance;
///
- /// 用户会话
+ /// 用户会话实例
///
public static readonly ISession Instance = new UserSession();
- ///
- /// 是否认证
- ///
- public bool IsAuthenticated => Web.Identity.IsAuthenticated;
+ ///
+ public virtual IServiceProvider ServiceProvider => Web.ServiceProvider;
- ///
- /// 用户标识
- ///
- public string UserId {
+ ///
+ public virtual bool IsAuthenticated => Web.Identity.IsAuthenticated;
+
+ ///
+ public virtual string UserId {
get {
var result = Web.Identity.GetValue( ClaimTypes.UserId );
return result.IsEmpty() ? Web.Identity.GetValue( System.Security.Claims.ClaimTypes.NameIdentifier ) : result;
}
}
+
+ ///
+ public virtual string TenantId => Web.Identity.GetValue( ClaimTypes.TenantId );
}
\ No newline at end of file
diff --git a/src/Util.AspNetCore/Usings.cs b/src/Util.AspNetCore/Usings.cs
index f988abb31..34a9bb72f 100644
--- a/src/Util.AspNetCore/Usings.cs
+++ b/src/Util.AspNetCore/Usings.cs
@@ -27,4 +27,5 @@
global using Microsoft.AspNetCore.Mvc.Filters;
global using Microsoft.AspNetCore.DataProtection;
global using Microsoft.AspNetCore.Builder;
-global using Util.Dependency;
\ No newline at end of file
+global using Util.Dependency;
+global using Util.Security;
\ No newline at end of file
diff --git a/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj b/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj
index 25ec771d7..9489ba848 100644
--- a/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj
+++ b/src/Util.Caching.EasyCaching/02-Util.Caching.EasyCaching.csproj
@@ -27,9 +27,9 @@
-
-
-
+
+
+
diff --git a/src/Util.Caching/NullCache.cs b/src/Util.Caching/NullCache.cs
new file mode 100644
index 000000000..0d65a2f44
--- /dev/null
+++ b/src/Util.Caching/NullCache.cs
@@ -0,0 +1,230 @@
+namespace Util.Caching;
+
+///
+/// 空缓存
+///
+public class NullCache : ILocalCache {
+ ///
+ /// 缓存空实例
+ ///
+ public static readonly ILocalCache Instance = new NullCache();
+
+ ///
+ public bool Exists( CacheKey key ) {
+ return false;
+ }
+
+ ///
+ public bool Exists( string key ) {
+ return false;
+ }
+
+ ///
+ public Task ExistsAsync( CacheKey key, CancellationToken cancellationToken = default ) {
+ return Task.FromResult( false );
+ }
+
+ ///
+ public Task ExistsAsync( string key, CancellationToken cancellationToken = default ) {
+ return Task.FromResult( false );
+ }
+
+ ///
+ public T Get( CacheKey key ) {
+ return default;
+ }
+
+ ///
+ public T Get( string key ) {
+ return default;
+ }
+
+ ///
+ public List Get( IEnumerable keys ) {
+ return new List();
+ }
+
+ ///
+ public List Get( IEnumerable keys ) {
+ return new List();
+ }
+
+ ///
+ public T Get( CacheKey key, Func action, CacheOptions options = null ) {
+ return action == null ? default : action();
+ }
+
+ ///
+ public T Get( string key, Func action, CacheOptions options = null ) {
+ return action == null ? default : action();
+ }
+
+ ///
+ public Task
/// 值
public static string FirstUpperCase( string value ) {
- if( string.IsNullOrWhiteSpace( value ) )
+ if ( string.IsNullOrWhiteSpace( value ) )
return string.Empty;
var result = Rune.DecodeFromUtf16( value, out var rune, out var charsConsumed );
- if( result != OperationStatus.Done || Rune.IsUpper( rune ) )
+ if ( result != OperationStatus.Done || Rune.IsUpper( rune ) )
return value;
return Rune.ToUpperInvariant( rune ) + value[charsConsumed..];
}
@@ -67,9 +67,9 @@ public static string FirstUpperCase( string value ) {
/// 值
/// 要移除的值
public static string RemoveStart( string value, string start ) {
- if( string.IsNullOrWhiteSpace( value ) )
+ if ( string.IsNullOrWhiteSpace( value ) )
return string.Empty;
- if( string.IsNullOrEmpty( start ) )
+ if ( string.IsNullOrEmpty( start ) )
return value;
if ( value.StartsWith( start, StringComparison.Ordinal ) == false )
return value;
@@ -82,15 +82,15 @@ public static string RemoveStart( string value, string start ) {
/// 值
/// 要移除的值
public static StringBuilder RemoveStart( StringBuilder value, string start ) {
- if( value == null || value.Length == 0 )
+ if ( value == null || value.Length == 0 )
return null;
- if( string.IsNullOrEmpty( start ) )
+ if ( string.IsNullOrEmpty( start ) )
return value;
- if( start.Length > value.Length )
+ if ( start.Length > value.Length )
return value;
var chars = start.ToCharArray();
- for( int i = 0; i < chars.Length; i++ ) {
- if( value[i] != chars[i] )
+ for ( int i = 0; i < chars.Length; i++ ) {
+ if ( value[i] != chars[i] )
return value;
}
return value.Remove( 0, start.Length );
@@ -106,11 +106,11 @@ public static StringBuilder RemoveStart( StringBuilder value, string start ) {
/// 值
/// 要移除的值
public static string RemoveEnd( string value, string end ) {
- if( string.IsNullOrWhiteSpace( value ) )
+ if ( string.IsNullOrWhiteSpace( value ) )
return string.Empty;
- if( string.IsNullOrEmpty( end ) )
+ if ( string.IsNullOrEmpty( end ) )
return value;
- if( value.EndsWith( end, StringComparison.Ordinal ) == false )
+ if ( value.EndsWith( end, StringComparison.Ordinal ) == false )
return value;
return value.Substring( 0, value.LastIndexOf( end, StringComparison.Ordinal ) );
}
@@ -121,16 +121,16 @@ public static string RemoveEnd( string value, string end ) {
/// 值
/// 要移除的值
public static StringBuilder RemoveEnd( StringBuilder value, string end ) {
- if( value == null || value.Length == 0 )
+ if ( value == null || value.Length == 0 )
return null;
- if( string.IsNullOrEmpty( end ) )
+ if ( string.IsNullOrEmpty( end ) )
return value;
- if( end.Length > value.Length )
+ if ( end.Length > value.Length )
return value;
var chars = end.ToCharArray();
- for( int i = chars.Length-1; i >= 0; i-- ) {
+ for ( int i = chars.Length - 1; i >= 0; i-- ) {
var j = value.Length - ( chars.Length - i );
- if( value[j] != chars[i] )
+ if ( value[j] != chars[i] )
return value;
}
return value.Remove( value.Length - end.Length, end.Length );
@@ -145,10 +145,10 @@ public static StringBuilder RemoveEnd( StringBuilder value, string end ) {
///
/// 汉字文本,范例: 中国
public static string PinYin( string chineseText ) {
- if( chineseText.IsEmpty() )
+ if ( chineseText.IsEmpty() )
return string.Empty;
var result = new StringBuilder();
- foreach( char text in chineseText )
+ foreach ( char text in chineseText )
result.Append( ResolvePinYin( text ) );
return result.ToString().ToLower();
}
@@ -158,11 +158,11 @@ public static string PinYin( string chineseText ) {
///
private static string ResolvePinYin( char text ) {
byte[] charBytes = Encoding.UTF8.GetBytes( text.ToString() );
- if( charBytes[0] <= 127 )
+ if ( charBytes[0] <= 127 )
return text.ToString();
var unicode = (ushort)( charBytes[0] * 256 + charBytes[1] );
string pinYin = ResolveByCode( unicode );
- if( pinYin.IsEmpty() == false )
+ if ( pinYin.IsEmpty() == false )
return pinYin;
return ResolveByConst( text.ToString() );
}
@@ -171,51 +171,51 @@ private static string ResolvePinYin( char text ) {
/// 使用字符编码方式获取拼音简码
///
private static string ResolveByCode( ushort unicode ) {
- if( unicode >= '\uB0A1' && unicode <= '\uB0C4' )
+ if ( unicode >= '\uB0A1' && unicode <= '\uB0C4' )
return "A";
- if( unicode >= '\uB0C5' && unicode <= '\uB2C0' && unicode != 45464 )
+ if ( unicode >= '\uB0C5' && unicode <= '\uB2C0' && unicode != 45464 )
return "B";
- if( unicode >= '\uB2C1' && unicode <= '\uB4ED' )
+ if ( unicode >= '\uB2C1' && unicode <= '\uB4ED' )
return "C";
- if( unicode >= '\uB4EE' && unicode <= '\uB6E9' )
+ if ( unicode >= '\uB4EE' && unicode <= '\uB6E9' )
return "D";
- if( unicode >= '\uB6EA' && unicode <= '\uB7A1' )
+ if ( unicode >= '\uB6EA' && unicode <= '\uB7A1' )
return "E";
- if( unicode >= '\uB7A2' && unicode <= '\uB8C0' )
+ if ( unicode >= '\uB7A2' && unicode <= '\uB8C0' )
return "F";
- if( unicode >= '\uB8C1' && unicode <= '\uB9FD' )
+ if ( unicode >= '\uB8C1' && unicode <= '\uB9FD' )
return "G";
- if( unicode >= '\uB9FE' && unicode <= '\uBBF6' )
+ if ( unicode >= '\uB9FE' && unicode <= '\uBBF6' )
return "H";
- if( unicode >= '\uBBF7' && unicode <= '\uBFA5' )
+ if ( unicode >= '\uBBF7' && unicode <= '\uBFA5' )
return "J";
- if( unicode >= '\uBFA6' && unicode <= '\uC0AB' )
+ if ( unicode >= '\uBFA6' && unicode <= '\uC0AB' )
return "K";
- if( unicode >= '\uC0AC' && unicode <= '\uC2E7' )
+ if ( unicode >= '\uC0AC' && unicode <= '\uC2E7' )
return "L";
- if( unicode >= '\uC2E8' && unicode <= '\uC4C2' )
+ if ( unicode >= '\uC2E8' && unicode <= '\uC4C2' )
return "M";
- if( unicode >= '\uC4C3' && unicode <= '\uC5B5' )
+ if ( unicode >= '\uC4C3' && unicode <= '\uC5B5' )
return "N";
- if( unicode >= '\uC5B6' && unicode <= '\uC5BD' )
+ if ( unicode >= '\uC5B6' && unicode <= '\uC5BD' )
return "O";
- if( unicode >= '\uC5BE' && unicode <= '\uC6D9' )
+ if ( unicode >= '\uC5BE' && unicode <= '\uC6D9' )
return "P";
- if( unicode >= '\uC6DA' && unicode <= '\uC8BA' )
+ if ( unicode >= '\uC6DA' && unicode <= '\uC8BA' )
return "Q";
- if( unicode >= '\uC8BB' && unicode <= '\uC8F5' )
+ if ( unicode >= '\uC8BB' && unicode <= '\uC8F5' )
return "R";
- if( unicode >= '\uC8F6' && unicode <= '\uCBF9' )
+ if ( unicode >= '\uC8F6' && unicode <= '\uCBF9' )
return "S";
- if( unicode >= '\uCBFA' && unicode <= '\uCDD9' )
+ if ( unicode >= '\uCBFA' && unicode <= '\uCDD9' )
return "T";
- if( unicode >= '\uCDDA' && unicode <= '\uCEF3' )
+ if ( unicode >= '\uCDDA' && unicode <= '\uCEF3' )
return "W";
- if( unicode >= '\uCEF4' && unicode <= '\uD188' )
+ if ( unicode >= '\uCEF4' && unicode <= '\uD188' )
return "X";
- if( unicode >= '\uD1B9' && unicode <= '\uD4D0' )
+ if ( unicode >= '\uD1B9' && unicode <= '\uD4D0' )
return "Y";
- if( unicode >= '\uD4D1' && unicode <= '\uD7F9' )
+ if ( unicode >= '\uD4D1' && unicode <= '\uD7F9' )
return "Z";
return string.Empty;
}
@@ -225,10 +225,107 @@ private static string ResolveByCode( ushort unicode ) {
///
private static string ResolveByConst( string text ) {
int index = Const.ChinesePinYin.IndexOf( text, StringComparison.Ordinal );
- if( index < 0 )
+ if ( index < 0 )
return string.Empty;
return Const.ChinesePinYin.Substring( index + 1, 1 );
}
#endregion
+
+ #region Extract(提取字符串中的变量值)
+
+ ///
+ /// 提取字符串中的变量值
+ ///
+ /// 原始值,范例: Hello,World
+ /// 字符串格式,范例: 原始值为Hello,World,格式为Hello,{value} ,则value变量的值为World
+ public static IDictionary Extract( string value, string format ) {
+ var result = new Dictionary();
+ if ( value.IsEmpty() )
+ return result;
+ if ( format.IsEmpty() )
+ return result;
+ if ( format.Contains( "{", StringComparison.Ordinal ) == false )
+ return result;
+ if ( format.Contains( "}", StringComparison.Ordinal ) == false )
+ return result;
+ var formatItems = SplitFormat( format.SafeString() );
+ return ExtractValue( value.SafeString(), formatItems );
+ }
+
+ ///
+ /// 拆分格式字符串
+ ///
+ private static List SplitFormat( string format ) {
+ var result = new List();
+ var item = new StringBuilder();
+ for ( int i = 0; i < format.Length; i++ ) {
+ var temp = format[i];
+ if ( temp == '{' ) {
+ item.RemoveEnd( "{" );
+ if ( i == 0 ) {
+ result.Add( string.Empty );
+ }
+ if ( item.Length > 0 ) {
+ result.Add( item.ToString() );
+ item.Clear();
+ }
+ item.Append( temp );
+ continue;
+ }
+ if ( temp == '}' ) {
+ if( item.ToString().IsEmpty() )
+ continue;
+ item.RemoveEnd( "}" );
+ item.Append( temp );
+ result.Add( item.ToString() );
+ item.Clear();
+ if ( i == format.Length - 1 )
+ result.Add( string.Empty );
+ continue;
+ }
+ item.Append( temp );
+ if ( i == format.Length - 1 ) {
+ result.Add( item.ToString() );
+ item.Clear();
+ }
+ }
+ return result;
+ }
+
+ ///
+ /// 提取字符串中变量值
+ ///
+ private static IDictionary ExtractValue( string value, List formatItems ) {
+ var result = new Dictionary();
+ var leftIndex = 0;
+ var length = 0;
+ for ( int i = 0; i < formatItems.Count; i++ ) {
+ var item = formatItems[i];
+ if ( item == string.Empty )
+ continue;
+ if ( item.StartsWith( "{", StringComparison.Ordinal ) == false ) {
+ leftIndex += item.Length;
+ continue;
+ }
+ if ( i + 1 < formatItems.Count ) {
+ var rightItem = formatItems[i + 1];
+ if ( rightItem == string.Empty )
+ length = value.Length - leftIndex;
+ else
+ length = value.IndexOf( rightItem, leftIndex + 1, StringComparison.OrdinalIgnoreCase ) - leftIndex;
+ }
+ var varName = item.Replace( "{", "" ).Replace( "}", "" );
+ if ( length <= 0 ) {
+ result.Add( varName, string.Empty );
+ continue;
+ }
+ var variableValue = value.Substring( leftIndex, length );
+ result.Add( varName, variableValue );
+ leftIndex += length;
+ }
+ return result;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Util.Core/Http/IHttpRequest.cs b/src/Util.Core/Http/IHttpRequest.cs
index 563ed7343..2c8a5fdca 100644
--- a/src/Util.Core/Http/IHttpRequest.cs
+++ b/src/Util.Core/Http/IHttpRequest.cs
@@ -85,6 +85,22 @@ public interface IHttpRequest : IHttpRequest where TResult : class {
/// 查询字符串对象
IHttpRequest QueryString( object queryString );
///
+ /// 设置是否自动携带cookie
+ ///
+ /// 是否自动携带cookie
+ IHttpRequest UseCookies( bool isUseCookies = true );
+ ///
+ /// 设置Cookie
+ ///
+ /// 键
+ /// 值
+ IHttpRequest Cookie( string key, string value );
+ ///
+ /// 设置Cookie集合
+ ///
+ /// Cookie集合
+ IHttpRequest Cookie( IDictionary cookies );
+ ///
/// 添加参数,作为请求内容发送
///
/// 键
diff --git a/src/Util.Core/Infrastructure/Bootstrapper.cs b/src/Util.Core/Infrastructure/Bootstrapper.cs
index 6efc10464..add89b6d4 100644
--- a/src/Util.Core/Infrastructure/Bootstrapper.cs
+++ b/src/Util.Core/Infrastructure/Bootstrapper.cs
@@ -18,9 +18,13 @@ public class Bootstrapper {
///
private readonly Action _setupAction;
///
+ /// 程序集查找器
+ ///
+ private readonly IAssemblyFinder _assemblyFinder;
+ ///
/// 类型查找器
///
- private readonly ITypeFinder _finder;
+ private readonly ITypeFinder _typeFinder;
///
/// 服务配置操作列表
///
@@ -34,8 +38,8 @@ public class Bootstrapper {
public Bootstrapper( IHostBuilder hostBuilder, Action setupAction = null ) {
_hostBuilder = hostBuilder ?? throw new ArgumentNullException( nameof( hostBuilder ) );
_setupAction = setupAction;
- var assemblyFinder = new AppDomainAssemblyFinder { AssemblySkipPattern = BootstrapperConfig.AssemblySkipPattern };
- _finder = new AppDomainTypeFinder( assemblyFinder );
+ _assemblyFinder = new AppDomainAssemblyFinder { AssemblySkipPattern = BootstrapperConfig.AssemblySkipPattern };
+ _typeFinder = new AppDomainTypeFinder( _assemblyFinder );
_serviceActions = new List();
}
@@ -56,7 +60,8 @@ public virtual void Start() {
protected virtual void SetConfiguration() {
_hostBuilder.ConfigureServices( ( context, services ) => {
Util.Helpers.Config.SetConfiguration( context.Configuration );
- services.TryAddSingleton( _finder );
+ services.TryAddSingleton( _assemblyFinder );
+ services.TryAddSingleton( _typeFinder );
} );
}
@@ -64,9 +69,9 @@ protected virtual void SetConfiguration() {
/// 解析服务注册器
///
protected virtual void ResolveServiceRegistrar() {
- var types = _finder.Find();
+ var types = _typeFinder.Find();
var instances = types.Select( type => Reflection.CreateInstance( type ) ).Where( t => t.Enabled ).OrderBy( t => t.OrderId ).ToList();
- var context = new ServiceContext( _hostBuilder, _finder );
+ var context = new ServiceContext( _hostBuilder, _assemblyFinder, _typeFinder );
instances.ForEach( t => _serviceActions.Add( t.Register( context ) ) );
}
@@ -81,7 +86,7 @@ protected virtual void ConfigOptions() {
/// 解析依赖注册器
///
protected virtual void ResolveDependencyRegistrar() {
- var types = _finder.Find();
+ var types = _typeFinder.Find();
var instances = types.Select( type => Reflection.CreateInstance( type ) ).OrderBy( t => t.Order ).ToList();
_hostBuilder.ConfigureServices( ( context, services ) => {
instances.ForEach( t => t.Register( services ) );
diff --git a/src/Util.Core/Infrastructure/BootstrapperConfig.cs b/src/Util.Core/Infrastructure/BootstrapperConfig.cs
index 2b397d33f..0f870f0a3 100644
--- a/src/Util.Core/Infrastructure/BootstrapperConfig.cs
+++ b/src/Util.Core/Infrastructure/BootstrapperConfig.cs
@@ -7,5 +7,5 @@ public class BootstrapperConfig {
///
/// 启动时扫描程序集的过滤模式
///
- public static string AssemblySkipPattern { get; set; } = "^System|^Mscorlib|^msvcr120|^Netstandard|^Microsoft|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously Hosted|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|sni|sos|SOS.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|WindowsBase|ucrtbase|^DotNetCore.CAP|^MongoDB|^Confluent.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^NSwag|^Humanizer|^NJsonSchema|^Namotion|^ReSharper|^JetBrains|^NuGet|^ProxyGenerator|^testhost";
+ public static string AssemblySkipPattern { get; set; } = "^System|^Mscorlib|^msvcr120|^Netstandard|^Microsoft|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously Hosted|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|sni|sos|SOS.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|WindowsBase|ucrtbase|^DotNetCore.CAP|^MongoDB|^Confluent.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^NSwag|^Humanizer|^NJsonSchema|^Namotion|^ReSharper|^JetBrains|^NuGet|^ProxyGenerator|^testhost|^MediatR|^Polly|^AspNetCore|^Minio|^SixLabors|^Quartz|^Hangfire|^Handlebars|^Serilog|^WebApiClientCore|^BouncyCastle|^RSAExtensions|^MartinCostello";
}
\ No newline at end of file
diff --git a/src/Util.Core/Infrastructure/ServiceContext.cs b/src/Util.Core/Infrastructure/ServiceContext.cs
index ca4dc8e7d..5a922b4aa 100644
--- a/src/Util.Core/Infrastructure/ServiceContext.cs
+++ b/src/Util.Core/Infrastructure/ServiceContext.cs
@@ -1,6 +1,6 @@
using Util.Reflections;
-namespace Util.Infrastructure;
+namespace Util.Infrastructure;
///
/// 服务上下文
@@ -10,10 +10,12 @@ public class ServiceContext {
/// 初始化服务上下文
///
/// 主机生成器
+ /// 程序集查找器
/// 类型查找器
- public ServiceContext( IHostBuilder hostBuilder, ITypeFinder typeFinder ) {
- HostBuilder = hostBuilder;
- TypeFinder = typeFinder;
+ public ServiceContext( IHostBuilder hostBuilder, IAssemblyFinder assemblyFinder, ITypeFinder typeFinder ) {
+ HostBuilder = hostBuilder ?? throw new ArgumentNullException( nameof( hostBuilder ) );
+ AssemblyFinder = assemblyFinder ?? throw new ArgumentNullException( nameof( assemblyFinder ) );
+ TypeFinder = typeFinder ?? throw new ArgumentNullException( nameof( typeFinder ) );
}
///
@@ -21,6 +23,11 @@ public ServiceContext( IHostBuilder hostBuilder, ITypeFinder typeFinder ) {
///
public IHostBuilder HostBuilder { get; }
+ ///
+ /// 程序集查找器
+ ///
+ public IAssemblyFinder AssemblyFinder { get; }
+
///
/// 类型查找器
///
diff --git a/src/Util.Core/Properties/R.Designer.cs b/src/Util.Core/Properties/R.Designer.cs
index 215fd508e..2effeff96 100644
--- a/src/Util.Core/Properties/R.Designer.cs
+++ b/src/Util.Core/Properties/R.Designer.cs
@@ -222,6 +222,15 @@ public static string SystemError {
}
}
+ ///
+ /// 查找类似 租户标识 的本地化字符串。
+ ///
+ public static string TenantId {
+ get {
+ return ResourceManager.GetString("TenantId", resourceCulture);
+ }
+ }
+
///
/// 查找类似 类型 {0} 不是枚举 的本地化字符串。
///
diff --git a/src/Util.Core/Properties/R.resx b/src/Util.Core/Properties/R.resx
index e584c26a8..89c75681a 100644
--- a/src/Util.Core/Properties/R.resx
+++ b/src/Util.Core/Properties/R.resx
@@ -172,6 +172,9 @@
系统忙,请稍后再试
抛出系统异常时显示该消息
+
+ 租户标识
+
类型 {0} 不是枚举
diff --git a/src/Util.Core/Sessions/ISession.cs b/src/Util.Core/Sessions/ISession.cs
index 75682ba9b..f4770acc8 100644
--- a/src/Util.Core/Sessions/ISession.cs
+++ b/src/Util.Core/Sessions/ISession.cs
@@ -1,9 +1,15 @@
-namespace Util.Sessions;
+using Util.Dependency;
+
+namespace Util.Sessions;
///
/// 用户会话
///
-public interface ISession {
+public interface ISession : ISingletonDependency {
+ ///
+ /// 服务提供器
+ ///
+ IServiceProvider ServiceProvider { get; }
///
/// 是否认证
///
@@ -12,4 +18,8 @@ public interface ISession {
/// 用户标识
///
string UserId { get; }
+ ///
+ /// 租户标识
+ ///
+ string TenantId { get; }
}
\ No newline at end of file
diff --git a/src/Util.Core/Sessions/NullSession.cs b/src/Util.Core/Sessions/NullSession.cs
index 82508a063..e46ad3c12 100644
--- a/src/Util.Core/Sessions/NullSession.cs
+++ b/src/Util.Core/Sessions/NullSession.cs
@@ -1,21 +1,30 @@
-namespace Util.Sessions;
+using Util.Dependency;
+
+namespace Util.Sessions;
///
/// 空用户会话
///
+[Ioc(-9)]
public class NullSession : ISession {
///
- /// 是否认证
+ /// 用户会话空实例
///
- public bool IsAuthenticated => false;
+ public static readonly ISession Instance = new NullSession();
///
- /// 用户标识
+ /// 服务提供器
///
- public string UserId => string.Empty;
+ public IServiceProvider ServiceProvider => null;
///
- /// 空用户会话实例
+ /// 是否认证
///
- public static readonly ISession Instance = new NullSession();
+ public bool IsAuthenticated => false;
+
+ ///
+ public string UserId => string.Empty;
+
+ ///
+ public string TenantId => string.Empty;
}
\ No newline at end of file
diff --git a/src/Util.Data.Abstractions/ConnectionStringCollection.cs b/src/Util.Data.Abstractions/ConnectionStringCollection.cs
new file mode 100644
index 000000000..acaf30fce
--- /dev/null
+++ b/src/Util.Data.Abstractions/ConnectionStringCollection.cs
@@ -0,0 +1,27 @@
+namespace Util.Data;
+
+///
+/// 连接字符串集合
+///
+public class ConnectionStringCollection : Dictionary {
+ ///
+ /// 默认连接字符串名称,值: Default
+ ///
+ public const string DefaultName = "Default";
+
+ ///
+ /// 默认连接字符串
+ ///
+ public string Default {
+ get => this.GetValue( DefaultName );
+ set => this[DefaultName] = value;
+ }
+
+ ///
+ /// 获取连接字符串
+ ///
+ /// 连接字符串名称
+ public string GetConnectionString( string name ) {
+ return ContainsKey( name ) ? this.GetValue( name ) : Default;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs b/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs
new file mode 100644
index 000000000..89408942b
--- /dev/null
+++ b/src/Util.Data.Abstractions/ConnectionStringNameAttribute.cs
@@ -0,0 +1,38 @@
+namespace Util.Data;
+
+///
+/// 连接字符串名称
+///
+public class ConnectionStringNameAttribute : Attribute {
+ ///
+ /// 连接字符串名称
+ ///
+ public string Name { get; }
+
+ ///
+ /// 初始化连接字符串名称
+ ///
+ /// 连接字符串名称
+ public ConnectionStringNameAttribute( string name ) {
+ Name = name;
+ }
+
+ ///
+ /// 获取连接字符串名称,如果未设置名称则返回类型名
+ ///
+ /// 类型
+ public static string GetName() {
+ return GetName( typeof( T ) );
+ }
+
+ ///
+ /// 获取连接字符串名称,如果未设置名称则返回类型名
+ ///
+ /// 类型
+ public static string GetName( Type type ) {
+ if ( type == null )
+ return null;
+ var attribute = type.GetCustomAttribute();
+ return attribute == null || attribute.Name.IsEmpty() ? type.Name : attribute.Name;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Data.Abstractions/Usings.cs b/src/Util.Data.Abstractions/Usings.cs
index 6eb207db5..18555658d 100644
--- a/src/Util.Data.Abstractions/Usings.cs
+++ b/src/Util.Data.Abstractions/Usings.cs
@@ -3,4 +3,5 @@
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
-global using System.Linq.Expressions;
\ No newline at end of file
+global using System.Linq.Expressions;
+global using System.Reflection;
\ No newline at end of file
diff --git a/src/Util.Data.Core/Filters/IFilter.cs b/src/Util.Data.Core/Filters/IFilter.cs
index 696dfd7fc..17d350193 100644
--- a/src/Util.Data.Core/Filters/IFilter.cs
+++ b/src/Util.Data.Core/Filters/IFilter.cs
@@ -27,7 +27,8 @@ public interface IFilter : ITransientDependency {
/// 获取过滤表达式
///
/// 实体类型
- Expression> GetExpression() where TEntity : class;
+ /// 参数
+ Expression> GetExpression( object state ) where TEntity : class;
}
///
diff --git a/src/Util.Data.Core/Filters/IFilterManager.cs b/src/Util.Data.Core/Filters/IFilterManager.cs
index e74d414c6..c94eae5aa 100644
--- a/src/Util.Data.Core/Filters/IFilterManager.cs
+++ b/src/Util.Data.Core/Filters/IFilterManager.cs
@@ -26,4 +26,10 @@ public interface IFilterManager : IFilterSwitch,IScopeDependency {
///
/// 过滤器类型
bool IsEnabled() where TFilterType : class;
+ ///
+ /// 获取过滤表达式
+ ///
+ /// 实体类型
+ /// 参数
+ Expression> GetExpression( object state ) where TEntity : class;
}
\ No newline at end of file
diff --git a/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj b/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj
index 36476e463..faecf0214 100644
--- a/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj
+++ b/src/Util.Data.Dapper.Core/01-Util.Data.Dapper.Core.csproj
@@ -27,7 +27,7 @@
-
+
diff --git a/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs
index 09ed309bc..a5b5acd73 100644
--- a/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore.MySql/MySqlUnitOfWorkBase.cs
@@ -12,4 +12,9 @@ public abstract class MySqlUnitOfWorkBase : UnitOfWorkBase {
protected MySqlUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options )
: base( serviceProvider, options ) {
}
+
+ ///
+ protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) {
+ optionsBuilder.UseMySql( connectionString, ServerVersion.AutoDetect( connectionString ) );
+ }
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs
index fa3f2033c..7b6902abf 100644
--- a/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore.Oracle/OracleUnitOfWorkBase.cs
@@ -18,6 +18,11 @@ protected OracleUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptio
: base( serviceProvider, options ) {
}
+ ///
+ protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) {
+ optionsBuilder.UseOracle( connectionString );
+ }
+
///
/// 配置扩展属性
///
diff --git a/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs
index d4131c0bb..d202494e5 100644
--- a/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore.PostgreSql/PgSqlUnitOfWorkBase.cs
@@ -12,4 +12,9 @@ public abstract class PgSqlUnitOfWorkBase : UnitOfWorkBase {
protected PgSqlUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options )
: base( serviceProvider, options ) {
}
+
+ ///
+ protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) {
+ optionsBuilder.UseNpgsql( connectionString );
+ }
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs
index 787091a51..e950c5b35 100644
--- a/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore.SqlServer/SqlServerUnitOfWorkBase.cs
@@ -1,6 +1,6 @@
using Util.Domain;
-namespace Util.Data.EntityFrameworkCore;
+namespace Util.Data.EntityFrameworkCore;
///
/// SqlServer工作单元基类
@@ -15,11 +15,12 @@ protected SqlServerUnitOfWorkBase( IServiceProvider serviceProvider,DbContextOpt
: base( serviceProvider, options ) {
}
- ///
- /// 配置乐观锁
- ///
- /// 模型生成器
- /// 实体类型
+ ///
+ protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) {
+ optionsBuilder.UseSqlServer( connectionString );
+ }
+
+ ///
protected override void ApplyVersion( ModelBuilder modelBuilder, IMutableEntityType entityType ) {
if( typeof( IVersion ).IsAssignableFrom( entityType.ClrType ) == false )
return;
diff --git a/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs
index b908b3fda..700558552 100644
--- a/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore.Sqlite/SqliteUnitOfWorkBase.cs
@@ -12,4 +12,9 @@ public abstract class SqliteUnitOfWorkBase : UnitOfWorkBase {
protected SqliteUnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions options )
: base( serviceProvider, options ) {
}
+
+ ///
+ protected override void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder, string connectionString ) {
+ optionsBuilder.UseSqlite( connectionString );
+ }
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj b/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj
index eb6d842c3..15b803066 100644
--- a/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj
+++ b/src/Util.Data.EntityFrameworkCore/01-Util.Data.EntityFrameworkCore.csproj
@@ -34,6 +34,7 @@
+
diff --git a/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs b/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs
index 2150d969b..1ff8a3dc4 100644
--- a/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs
+++ b/src/Util.Data.EntityFrameworkCore/FilterExtensions.cs
@@ -1,7 +1,4 @@
-using Util.Data.Filters;
-using Util.Domain;
-
-namespace Util.Data.EntityFrameworkCore;
+namespace Util.Data.EntityFrameworkCore;
///
/// 过滤器操作扩展
@@ -22,4 +19,20 @@ public static void EnableDeleteFilter( this IFilterOperation source ) {
public static IDisposable DisableDeleteFilter( this IFilterOperation source ) {
return source.DisableFilter();
}
+
+ ///
+ /// 启用租户过滤器
+ ///
+ /// 源
+ public static void EnableTenantFilter( this IFilterOperation source ) {
+ source.EnableFilter();
+ }
+
+ ///
+ /// 禁用租户过滤器
+ ///
+ /// 源
+ public static IDisposable DisableTenantFilter( this IFilterOperation source ) {
+ return source.DisableFilter();
+ }
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs b/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs
index 0fe2df12a..fa7a600b5 100644
--- a/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs
+++ b/src/Util.Data.EntityFrameworkCore/Filters/DeleteFilter.cs
@@ -1,6 +1,4 @@
-using Util.Domain;
-
-namespace Util.Data.EntityFrameworkCore.Filters;
+namespace Util.Data.EntityFrameworkCore.Filters;
///
/// 逻辑删除过滤器
@@ -10,7 +8,12 @@ public class DeleteFilter : FilterBase {
/// 获取过滤表达式
///
/// 实体类型
- public override Expression> GetExpression() where TEntity : class {
- return entity => !EF.Property( entity, "IsDeleted" );
+ public override Expression> GetExpression( object state ) where TEntity : class {
+ var unitOfWork = state as UnitOfWorkBase;
+ Expression> expression = entity => !EF.Property( entity, "IsDeleted" );
+ if ( unitOfWork == null )
+ return expression;
+ Expression> isEnabled = entity => !unitOfWork.IsDeleteFilterEnabled;
+ return isEnabled.Or( expression );
}
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs b/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs
index 0875e7343..c24ce2509 100644
--- a/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs
+++ b/src/Util.Data.EntityFrameworkCore/Filters/FilterBase.cs
@@ -1,6 +1,4 @@
-using Util.Data.Filters;
-
-namespace Util.Data.EntityFrameworkCore.Filters;
+namespace Util.Data.EntityFrameworkCore.Filters;
///
/// 数据过滤器基类
@@ -10,27 +8,27 @@ public abstract class FilterBase : IFilter where TFilt
///
/// 过滤器是否启用
///
- public bool IsEnabled { get; private set; } = true;
+ public virtual bool IsEnabled { get; private set; } = true;
///
/// 实体是否启用过滤器
///
/// 实体类型
- public bool IsEntityEnabled() {
+ public virtual bool IsEntityEnabled() {
return typeof(TFilterType).IsAssignableFrom( typeof(TEntity) );
}
///
/// 启用
///
- public void Enable() {
+ public virtual void Enable() {
IsEnabled = true;
}
///
/// 禁用
///
- public IDisposable Disable() {
+ public virtual IDisposable Disable() {
if ( IsEnabled == false )
return DisposeAction.Null;
IsEnabled = false;
@@ -41,5 +39,6 @@ public IDisposable Disable() {
/// 获取过滤表达式
///
/// 实体类型
- public abstract Expression> GetExpression() where TEntity : class;
+ /// 参数
+ public abstract Expression> GetExpression( object state ) where TEntity : class;
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs b/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs
index 2a4401c27..05fcbf9de 100644
--- a/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs
+++ b/src/Util.Data.EntityFrameworkCore/Filters/FilterManager.cs
@@ -1,6 +1,4 @@
-using Util.Data.Filters;
-
-namespace Util.Data.EntityFrameworkCore.Filters;
+namespace Util.Data.EntityFrameworkCore.Filters;
///
/// 数据过滤器管理器
@@ -57,35 +55,30 @@ public static void RemoveFilterType() {
}
///
- /// 启用过滤器
+ /// 清空过滤器类型
///
- /// 过滤器类型
+ public static void ClearFilterTypes() {
+ _filterTypes.Clear();
+ }
+
+ ///
public void EnableFilter() where TFilterType : class {
var filter = GetFilter();
filter?.Enable();
}
- ///
- /// 禁用过滤器
- ///
- /// 过滤器类型
+ ///
public IDisposable DisableFilter() where TFilterType : class {
var filter = GetFilter();
return filter?.Disable();
}
- ///
- /// 获取过滤器
- ///
- /// 过滤器类型
+ ///
public IFilter GetFilter() where TFilterType : class {
return GetFilter( typeof(TFilterType) );
}
- ///
- /// 获取过滤器
- ///
- /// 过滤器类型
+ ///
public IFilter GetFilter( Type filterType ) {
if( _filters.ContainsKey( filterType ) == false ) {
var serviceType = typeof( IFilter<> ).MakeGenericType( filterType );
@@ -95,9 +88,7 @@ public IFilter GetFilter( Type filterType ) {
return _filters[filterType];
}
- ///
- /// 实体是否启用过滤器
- ///
+ ///
public bool IsEntityEnabled() {
foreach( var type in _filterTypes ) {
var filter = GetFilter( type );
@@ -107,14 +98,22 @@ public bool IsEntityEnabled() {
return false;
}
- ///
- /// 过滤器是否启用
- ///
- /// 过滤器类型
+ ///
public bool IsEnabled() where TFilterType : class {
var filter = GetFilter();
if ( filter == null )
return false;
return filter.IsEnabled;
}
+
+ ///
+ public Expression> GetExpression( object state ) where TEntity : class {
+ Expression> expression = null;
+ foreach ( var type in _filterTypes ) {
+ var filter = GetFilter( type );
+ if ( filter.IsEntityEnabled() )
+ expression = expression.And( filter.GetExpression( state ) );
+ }
+ return expression;
+ }
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs b/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs
new file mode 100644
index 000000000..7ce5ed569
--- /dev/null
+++ b/src/Util.Data.EntityFrameworkCore/Filters/TenantFilter.cs
@@ -0,0 +1,36 @@
+namespace Util.Data.EntityFrameworkCore.Filters;
+
+///
+/// 租户过滤器
+///
+public class TenantFilter : FilterBase {
+ ///
+ /// 租户管理器
+ ///
+ private readonly ITenantManager _manager;
+
+ ///
+ /// 初始化租户过滤器
+ ///
+ /// 租户管理器
+ public TenantFilter( ITenantManager manager ) {
+ _manager = manager ?? throw new ArgumentNullException( nameof( manager ) );
+ }
+
+ ///
+ /// 获取过滤表达式
+ ///
+ /// 实体类型
+ public override Expression> GetExpression( object state ) where TEntity : class {
+ if ( _manager.Enabled() == false )
+ return null;
+ if ( _manager.IsDisableTenantFilter() )
+ return null;
+ var unitOfWork = state as UnitOfWorkBase;
+ if ( unitOfWork == null )
+ return null;
+ Expression> isEnabled = entity => !unitOfWork.IsTenantFilterEnabled;
+ Expression> expression = entity => EF.Property( entity, "TenantId" ) == unitOfWork.CurrentTenantId;
+ return isEnabled.Or( expression );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs b/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs
index 9a63093eb..0bc989071 100644
--- a/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs
+++ b/src/Util.Data.EntityFrameworkCore/Infrastructure/EntityFrameworkServiceRegistrar.cs
@@ -1,6 +1,4 @@
using Util.Data.EntityFrameworkCore.Filters;
-using Util.Domain;
-using Util.Infrastructure;
namespace Util.Data.EntityFrameworkCore.Infrastructure;
@@ -29,6 +27,7 @@ public class EntityFrameworkServiceRegistrar : IServiceRegistrar {
/// 服务上下文
public Action Register( ServiceContext serviceContext ) {
FilterManager.AddFilterType();
+ FilterManager.AddFilterType();
return null;
}
}
\ No newline at end of file
diff --git a/src/Util.Data.EntityFrameworkCore/StoreBase.cs b/src/Util.Data.EntityFrameworkCore/StoreBase.cs
index c76916a92..ff9cdfec9 100644
--- a/src/Util.Data.EntityFrameworkCore/StoreBase.cs
+++ b/src/Util.Data.EntityFrameworkCore/StoreBase.cs
@@ -1,7 +1,5 @@
-using Util.Data.Filters;
-using Util.Data.Queries;
+using Util.Data.Queries;
using Util.Data.Stores;
-using Util.Domain;
using Util.Exceptions;
namespace Util.Data.EntityFrameworkCore;
diff --git a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs
index d86f97033..8a8d29d40 100644
--- a/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs
+++ b/src/Util.Data.EntityFrameworkCore/UnitOfWorkBase.cs
@@ -1,8 +1,6 @@
using Util.Data.EntityFrameworkCore.ValueComparers;
using Util.Data.EntityFrameworkCore.ValueConverters;
-using Util.Data.Filters;
using Util.Dates;
-using Util.Domain;
using Util.Domain.Auditing;
using Util.Domain.Events;
using Util.Domain.Extending;
@@ -12,7 +10,7 @@
using Util.Properties;
using Util.Sessions;
-namespace Util.Data.EntityFrameworkCore;
+namespace Util.Data.EntityFrameworkCore;
///
/// 工作单元基类
@@ -31,10 +29,12 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt
ServiceProvider = serviceProvider ?? throw new ArgumentNullException( nameof( serviceProvider ) );
Environment = serviceProvider.GetService();
FilterManager = ServiceProvider.GetService();
+ TenantManager = ServiceProvider.GetService() ?? NullTenantManager.Instance;
Session = serviceProvider.GetService() ?? NullSession.Instance;
EventBus = serviceProvider.GetService() ?? NullEventBus.Instance;
ActionManager = serviceProvider.GetService() ?? NullUnitOfWorkActionManager.Instance;
- Events = new List();
+ SaveBeforeEvents = new List();
+ SaveAfterEvents = new List();
}
#endregion
@@ -58,9 +58,9 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt
///
protected IFilterManager FilterManager { get; }
///
- /// 逻辑删除过滤器是否启用
+ /// 租户管理器
///
- protected virtual bool IsDeleteFilterEnabled => FilterManager?.IsEnabled() ?? false;
+ protected ITenantManager TenantManager { get; }
///
/// 事件总线
///
@@ -70,9 +70,25 @@ protected UnitOfWorkBase( IServiceProvider serviceProvider, DbContextOptions opt
///
protected IUnitOfWorkActionManager ActionManager { get; }
///
- /// 事件集合
+ /// 保存前发送的事件集合
+ ///
+ protected List SaveBeforeEvents { get; }
+ ///
+ /// 保存后发送的事件集合
+ ///
+ protected List SaveAfterEvents { get; }
+ ///
+ /// 逻辑删除过滤器是否启用
///
- protected List Events { get; }
+ public virtual bool IsDeleteFilterEnabled => FilterManager?.IsEnabled() ?? false;
+ ///
+ /// 租户过滤器是否启用
+ ///
+ public virtual bool IsTenantFilterEnabled => FilterManager?.IsEnabled() ?? false;
+ ///
+ /// 当前租户标识
+ ///
+ public virtual string CurrentTenantId => TenantManager.GetTenantId();
///
/// 是否清除字符串两端的空白,默认为true
///
@@ -125,6 +141,7 @@ public IDisposable DisableFilter() where TFilterType : class {
/// 配置生成器
protected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder ) {
ConfigLog( optionsBuilder );
+ ConfigTenant( optionsBuilder );
}
#endregion
@@ -145,6 +162,37 @@ protected virtual void ConfigLog( DbContextOptionsBuilder optionsBuilder ) {
#endregion
+ #region ConfigTenant(配置租户)
+
+ ///
+ /// 配置租户
+ ///
+ /// 配置生成器
+ protected virtual void ConfigTenant( DbContextOptionsBuilder optionsBuilder ) {
+ if ( TenantManager.Enabled() == false )
+ return;
+ if ( TenantManager.AllowMultipleDatabase() == false )
+ return;
+ var tenant = TenantManager.GetTenant();
+ if ( tenant == null )
+ return;
+ var name = ConnectionStringNameAttribute.GetName( GetType() );
+ var connectionString = tenant.ConnectionStrings.GetConnectionString( name );
+ if ( connectionString.IsEmpty() )
+ return;
+ ConfigTenantConnectionString( optionsBuilder, connectionString );
+ }
+
+ ///
+ /// 配置租户连接字符串
+ ///
+ /// 配置生成器
+ /// 连接字符串
+ protected virtual void ConfigTenantConnectionString( DbContextOptionsBuilder optionsBuilder,string connectionString ) {
+ }
+
+ #endregion
+
#region OnModelCreating(配置模型)
///
@@ -158,6 +206,7 @@ protected override void OnModelCreating( ModelBuilder modelBuilder ) {
ApplyExtraProperties( modelBuilder, entityType );
ApplyVersion( modelBuilder, entityType );
ApplyIsDeleted( modelBuilder, entityType );
+ ApplyTenantId( modelBuilder, entityType );
ApplyUtc( modelBuilder, entityType );
ApplyTrimString( modelBuilder, entityType );
}
@@ -198,7 +247,10 @@ protected virtual void ApplyFiltersImp( ModelBuilder modelBuilder ) whe
return;
if ( FilterManager.IsEntityEnabled() == false )
return;
- modelBuilder.Entity().HasQueryFilter( GetFilterExpression() );
+ var expression = GetFilterExpression();
+ if ( expression == null )
+ return;
+ modelBuilder.Entity().HasQueryFilter( expression );
}
///
@@ -206,20 +258,7 @@ protected virtual void ApplyFiltersImp( ModelBuilder modelBuilder ) whe
///
/// 实体类型
protected virtual Expression> GetFilterExpression() where TEntity : class {
- return GetDeleteFilterExpression();
- }
-
- ///
- /// 获取逻辑删除过滤器表达式
- ///
- /// 实体类型
- protected virtual Expression> GetDeleteFilterExpression() where TEntity : class {
- var filter = FilterManager.GetFilter();
- if ( filter.IsEntityEnabled() == false )
- return null;
- var expression = filter.GetExpression();
- Expression> result = entity => !IsDeleteFilterEnabled;
- return result.Or( expression );
+ return FilterManager.GetExpression( this );
}
#endregion
@@ -281,6 +320,24 @@ protected virtual void ApplyIsDeleted( ModelBuilder modelBuilder, IMutableEntity
#endregion
+ #region ApplyTenantId(配置租户标识)
+
+ ///
+ /// 配置租户标识
+ ///
+ /// 模型生成器
+ /// 实体类型
+ protected virtual void ApplyTenantId( ModelBuilder modelBuilder, IMutableEntityType entityType ) {
+ if ( typeof( ITenant ).IsAssignableFrom( entityType.ClrType ) == false )
+ return;
+ modelBuilder.Entity( entityType.ClrType )
+ .Property( "TenantId" )
+ .HasColumnName( "TenantId" )
+ .HasComment( R.TenantId );
+ }
+
+ #endregion
+
#region ApplyUtc(配置Utc日期)
///
@@ -349,7 +406,7 @@ public async Task CommitAsync() {
/// 保存
///
public override async Task SaveChangesAsync( CancellationToken cancellationToken = default ) {
- SaveChangesBefore();
+ await SaveChangesBefore();
var result = await base.SaveChangesAsync( cancellationToken );
await SaveChangesAfter();
return result;
@@ -362,8 +419,10 @@ public async Task CommitAsync() {
///
/// 保存前操作
///
- protected virtual void SaveChangesBefore() {
+ protected virtual async Task SaveChangesBefore() {
foreach ( var entry in ChangeTracker.Entries() ) {
+ UpdateTenantId( entry );
+ AddDomainEvents( entry );
switch ( entry.State ) {
case EntityState.Added:
AddBefore( entry );
@@ -376,6 +435,47 @@ protected virtual void SaveChangesBefore() {
break;
}
}
+ await PublishSaveBeforeEventsAsync();
+ }
+
+ #endregion
+
+ #region UpdateTenantId(更新租户标识)
+
+ ///
+ /// 更新租户标识
+ ///
+ protected virtual void UpdateTenantId( EntityEntry entry ) {
+ if ( TenantManager.Enabled() == false )
+ return;
+ if ( entry.Entity is not ITenant tenant )
+ return;
+ var tenantId = TenantManager.GetTenantId();
+ if ( tenantId.IsEmpty() )
+ return;
+ tenant.TenantId = tenantId;
+ }
+
+ #endregion
+
+ #region AddDomainEvents(添加领域事件)
+
+ ///
+ /// 添加领域事件
+ ///
+ protected virtual void AddDomainEvents( EntityEntry entry ) {
+ if ( entry.Entity is not IDomainEventManager eventManager )
+ return;
+ if ( eventManager.DomainEvents == null )
+ return;
+ foreach ( var domainEvent in eventManager.DomainEvents ) {
+ if ( domainEvent is IIntegrationEvent ) {
+ SaveAfterEvents.Add( domainEvent );
+ continue;
+ }
+ SaveBeforeEvents.Add( domainEvent );
+ }
+ eventManager.ClearDomainEvents();
}
#endregion
@@ -476,7 +576,7 @@ protected virtual byte[] GetVersion() {
protected virtual void AddEntityChangedEvent( object entity, EntityChangeType changeType ) {
var eventType = typeof( EntityChangedEvent<> ).MakeGenericType( entity.GetType() );
var @event = Reflection.CreateInstance( eventType, entity, changeType );
- Events.Add( @event );
+ SaveAfterEvents.Add( @event );
}
#endregion
@@ -488,7 +588,7 @@ protected virtual void AddEntityChangedEvent( object entity, EntityChangeType ch
///
protected virtual void AddEntityCreatedEvent( object entity ) {
var @event = CreateEntityEvent( typeof( EntityCreatedEvent<> ), entity );
- Events.Add( @event );
+ SaveAfterEvents.Add( @event );
AddEntityChangedEvent( entity, EntityChangeType.Created );
}
@@ -513,7 +613,7 @@ protected virtual void AddEntityUpdatedEvent( object entity ) {
return;
}
var @event = CreateEntityEvent( typeof( EntityUpdatedEvent<> ), entity );
- Events.Add( @event );
+ SaveAfterEvents.Add( @event );
AddEntityChangedEvent( entity, EntityChangeType.Updated );
}
@@ -526,44 +626,59 @@ protected virtual void AddEntityUpdatedEvent( object entity ) {
///
protected virtual void AddEntityDeletedEvent( object entity ) {
var @event = CreateEntityEvent( typeof( EntityDeletedEvent<> ), entity );
- Events.Add( @event );
+ SaveAfterEvents.Add( @event );
AddEntityChangedEvent( entity, EntityChangeType.Deleted );
}
#endregion
+ #region PublishSaveBeforeEventsAsync(发布保存前事件)
+
+ ///
+ /// 发布保存前事件
+ ///
+ protected virtual async Task PublishSaveBeforeEventsAsync() {
+ if ( SaveBeforeEvents.Count == 0 )
+ return;
+ var events = new List( SaveBeforeEvents );
+ SaveBeforeEvents.Clear();
+ await EventBus.PublishAsync( events );
+ }
+
+ #endregion
+
#region SaveChangesAfter(保存后操作)
///
/// 保存后操作
///
protected virtual async Task SaveChangesAfter() {
+ await PublishSaveAfterEventsAsync();
await ExecuteActionsAsync();
- await PublishEventsAsync();
}
#endregion
- #region ExecuteActionsAsync(执行工作单元操作集合)
+ #region PublishSaveAfterEventsAsync(发布保存后事件)
///
- /// 执行工作单元操作集合
+ /// 发布保存后事件
///
- protected virtual async Task ExecuteActionsAsync() {
- await ActionManager.ExecuteAsync();
+ protected virtual async Task PublishSaveAfterEventsAsync() {
+ var events = new List( SaveAfterEvents );
+ SaveAfterEvents.Clear();
+ await EventBus.PublishAsync( events );
}
#endregion
- #region PublishEventsAsync(发布事件)
+ #region ExecuteActionsAsync(执行工作单元操作集合)
///
- /// 发布事件
+ /// 执行工作单元操作集合
///
- protected virtual async Task PublishEventsAsync() {
- var events = new List( Events );
- Events.Clear();
- await EventBus.PublishAsync( events );
+ protected virtual async Task ExecuteActionsAsync() {
+ await ActionManager.ExecuteAsync();
}
#endregion
diff --git a/src/Util.Data.EntityFrameworkCore/Usings.cs b/src/Util.Data.EntityFrameworkCore/Usings.cs
index bdf0a4378..8558fc694 100644
--- a/src/Util.Data.EntityFrameworkCore/Usings.cs
+++ b/src/Util.Data.EntityFrameworkCore/Usings.cs
@@ -19,3 +19,7 @@
global using Microsoft.EntityFrameworkCore.ChangeTracking;
global using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
global using Microsoft.EntityFrameworkCore.Metadata;
+global using Util.Tenants;
+global using Util.Domain;
+global using Util.Infrastructure;
+global using Util.Data.Filters;
\ No newline at end of file
diff --git a/src/Util.Domain/Entities/AggregateRoot.cs b/src/Util.Domain/Entities/AggregateRoot.cs
index 6b234059f..63758ff36 100644
--- a/src/Util.Domain/Entities/AggregateRoot.cs
+++ b/src/Util.Domain/Entities/AggregateRoot.cs
@@ -1,4 +1,6 @@
-namespace Util.Domain.Entities;
+using Util.Domain.Events;
+
+namespace Util.Domain.Entities;
///
/// 聚合根
@@ -18,7 +20,12 @@ protected AggregateRoot( Guid id ) : base( id ) {
///
/// 实体类型
/// 标识类型
-public abstract class AggregateRoot : EntityBase, IAggregateRoot where TEntity : IAggregateRoot {
+public abstract class AggregateRoot : EntityBase, IAggregateRoot, IDomainEventManager where TEntity : IAggregateRoot {
+ ///
+ /// 领域事件列表
+ ///
+ private List _domainEvents;
+
///
/// 初始化聚合根
///
@@ -30,4 +37,28 @@ protected AggregateRoot( TKey id ) : base( id ) {
/// 版本号
///
public byte[] Version { get; set; }
+
+ ///
+ [NotMapped]
+ public IReadOnlyCollection DomainEvents => _domainEvents?.AsReadOnly();
+
+ ///
+ public void AddDomainEvent( IEvent @event ) {
+ if ( @event == null )
+ return;
+ _domainEvents ??= new List();
+ _domainEvents.Add( @event );
+ }
+
+ ///
+ public void RemoveDomainEvent( IEvent @event ) {
+ if ( @event == null )
+ return;
+ _domainEvents?.Remove( @event );
+ }
+
+ ///
+ public void ClearDomainEvents() {
+ _domainEvents?.Clear();
+ }
}
\ No newline at end of file
diff --git a/src/Util.Domain/Events/IDomainEventManager.cs b/src/Util.Domain/Events/IDomainEventManager.cs
new file mode 100644
index 000000000..ad34f746e
--- /dev/null
+++ b/src/Util.Domain/Events/IDomainEventManager.cs
@@ -0,0 +1,25 @@
+namespace Util.Domain.Events;
+
+///
+/// 领域事件服务
+///
+public interface IDomainEventManager {
+ ///
+ /// 领域事件集合
+ ///
+ IReadOnlyCollection DomainEvents { get; }
+ ///
+ /// 添加领域事件
+ ///
+ /// 领域事件
+ void AddDomainEvent( IEvent @event );
+ ///
+ /// 移除领域事件
+ ///
+ /// 领域事件
+ void RemoveDomainEvent( IEvent @event );
+ ///
+ /// 清空领域事件
+ ///
+ void ClearDomainEvents();
+}
\ No newline at end of file
diff --git a/src/Util.Domain/Usings.cs b/src/Util.Domain/Usings.cs
index 39f0dc68b..cca7e5a3f 100644
--- a/src/Util.Domain/Usings.cs
+++ b/src/Util.Domain/Usings.cs
@@ -6,4 +6,6 @@
global using System.Text;
global using System.Reflection;
global using System.Linq.Expressions;
-global using System.ComponentModel.DataAnnotations;
\ No newline at end of file
+global using System.ComponentModel.DataAnnotations;
+global using System.ComponentModel.DataAnnotations.Schema;
+global using Util.Events;
\ No newline at end of file
diff --git a/src/Util.Events.Abstractions/IIntegrationEventBus.cs b/src/Util.Events.Abstractions/IIntegrationEventBus.cs
index 5b73d1825..ef72eb6af 100644
--- a/src/Util.Events.Abstractions/IIntegrationEventBus.cs
+++ b/src/Util.Events.Abstractions/IIntegrationEventBus.cs
@@ -4,11 +4,6 @@
/// 基于消息的集成事件总线
///
public interface IIntegrationEventBus : ITransientDependency {
- ///
- /// 设置是否立即发送事件
- ///
- /// 是否立即发送事件,如果希望发送操作延迟到工作单元提交成功后,则设置为false
- IIntegrationEventBus SendNow( bool isSend );
///
/// 设置发布订阅配置名称
///
diff --git a/src/Util.Events.Abstractions/IIntegrationEventExtend.cs b/src/Util.Events.Abstractions/IIntegrationEventExtend.cs
deleted file mode 100644
index 2ddf97110..000000000
--- a/src/Util.Events.Abstractions/IIntegrationEventExtend.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Util.Events;
-
-///
-/// 集成事件扩展属性
-///
-public interface IIntegrationEventExtend {
- ///
- /// 是否立即发送,默认值: true
- ///
- bool? SendNow { get; }
- ///
- /// 发布订阅名称,默认值: pubsub
- ///
- string PubsubName { get; }
- ///
- /// 事件主题,默认为事件类型名称
- ///
- string Topic { get; }
-}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj b/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj
new file mode 100644
index 000000000..fe2a2d455
--- /dev/null
+++ b/src/Util.Events.MediatR/03-Util.Events.MediatR.csproj
@@ -0,0 +1,37 @@
+
+
+
+ $(NetTargetFramework)
+ icon.jpg
+ Util.Events.MediatR
+ Util.Events
+ Util.Events.MediatR是Util应用框架基于MediatR的事件总线操作类库
+
+
+
+
+ .\obj\Debug\$(NetTargetFramework)\Util.Events.MediatR.xml
+
+
+
+
+ .\obj\Release\$(NetTargetFramework)\Util.Events.MediatR.xml
+
+
+
+
+ True
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Util.Events.MediatR/AppBuilderExtensions.cs b/src/Util.Events.MediatR/AppBuilderExtensions.cs
new file mode 100644
index 000000000..738097ba6
--- /dev/null
+++ b/src/Util.Events.MediatR/AppBuilderExtensions.cs
@@ -0,0 +1,35 @@
+using Util.Configs;
+
+namespace Util.Events;
+
+///
+/// MediatR事件总线操作扩展
+///
+public static class AppBuilderExtensions {
+ ///
+ /// 配置MediatR事件总线操作
+ ///
+ /// 应用生成器
+ public static IAppBuilder AddMediatR( this IAppBuilder builder ) {
+ builder.CheckNull( nameof( builder ) );
+ MediatROptions.IsScan = true;
+ builder.Host.ConfigureServices( ( context, services ) => {
+ services.TryAddTransient();
+ } );
+ return builder;
+ }
+
+ ///
+ /// 配置MediatR事件总线操作
+ ///
+ /// 应用生成器
+ /// 事件总线配置操作
+ public static IAppBuilder AddMediatR( this IAppBuilder builder, Action setupAction ) {
+ builder.CheckNull( nameof( builder ) );
+ builder.Host.ConfigureServices( ( context, services ) => {
+ services.TryAddTransient();
+ services.AddMediatR( setupAction );
+ } );
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/EventBase.cs b/src/Util.Events.MediatR/EventBase.cs
new file mode 100644
index 000000000..ddf40d589
--- /dev/null
+++ b/src/Util.Events.MediatR/EventBase.cs
@@ -0,0 +1,7 @@
+namespace Util.Events;
+
+///
+/// 事件
+///
+public abstract class EventBase : IEvent, INotification {
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/EventHandlerBase.cs b/src/Util.Events.MediatR/EventHandlerBase.cs
new file mode 100644
index 000000000..e79c44525
--- /dev/null
+++ b/src/Util.Events.MediatR/EventHandlerBase.cs
@@ -0,0 +1,30 @@
+namespace Util.Events;
+
+///
+/// 基于MediatR的本地事件处理器基类
+///
+/// 事件类型
+public abstract class EventHandlerBase : INotificationHandler where TEvent : IEvent, INotification {
+ ///
+ /// 是否启用
+ ///
+ public virtual bool Enabled => true;
+
+ ///
+ /// 处理事件
+ ///
+ /// 事件
+ /// 取消令牌
+ public async Task Handle( TEvent @event, CancellationToken cancellationToken ) {
+ if ( Enabled == false )
+ return;
+ await HandleAsync( @event, cancellationToken );
+ }
+
+ ///
+ /// 处理事件
+ ///
+ /// 事件
+ /// 取消令牌
+ public abstract Task HandleAsync( TEvent @event, CancellationToken cancellationToken );
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs b/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs
new file mode 100644
index 000000000..0ff355c17
--- /dev/null
+++ b/src/Util.Events.MediatR/Infrastructure/MediatREventBusServiceRegistrar.cs
@@ -0,0 +1,41 @@
+namespace Util.Events.Infrastructure;
+
+///
+/// MediatR本地事件总线服务注册器
+///
+public class MediatREventBusServiceRegistrar : IServiceRegistrar {
+ ///
+ /// 获取服务名
+ ///
+ public static string ServiceName => "Util.Events.MediatR.Infrastructure.LocalEventBusServiceRegistrar";
+
+ ///
+ /// 排序号
+ ///
+ public int OrderId => 511;
+
+ ///
+ /// 是否启用
+ ///
+ public bool Enabled => ServiceRegistrarConfig.IsEnabled( ServiceName );
+
+ ///
+ /// 注册服务
+ ///
+ /// 服务上下文
+ public Action Register( ServiceContext serviceContext ) {
+ serviceContext.HostBuilder.ConfigureServices( ( context, services ) => {
+ RegisterMediatR( services, serviceContext.AssemblyFinder );
+ } );
+ return null;
+ }
+
+ ///
+ /// 注册MediatR
+ ///
+ private void RegisterMediatR( IServiceCollection services, IAssemblyFinder finder ) {
+ if ( MediatROptions.IsScan == false )
+ return;
+ services.AddMediatR( t => t.RegisterServicesFromAssemblies( finder.Find().ToArray() ) );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs b/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs
new file mode 100644
index 000000000..2b1c713a1
--- /dev/null
+++ b/src/Util.Events.MediatR/Infrastructure/ServiceRegistrarConfigExtensions.cs
@@ -0,0 +1,24 @@
+namespace Util.Events.Infrastructure;
+
+///
+/// MediatR本地事件总线服务注册器配置扩展
+///
+public static class ServiceRegistrarConfigExtensions {
+ ///
+ /// 启用MediatR本地事件总线服务注册器
+ ///
+ /// 服务注册器配置
+ public static ServiceRegistrarConfig EnableMediatREventBusServiceRegistrar( this ServiceRegistrarConfig config ) {
+ ServiceRegistrarConfig.Enable( MediatREventBusServiceRegistrar.ServiceName );
+ return config;
+ }
+
+ ///
+ ///禁用MediatR本地事件总线服务注册器
+ ///
+ /// 服务注册器配置
+ public static ServiceRegistrarConfig DisableMediatREventBusServiceRegistrar( this ServiceRegistrarConfig config ) {
+ ServiceRegistrarConfig.Disable( MediatREventBusServiceRegistrar.ServiceName );
+ return config;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/MediatREventBus.cs b/src/Util.Events.MediatR/MediatREventBus.cs
new file mode 100644
index 000000000..02b7ec6cb
--- /dev/null
+++ b/src/Util.Events.MediatR/MediatREventBus.cs
@@ -0,0 +1,27 @@
+namespace Util.Events;
+
+///
+/// 基于MediatR的本地事件总线
+///
+public class MediatREventBus : ILocalEventBus {
+ ///
+ /// 事件发布器
+ ///
+ private readonly IMediator _publisher;
+
+ ///
+ /// 初始化基于MediatR的本地事件总线
+ ///
+ /// 事件发布器
+ public MediatREventBus( IMediator publisher ) {
+ _publisher = publisher ?? throw new ArgumentNullException( nameof( publisher ) );
+ }
+
+ ///
+ public async Task PublishAsync( TEvent @event,CancellationToken cancellationToken = default ) where TEvent : IEvent {
+ cancellationToken.ThrowIfCancellationRequested();
+ if ( @event == null )
+ return;
+ await _publisher.Publish( @event, cancellationToken );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/MediatROptions.cs b/src/Util.Events.MediatR/MediatROptions.cs
new file mode 100644
index 000000000..b6042ceab
--- /dev/null
+++ b/src/Util.Events.MediatR/MediatROptions.cs
@@ -0,0 +1,11 @@
+namespace Util.Events;
+
+///
+/// MediatR事件总线配置
+///
+public static class MediatROptions {
+ ///
+ /// 是否扫描加载程序集
+ ///
+ public static bool IsScan { get; set; }
+}
\ No newline at end of file
diff --git a/src/Util.Events.MediatR/Usings.cs b/src/Util.Events.MediatR/Usings.cs
new file mode 100644
index 000000000..9e38359f6
--- /dev/null
+++ b/src/Util.Events.MediatR/Usings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.Threading.Tasks;
+global using System.Threading;
+global using Microsoft.Extensions.DependencyInjection.Extensions;
+global using Microsoft.Extensions.DependencyInjection;
+global using MediatR;
+global using Util.Infrastructure;
+global using Util.Reflections;
\ No newline at end of file
diff --git a/src/Util.Events.Abstractions/ILocalEventHandler.cs b/src/Util.Events/ILocalEventHandler.cs
similarity index 100%
rename from src/Util.Events.Abstractions/ILocalEventHandler.cs
rename to src/Util.Events/ILocalEventHandler.cs
diff --git a/src/Util.Events/LocalEventBus.cs b/src/Util.Events/LocalEventBus.cs
index 6e0af22ac..71ce98860 100644
--- a/src/Util.Events/LocalEventBus.cs
+++ b/src/Util.Events/LocalEventBus.cs
@@ -1,6 +1,4 @@
-using Util.Data;
-
-namespace Util.Events;
+namespace Util.Events;
///
/// 基于内存的本地事件总线
@@ -10,10 +8,6 @@ public class LocalEventBus : ILocalEventBus {
/// 服务提供器
///
private readonly IServiceProvider _serviceProvider;
- ///
- /// 工作单元操作管理器
- ///
- private readonly IUnitOfWorkActionManager _actionManager;
///
/// 初始化本地事件总线
@@ -21,7 +15,6 @@ public class LocalEventBus : ILocalEventBus {
/// 服务提供器
public LocalEventBus( IServiceProvider serviceProvider ) {
_serviceProvider = serviceProvider ?? throw new ArgumentNullException( nameof( serviceProvider ) );
- _actionManager = _serviceProvider.GetService();
}
///
@@ -29,21 +22,7 @@ public LocalEventBus( IServiceProvider serviceProvider ) {
cancellationToken.ThrowIfCancellationRequested();
if ( @event == null )
return;
- if ( @event is not IIntegrationEvent integrationEvent ) {
- await PublishLocalEventAsync( @event, cancellationToken );
- return;
- }
- if( _actionManager == null ) {
- await PublishLocalEventAsync( @event, cancellationToken );
- return;
- }
- if ( integrationEvent is IIntegrationEventExtend { SendNow: true } ) {
- await PublishLocalEventAsync( @event, cancellationToken );
- return;
- }
- _actionManager.Register( async () => {
- await PublishLocalEventAsync( @event, cancellationToken );
- } );
+ await PublishLocalEventAsync( @event, cancellationToken );
}
///
diff --git a/src/Util.Generators/Configuration/ProjectOptions.cs b/src/Util.Generators/Configuration/ProjectOptions.cs
index d741a62bd..cc1d93b59 100644
--- a/src/Util.Generators/Configuration/ProjectOptions.cs
+++ b/src/Util.Generators/Configuration/ProjectOptions.cs
@@ -50,6 +50,10 @@ public ProjectOptions() {
///
public string ApiPort { get; set; }
///
+ /// 是否启用架构
+ ///
+ public bool EnableSchema { get; set; }
+ ///
/// 项目类型
///
public ProjectType? ProjectType { get; set; }
@@ -60,5 +64,5 @@ public ProjectOptions() {
///
/// 扩展
///
- public object Extend { get; set; }
+ public string Extend { get; set; }
}
\ No newline at end of file
diff --git a/src/Util.Generators/Contexts/GeneratorContextBuilder.cs b/src/Util.Generators/Contexts/GeneratorContextBuilder.cs
index 36a21f284..945bccc1e 100644
--- a/src/Util.Generators/Contexts/GeneratorContextBuilder.cs
+++ b/src/Util.Generators/Contexts/GeneratorContextBuilder.cs
@@ -111,6 +111,7 @@ protected async Task CreateProjectContext( GeneratorContext gene
I18n = projectOptions.I18n,
ProjectType = projectOptions.ProjectType,
ApiPort = projectOptions.ApiPort,
+ EnableSchema = projectOptions.EnableSchema,
Extend = projectOptions.Extend
};
if ( projectOptions.Enabled == false )
diff --git a/src/Util.Generators/Contexts/ProjectContext.cs b/src/Util.Generators/Contexts/ProjectContext.cs
index d1bf1194c..2938fc014 100644
--- a/src/Util.Generators/Contexts/ProjectContext.cs
+++ b/src/Util.Generators/Contexts/ProjectContext.cs
@@ -76,6 +76,11 @@ public ProjectContext( GeneratorContext generatorContext ) {
///
public ProjectType? ProjectType { get; set; }
+ ///
+ /// 是否启用架构
+ ///
+ public bool EnableSchema { get; set; }
+
///
/// 客户端配置
///
@@ -84,7 +89,7 @@ public ProjectContext( GeneratorContext generatorContext ) {
///
/// 扩展
///
- public object Extend { get; set; }
+ public string Extend { get; set; }
///
/// 生成器上下文
@@ -105,7 +110,7 @@ public ProjectContext( GeneratorContext generatorContext ) {
/// 获取扩展
///
public T GetExtend() {
- return Util.Helpers.Convert.To( Extend );
+ return Util.Helpers.Json.ToObject( Extend );
}
///
@@ -136,18 +141,11 @@ public ProjectContext Clone( GeneratorContext generatorContext ) {
I18n = I18n,
ProjectType = ProjectType,
ApiPort = ApiPort,
- Extend = CloneExtend()
+ EnableSchema = EnableSchema,
+ Extend = Extend
};
result.Schemas.AddRange( Schemas );
Entities.ForEach( entity => result.Entities.Add( entity.Clone( result ) ) );
return result;
}
-
- ///
- /// 复制扩展
- ///
- private object CloneExtend() {
- var json = Util.Helpers.Json.ToJson( Extend );
- return Util.Helpers.Json.ToObject
-
+
diff --git a/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj b/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj
index 141913618..86d597162 100644
--- a/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj
+++ b/src/Util.Logging.Serilog/02-Util.Logging.Serilog.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/Util.Microservices.Dapr/AppBuilderExtensions.cs b/src/Util.Microservices.Dapr/AppBuilderExtensions.cs
index 72053f9b3..0f713dc19 100644
--- a/src/Util.Microservices.Dapr/AppBuilderExtensions.cs
+++ b/src/Util.Microservices.Dapr/AppBuilderExtensions.cs
@@ -32,9 +32,9 @@ public static IAppBuilder AddDapr( this IAppBuilder builder, Action
/// Dapr配置操作
/// Dapr客户端生成操作
public static IAppBuilder AddDapr( this IAppBuilder builder, Action setupAction, Action buildAction ) {
+ builder.CheckNull( nameof( builder ) );
var options = new DaprOptions();
setupAction?.Invoke( options );
- builder.CheckNull( nameof( builder ) );
builder.Host.ConfigureServices( ( context, services ) => {
services.AddDaprClient( clientBuilder => {
clientBuilder.UseJsonSerializationOptions( GetJsonSerializerOptions() );
diff --git a/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs b/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs
index d5523aa02..db2575802 100644
--- a/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs
+++ b/src/Util.Microservices.Dapr/DaprMicroserviceClient.cs
@@ -22,7 +22,7 @@ public DaprMicroserviceClient( DaprClient client, HttpClient httpClient, IOption
ServiceInvocation = new DaprServiceInvocation( client, options, loggerFactory ).Service( appId );
IntegrationEventBus = new DaprIntegrationEventBus( client, options, loggerFactory, serviceProvider );
var keyGenerator = serviceProvider.GetService();
- StateManage = new DaprStateManage( client, options, loggerFactory, keyGenerator );
+ StateManager = new DaprStateManager( client, options, loggerFactory, keyGenerator );
}
///
@@ -43,5 +43,5 @@ public DaprMicroserviceClient( DaprClient client, HttpClient httpClient, IOption
///
/// 状态管理
///
- public IStateManage StateManage { get; }
+ public IStateManager StateManager { get; }
}
\ No newline at end of file
diff --git a/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs b/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs
index d7146785d..f7e346152 100644
--- a/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs
+++ b/src/Util.Microservices.Dapr/Events/DaprIntegrationEventBus.cs
@@ -22,10 +22,6 @@ public class DaprIntegrationEventBus : IIntegrationEventBus {
///
protected readonly ILogger Logger;
///
- /// 工作单元操作管理器
- ///
- protected readonly IUnitOfWorkActionManager ActionManager;
- ///
/// 发布订阅回调操作
///
protected IPubsubCallback PubsubCallback;
@@ -34,10 +30,6 @@ public class DaprIntegrationEventBus : IIntegrationEventBus {
///
protected IIntegrationEventManager EventManager;
///
- /// 是否立即发送事件
- ///
- protected bool? IsSend;
- ///
/// 发布订阅配置名称
///
protected string Pubsub;
@@ -90,8 +82,6 @@ public DaprIntegrationEventBus( DaprClient client, IOptions options
Options = options?.Value ?? new DaprOptions();
Logger = loggerFactory?.CreateLogger( typeof( DaprEventBus ) ) ?? NullLogger.Instance;
serviceProvider.CheckNull( nameof( serviceProvider ) );
- ActionManager = serviceProvider.GetService();
- ActionManager.CheckNull( nameof( ActionManager ) );
PubsubCallback = serviceProvider.GetService() ?? NullPubsubCallback.Instance;
EventManager = serviceProvider.GetRequiredService();
Headers = new Dictionary();
@@ -102,16 +92,6 @@ public DaprIntegrationEventBus( DaprClient client, IOptions options
#endregion
- #region SendNow
-
- ///
- public IIntegrationEventBus SendNow( bool isSend ) {
- IsSend = isSend;
- return this;
- }
-
- #endregion
-
#region PubsubName
///
@@ -276,13 +256,7 @@ public IIntegrationEventBus OnAfter( Func {
- await PublishEventAsync( @event, cancellationToken );
- } );
+ await PublishEventAsync( @event, cancellationToken );
}
///
@@ -335,35 +309,17 @@ private IDictionary GetImportHeaders() {
/// 初始化
///
protected void Init( IIntegrationEvent integrationEvent ) {
- InitIsSend( integrationEvent );
InitPubsubName( integrationEvent );
InitTopic( integrationEvent );
}
///
- /// 初始化是否立即发送
- ///
- protected void InitIsSend( IIntegrationEvent integrationEvent ) {
- if ( IsSend != null )
- return;
- IsSend = true;
- if ( integrationEvent is not IIntegrationEventExtend extend )
- return;
- if ( extend.SendNow != null )
- IsSend = extend.SendNow;
- }
-
- ///
- /// 初始化发布订阅配置名称
+ /// 初始化发布订阅名称
///
protected void InitPubsubName( IIntegrationEvent integrationEvent ) {
if ( Pubsub.IsEmpty() == false )
return;
- Pubsub = "pubsub";
- if ( integrationEvent is not IIntegrationEventExtend extend )
- return;
- if ( extend.PubsubName.IsEmpty() == false )
- Pubsub = extend.PubsubName;
+ Pubsub = PubsubNameAttribute.GetName( integrationEvent.GetType() );
}
///
@@ -372,11 +328,7 @@ protected void InitPubsubName( IIntegrationEvent integrationEvent ) {
protected void InitTopic( IIntegrationEvent integrationEvent ) {
if ( TopicName.IsEmpty() == false )
return;
- TopicName = integrationEvent.GetType().Name;
- if ( integrationEvent is not IIntegrationEventExtend extend )
- return;
- if ( extend.Topic.IsEmpty() == false )
- TopicName = extend.Topic;
+ TopicName = TopicNameAttribute.GetName( integrationEvent.GetType() );
}
///
diff --git a/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs b/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs
index 76f5c2e8a..c1546eba6 100644
--- a/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs
+++ b/src/Util.Microservices.Dapr/Events/EventLogPubsubCallback.cs
@@ -24,7 +24,10 @@ public EventLogPubsubCallback( IIntegrationEventManager manager ) {
///
public virtual async Task OnPublishAfter( PubsubArgument argument, CancellationToken cancellationToken = default ) {
- await Manager.CreatePublishLogAsync( argument, cancellationToken );
+ var result = await Manager.CreatePublishLogAsync( argument, cancellationToken );
+ if ( result == NullIntegrationEventLog.Instance )
+ return;
+ await Manager.IncrementAsync( cancellationToken );
}
///
diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs
new file mode 100644
index 000000000..5626b2878
--- /dev/null
+++ b/src/Util.Microservices.Dapr/Events/IntegrationEventCount.cs
@@ -0,0 +1,17 @@
+namespace Util.Microservices.Dapr.Events;
+
+///
+/// 集成事件计数
+///
+public class IntegrationEventCount : IDataKey, IETag {
+ ///
+ public string Id { get; set; }
+
+ ///
+ public string ETag { get; set; }
+
+ ///
+ /// 集成事件总条数
+ ///
+ public int? Count { get; set; }
+}
\ No newline at end of file
diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs
index e43a341b9..3a6e33890 100644
--- a/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs
+++ b/src/Util.Microservices.Dapr/Events/IntegrationEventLogStore.cs
@@ -7,29 +7,54 @@ public class IntegrationEventLogStore : IIntegrationEventLogStore {
///
/// 初始化集成事件日志记录存储器
///
- /// 状态管理操作
+ /// 状态管理操作
/// 配置
- public IntegrationEventLogStore( IStateManage stateManage, IOptions options ) {
- StateManage = stateManage ?? throw new ArgumentNullException( nameof( stateManage ) );
- StateManage.StoreName( options?.Value.Pubsub?.EventLogStoreName ?? "statestore" );
+ public IntegrationEventLogStore( IStateManager stateManager, IOptions options ) {
+ StateManager = stateManager ?? throw new ArgumentNullException( nameof( stateManager ) );
+ StateManager.StoreName( options?.Value.Pubsub?.EventLogStoreName ?? "statestore" );
}
+ ///
+ /// 集成事件总条数键名
+ ///
+ public const string KeyCount = "IntegrationEventCount";
///
/// 状态管理操作
///
- protected IStateManage StateManage;
+ protected IStateManager StateManager;
///
public virtual async Task GetAsync( string eventId, CancellationToken cancellationToken = default ) {
- return await StateManage.GetByIdAsync( eventId, cancellationToken );
+ return await StateManager.GetByIdAsync( eventId, cancellationToken );
+ }
+
+ ///
+ public virtual async Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) {
+ await StateManager.SaveAsync( eventLog, cancellationToken );
+ }
+
+ ///
+ public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) {
+ var result = await GetIntegrationEventCount( cancellationToken );
+ result.Count = result.Count.SafeValue() + 1;
+ await StateManager.SaveAsync( result, cancellationToken, KeyCount );
}
///
- /// 保存集成事件日志记录
+ /// 获取集成事件计数
///
- /// 集成事件日志记录
- /// 取消令牌
- public virtual async Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) {
- await StateManage.SaveAsync( eventLog, cancellationToken );
+ protected async Task GetIntegrationEventCount( CancellationToken cancellationToken ) {
+ return await StateManager.GetAsync( KeyCount,cancellationToken ) ?? new IntegrationEventCount();
+ }
+
+ ///
+ public virtual async Task GetCountAsync( CancellationToken cancellationToken = default ) {
+ var result = await GetIntegrationEventCount( cancellationToken );
+ return result.Count.SafeValue();
+ }
+
+ ///
+ public virtual async Task ClearCountAsync( CancellationToken cancellationToken = default ) {
+ await StateManager.RemoveAsync( KeyCount, cancellationToken );
}
}
\ No newline at end of file
diff --git a/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs b/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs
index 84734adc2..3ffe00cb0 100644
--- a/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs
+++ b/src/Util.Microservices.Dapr/Events/IntegrationEventManager.cs
@@ -53,12 +53,48 @@ public IntegrationEventManager( IIntegrationEventLogStore store, Util.Sessions.I
#endregion
+ #region IncrementAsync
+
+ ///
+ public virtual async Task IncrementAsync( CancellationToken cancellationToken = default ) {
+ try {
+ await Store.IncrementAsync( cancellationToken );
+ }
+ catch ( ConcurrencyException ) {
+ Log.LogDebug( "更新集成事件计数出现并发异常,即将重试" );
+ await IncrementAsync( cancellationToken );
+ }
+ catch ( Exception exception ) {
+ Log.LogError( exception, "更新集成事件计数失败" );
+ }
+ }
+
+ #endregion
+
+ #region GetCountAsync
+
+ ///
+ public virtual async Task GetCountAsync( CancellationToken cancellationToken = default ) {
+ return await Store.GetCountAsync( cancellationToken );
+ }
+
+ #endregion
+
+ #region ClearCountAsync
+
+ ///
+ public virtual async Task ClearCountAsync( CancellationToken cancellationToken = default ) {
+ await Store.ClearCountAsync( cancellationToken );
+ }
+
+ #endregion
+
#region GetAsync
///
public virtual async Task GetAsync( string eventId, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
return await Store.GetAsync( eventId, cancellationToken );
}
@@ -148,10 +184,10 @@ public virtual bool IsSubscriptionSuccess( IntegrationEventLog eventLog ) {
///
public virtual async Task CreatePublishLogAsync( PubsubArgument argument, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
var result = CreateEventLog( argument );
if ( result == null )
- return null;
+ return NullIntegrationEventLog.Instance;
await SaveAsync( result, cancellationToken );
Log.LogDebug( "创建集成事件发布日志记录成功,EventLog={@EventLog}", result );
return result;
@@ -189,10 +225,10 @@ protected virtual IntegrationEventLog CreateEventLog( PubsubArgument argument )
///
public virtual async Task CreateSubscriptionLogAsync( string eventId, string routeUrl, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
if ( eventId.IsEmpty() ) {
Log.LogWarning( "创建集成事件订阅日志记录失败,事件标识不能为空,routeUrl={@routeUrl}", routeUrl );
- return null;
+ return NullIntegrationEventLog.Instance;
}
var eventLog = await GetAsync( eventId, cancellationToken );
return await CreateSubscriptionLogAsync( eventLog, routeUrl, cancellationToken );
@@ -201,7 +237,7 @@ protected virtual IntegrationEventLog CreateEventLog( PubsubArgument argument )
///
public virtual async Task CreateSubscriptionLogAsync( IntegrationEventLog eventLog, string routeUrl, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
eventLog.CheckNull( nameof( eventLog ) );
if ( CanSubscription( eventLog ) == false )
return eventLog;
@@ -293,10 +329,10 @@ protected virtual void UpdateRetryTime( SubscriptionLog subscriptionLog ) {
///
public virtual async Task SubscriptionSuccessAsync( string eventId, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
if ( eventId.IsEmpty() ) {
Log.LogWarning( "SubscriptionSuccessAsync更新集成事件订阅日志记录失败,事件标识不能为空." );
- return null;
+ return NullIntegrationEventLog.Instance;
}
var eventLog = await GetAsync( eventId, cancellationToken );
return await SubscriptionSuccessAsync( eventLog, cancellationToken );
@@ -309,13 +345,13 @@ protected virtual void UpdateRetryTime( SubscriptionLog subscriptionLog ) {
/// 取消令牌
public virtual async Task SubscriptionSuccessAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
eventLog.CheckNull( nameof( eventLog ) );
var appId = GetAppId( eventLog.Id );
var subscriptionLog = GetSubscriptionLog( eventLog, appId );
if ( subscriptionLog == null ) {
Log.LogWarning( "更新集成事件订阅日志记录失败,未找到订阅日志记录,appId={appId},EventLog={@EventLog}", appId,ToDebugEventLog( eventLog ) );
- return null;
+ return NullIntegrationEventLog.Instance;
}
subscriptionLog.State = SubscriptionState.Success;
subscriptionLog.LastModificationTime = Time.Now;
@@ -362,10 +398,10 @@ protected void UpdateEventLogState( IntegrationEventLog eventLog ) {
/// 取消令牌
public virtual async Task SubscriptionFailAsync( string eventId, string message, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
if ( eventId.IsEmpty() ) {
Log.LogWarning( "SubscriptionFailAsync更新集成事件订阅日志记录失败,事件标识不能为空." );
- return null;
+ return NullIntegrationEventLog.Instance;
}
var eventLog = await GetAsync( eventId, cancellationToken );
return await SubscriptionFailAsync( eventLog, message, cancellationToken );
@@ -379,13 +415,13 @@ protected void UpdateEventLogState( IntegrationEventLog eventLog ) {
/// 取消令牌
public virtual async Task SubscriptionFailAsync( IntegrationEventLog eventLog, string message, CancellationToken cancellationToken = default ) {
if ( Options.Pubsub.EnableEventLog == false )
- return new IntegrationEventLog();
+ return NullIntegrationEventLog.Instance;
eventLog.CheckNull( nameof( eventLog ) );
var appId = GetAppId( eventLog.Id );
var subscriptionLog = GetSubscriptionLog( eventLog, appId );
if ( subscriptionLog == null ) {
Log.LogWarning( "更新集成事件订阅日志记录失败,未找到订阅日志记录,appId={appId},EventLog={@EventLog}", appId, ToDebugEventLog( eventLog ) );
- return null;
+ return NullIntegrationEventLog.Instance;
}
subscriptionLog.State = SubscriptionState.Fail;
subscriptionLog.LastModificationTime = Time.Now;
diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs
similarity index 55%
rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs
rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs
index 5aa79415a..d86ce746d 100644
--- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManage.cs
+++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManager.cs
@@ -3,7 +3,7 @@
///
/// Dapr状态管理
///
-public class DaprStateManage : DaprStateManageBase, IStateManage {
+public class DaprStateManager : DaprStateManagerBase, IStateManager {
///
/// 初始化Dapr状态管理
///
@@ -11,36 +11,36 @@ public class DaprStateManage : DaprStateManageBase, IStateManage {
/// Dapr配置
/// 日志工厂
/// 状态存储键生成器
- public DaprStateManage( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator )
+ public DaprStateManager( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator )
: base(client,options,loggerFactory,keyGenerator){
}
///
- public IStateManage OrderBy( Expression> expression ) {
+ public IStateManager OrderBy( Expression> expression ) {
Sort.OrderBy( expression );
return this;
}
///
- public IStateManage OrderByDescending( Expression> expression ) {
+ public IStateManager OrderByDescending( Expression> expression ) {
Sort.OrderByDescending( expression );
return this;
}
///
- public IStateManage Equal( Expression> expression, object value ) {
+ public IStateManager Equal( Expression> expression, object value ) {
Filter.Equal( expression, value );
return this;
}
///
- public IStateManage In( Expression> expression, IEnumerable
/// 分页大小
- public TStateManage Limit( int pageSize ) {
+ public TStateManager Limit( int pageSize ) {
Page.Limit = pageSize;
return Return();
}
@@ -28,7 +28,7 @@ public TStateManage Limit( int pageSize ) {
/// 添加迭代令牌
///
/// 迭代令牌
- public TStateManage Token( int token ) {
+ public TStateManager Token( int token ) {
Page.Token = token.ToString();
return Return();
}
@@ -38,7 +38,7 @@ public TStateManage Token( int token ) {
#region OrderBy
///
- public TStateManage OrderBy( string orderBy ) {
+ public TStateManager OrderBy( string orderBy ) {
Sort.OrderBy( orderBy );
return Return();
}
@@ -48,7 +48,7 @@ public TStateManage OrderBy( string orderBy ) {
#region Equal
///
- public TStateManage Equal( string property, object value ) {
+ public TStateManager Equal( string property, object value ) {
Filter.Equal( property, value );
return Return();
}
@@ -58,7 +58,7 @@ public TStateManage Equal( string property, object value ) {
#region EqualIf
///
- public TStateManage EqualIf( string property, object value, bool condition ) {
+ public TStateManager EqualIf( string property, object value, bool condition ) {
return condition ? Equal( property, value ) : Return();
}
@@ -67,13 +67,13 @@ public TStateManage EqualIf( string property, object value, bool condition ) {
#region In
///
- public TStateManage In( string property, IEnumerable values ) {
+ public TStateManager In( string property, IEnumerable values ) {
Filter.In( property, values );
return Return();
}
///
- public TStateManage In( string property, params object[] values ) {
+ public TStateManager In( string property, params object[] values ) {
Filter.In( property, values );
return Return();
}
@@ -83,7 +83,7 @@ public TStateManage In( string property, params object[] values ) {
#region And
///
- public TStateManage And( params IStateCondition[] conditions ) {
+ public TStateManager And( params IStateCondition[] conditions ) {
if ( conditions == null )
return Return();
foreach ( var condition in conditions )
@@ -96,7 +96,7 @@ public TStateManage And( params IStateCondition[] conditions ) {
#region Or
///
- public TStateManage Or( params IStateCondition[] conditions ) {
+ public TStateManager Or( params IStateCondition[] conditions ) {
if ( conditions == null )
return Return();
foreach ( var condition in conditions )
@@ -191,6 +191,19 @@ public TStateManage Or( params IStateCondition[] conditions ) {
#endregion
+ #region SingleAsync
+
+ ///
+ public virtual async Task SingleAsync( CancellationToken cancellationToken = default ) where TValue : IDataKey {
+ SetDataTypeCondition();
+ Page.Limit = 1;
+ var result = await QueryAsync( cancellationToken );
+ var value = result.FirstOrDefault();
+ return await GetByIdAsync( value?.Id, cancellationToken );
+ }
+
+ #endregion
+
#region GetAllAsync
///
diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs
similarity index 90%
rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs
rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs
index 01d8e4ff4..4e8bae550 100644
--- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageBase.cs
+++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerBase.cs
@@ -6,7 +6,7 @@ namespace Util.Microservices.Dapr.StateManagements;
///
/// Dapr状态管理
///
-public partial class DaprStateManageBase : IStateManageBase where TStateManage : IStateManageBase {
+public partial class DaprStateManagerBase : IStateManagerBase where TStateManager : IStateManagerBase {
#region 字段
@@ -74,10 +74,10 @@ public partial class DaprStateManageBase : IStateManageBaseDapr配置
/// 日志工厂
/// 状态存储键生成器
- public DaprStateManageBase( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) {
+ public DaprStateManagerBase( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator ) {
Client = client ?? throw new ArgumentNullException( nameof( client ) );
Options = options?.Value ?? new DaprOptions();
- Logger = loggerFactory?.CreateLogger( typeof( DaprStateManage ) ) ?? NullLogger.Instance;
+ Logger = loggerFactory?.CreateLogger( typeof( DaprStateManager ) ) ?? NullLogger.Instance;
KeyGenerator = keyGenerator ?? throw new ArgumentNullException( nameof( keyGenerator ) );
Metadatas = new Dictionary();
ConsistencyMode = ConsistencyMode.Eventual;
@@ -94,8 +94,8 @@ public DaprStateManageBase( DaprClient client, IOptions options, IL
///
/// 返回
///
- private TStateManage Return() {
- return (TStateManage)(object)this;
+ private TStateManager Return() {
+ return (TStateManager)(object)this;
}
///
@@ -147,7 +147,7 @@ protected void SetDataTypeCondition() {
#region StoreName
///
- public TStateManage StoreName( string storeName ) {
+ public TStateManager StoreName( string storeName ) {
_storeName = storeName;
return Return();
}
@@ -157,7 +157,7 @@ public TStateManage StoreName( string storeName ) {
#region Clear
///
- public TStateManage Clear() {
+ public TStateManager Clear() {
Metadatas.Clear();
ConsistencyMode = ConsistencyMode.Eventual;
StateTransactionRequests.Clear();
@@ -180,7 +180,7 @@ protected void ClearQuery() {
#region BeginTransaction
///
- public TStateManage BeginTransaction() {
+ public TStateManager BeginTransaction() {
IsTransaction = true;
return Return();
}
@@ -190,7 +190,7 @@ public TStateManage BeginTransaction() {
#region JsonSerializerOptions
///
- public TStateManage JsonSerializerOptions( JsonSerializerOptions options ) {
+ public TStateManager JsonSerializerOptions( JsonSerializerOptions options ) {
SerializerOptions = options;
return Return();
}
@@ -204,7 +204,7 @@ public TStateManage JsonSerializerOptions( JsonSerializerOptions options ) {
///
/// 键
/// 值
- public TStateManage Metadata( string key, string value ) {
+ public TStateManager Metadata( string key, string value ) {
if ( key.IsEmpty() )
return Return();
if ( value.IsEmpty() )
@@ -218,7 +218,7 @@ public TStateManage Metadata( string key, string value ) {
/// 设置元数据
///
/// 元数据键值对集合
- public TStateManage Metadata( IDictionary metadata ) {
+ public TStateManager Metadata( IDictionary metadata ) {
if ( metadata == null )
return Return();
foreach ( var item in metadata )
@@ -234,7 +234,7 @@ public TStateManage Metadata( IDictionary metadata ) {
/// 移除元数据
///
/// 键
- public TStateManage RemoveMetadata( string key ) {
+ public TStateManager RemoveMetadata( string key ) {
if ( key.IsEmpty() )
return Return();
if ( Metadatas.ContainsKey( key ) )
@@ -247,7 +247,7 @@ public TStateManage RemoveMetadata( string key ) {
#region ContentType
///
- public TStateManage ContentType( string type ) {
+ public TStateManager ContentType( string type ) {
Metadata( "contentType", type );
return Return();
}
@@ -259,7 +259,7 @@ public TStateManage ContentType( string type ) {
///
/// 设置contentType为application/json
///
- public TStateManage JsonType() {
+ public TStateManager JsonType() {
return ContentType( "application/json" );
}
diff --git a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs
similarity index 55%
rename from src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs
rename to src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs
index 8ee99901c..9c39c2e8d 100644
--- a/src/Util.Microservices.Dapr/StateManagements/DaprStateManageOfT.cs
+++ b/src/Util.Microservices.Dapr/StateManagements/DaprStateManagerOfT.cs
@@ -3,7 +3,7 @@
///
/// Dapr状态管理
///
-public class DaprStateManage : DaprStateManageBase>, IStateManage {
+public class DaprStateManager : DaprStateManagerBase>, IStateManager {
///
/// 初始化Dapr状态管理
///
@@ -11,36 +11,36 @@ public class DaprStateManage : DaprStateManageBase>, IStateMa
/// Dapr配置
/// 日志工厂
/// 状态存储键生成器
- public DaprStateManage( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator )
+ public DaprStateManager( DaprClient client, IOptions options, ILoggerFactory loggerFactory, IKeyGenerator keyGenerator )
: base( client, options, loggerFactory, keyGenerator ) {
}
///
- public IStateManage OrderBy( Expression> expression ) {
+ public IStateManager OrderBy( Expression> expression ) {
Sort.OrderBy( expression );
return this;
}
///
- public IStateManage OrderByDescending( Expression> expression ) {
+ public IStateManager OrderByDescending( Expression> expression ) {
Sort.OrderByDescending( expression );
return this;
}
///
- public IStateManage Equal( Expression> expression, object value ) {
+ public IStateManager Equal( Expression> expression, object value ) {
Filter.Equal( expression, value );
return this;
}
///
- public IStateManage In( Expression> expression, IEnumerable values ) {
+ public IStateManager In( Expression> expression, IEnumerable values ) {
Filter.In( expression, values );
return this;
}
///
- public IStateManage In( Expression> expression, params object[] values ) {
+ public IStateManager In( Expression> expression, params object[] values ) {
Filter.In( expression, values );
return this;
}
diff --git a/src/Util.Microservices.Dapr/StateManageExtensions.cs b/src/Util.Microservices.Dapr/StateManagerExtensions.cs
similarity index 70%
rename from src/Util.Microservices.Dapr/StateManageExtensions.cs
rename to src/Util.Microservices.Dapr/StateManagerExtensions.cs
index 0fd4194b1..b7a8f49ee 100644
--- a/src/Util.Microservices.Dapr/StateManageExtensions.cs
+++ b/src/Util.Microservices.Dapr/StateManagerExtensions.cs
@@ -3,7 +3,7 @@
///
/// 状态管理操作扩展
///
-public static class StateManageExtensions {
+public static class StateManagerExtensions {
#region EqualIfNotEmpty
@@ -13,7 +13,7 @@ public static class StateManageExtensions {
/// 状态管理操作
/// 属性名
/// 属性值,如果为空,则忽略该查询条件
- public static TStateManage EqualIfNotEmpty( this TStateManage source, string property, object value ) where TStateManage : IStateManageBase {
+ public static TStateManager EqualIfNotEmpty( this TStateManager source, string property, object value ) where TStateManager : IStateManagerBase {
source.CheckNull( nameof( source ) );
return value.SafeString().IsEmpty() ? source : source.Equal( property, value );
}
@@ -29,7 +29,7 @@ public static TStateManage EqualIfNotEmpty( this TStateManage sour
/// 属性名
/// 属性值
/// 该值为true时添加查询条件,否则忽略
- public static TStateManage InIf( this TStateManage source, string property, IEnumerable values, bool condition ) where TStateManage : IStateManageBase {
+ public static TStateManager InIf( this TStateManager source, string property, IEnumerable values, bool condition ) where TStateManager : IStateManagerBase {
source.CheckNull( nameof( source ) );
return condition ? source.In( property, values ) : source;
}
diff --git a/src/Util.Microservices/Events/IIntegrationEventLogStore.cs b/src/Util.Microservices/Events/IIntegrationEventLogStore.cs
index 2b170247c..d5cc3b457 100644
--- a/src/Util.Microservices/Events/IIntegrationEventLogStore.cs
+++ b/src/Util.Microservices/Events/IIntegrationEventLogStore.cs
@@ -16,4 +16,19 @@ public interface IIntegrationEventLogStore : ITransientDependency {
/// 集成事件日志记录
/// 取消令牌
Task SaveAsync( IntegrationEventLog eventLog, CancellationToken cancellationToken = default );
+ ///
+ /// 集成事件总条数加1
+ ///
+ /// 取消令牌
+ Task IncrementAsync( CancellationToken cancellationToken = default );
+ ///
+ /// 获取集成事件总条数
+ ///
+ /// 取消令牌
+ Task GetCountAsync( CancellationToken cancellationToken = default );
+ ///
+ /// 清空集成事件总条数
+ ///
+ /// 取消令牌
+ Task ClearCountAsync( CancellationToken cancellationToken = default );
}
\ No newline at end of file
diff --git a/src/Util.Microservices/Events/IIntegrationEventManager.cs b/src/Util.Microservices/Events/IIntegrationEventManager.cs
index 98e03eed6..ca1ca66b9 100644
--- a/src/Util.Microservices/Events/IIntegrationEventManager.cs
+++ b/src/Util.Microservices/Events/IIntegrationEventManager.cs
@@ -4,6 +4,21 @@
/// 集成事件管理器
///
public interface IIntegrationEventManager : ITransientDependency {
+ ///
+ /// 集成事件总条数加1
+ ///
+ /// 取消令牌
+ Task IncrementAsync( CancellationToken cancellationToken = default );
+ ///
+ /// 获取集成事件总条数
+ ///
+ /// 取消令牌
+ Task GetCountAsync( CancellationToken cancellationToken = default );
+ ///
+ /// 清空集成事件总条数
+ ///
+ /// 取消令牌
+ Task ClearCountAsync( CancellationToken cancellationToken = default );
///
/// 获取集成事件日志记录
///
diff --git a/src/Util.Microservices/Events/IntegrationEventLog.cs b/src/Util.Microservices/Events/IntegrationEventLog.cs
index b5e76dd21..9916dba79 100644
--- a/src/Util.Microservices/Events/IntegrationEventLog.cs
+++ b/src/Util.Microservices/Events/IntegrationEventLog.cs
@@ -1,5 +1,15 @@
namespace Util.Microservices.Events;
+///
+/// 空集成事件日志记录
+///
+public class NullIntegrationEventLog : IntegrationEventLog {
+ ///
+ /// 空集成事件日志记录实例
+ ///
+ public static readonly IntegrationEventLog Instance = new NullIntegrationEventLog();
+}
+
///
/// 集成事件日志记录
///
diff --git a/src/Util.Microservices/IMicroserviceClient.cs b/src/Util.Microservices/IMicroserviceClient.cs
index 3b91559b7..7ee1e0ef0 100644
--- a/src/Util.Microservices/IMicroserviceClient.cs
+++ b/src/Util.Microservices/IMicroserviceClient.cs
@@ -22,5 +22,5 @@ public interface IMicroserviceClient {
///
/// 状态管理
///
- IStateManage StateManage { get; }
+ IStateManager StateManager { get; }
}
\ No newline at end of file
diff --git a/src/Util.Microservices/IStateManage.cs b/src/Util.Microservices/IStateManager.cs
similarity index 60%
rename from src/Util.Microservices/IStateManage.cs
rename to src/Util.Microservices/IStateManager.cs
index 9cfc7fc35..7a57488f1 100644
--- a/src/Util.Microservices/IStateManage.cs
+++ b/src/Util.Microservices/IStateManager.cs
@@ -3,67 +3,67 @@
///
/// 状态管理
///
-public interface IStateManage : IStateManageBase {
+public interface IStateManager : IStateManagerBase {
///
/// 设置升序排序属性
///
/// 属性表达式
- IStateManage OrderBy( Expression> expression );
+ IStateManager OrderBy( Expression> expression );
///
/// 设置降序排序属性
///
/// 属性表达式
- IStateManage OrderByDescending( Expression> expression );
+ IStateManager OrderByDescending( Expression> expression );
///
/// 设置相等条件
///
/// 属性表达式
/// 属性值
- IStateManage Equal( Expression> expression, object value );
+ IStateManager Equal( Expression> expression, object value );
///
/// 设置In条件
///
/// 属性表达式
/// 属性值
- IStateManage In( Expression> expression, IEnumerable values );
+ IStateManager In( Expression> expression, IEnumerable values );
///
/// 设置In条件
///
/// 属性表达式
/// 属性值
- IStateManage In( Expression> expression, params object[] values );
+ IStateManager In( Expression> expression, params object[] values );
}
///
/// 状态管理
///
-public interface IStateManage : IStateManageBase> {
+public interface IStateManager : IStateManagerBase> {
///
/// 设置升序排序属性
///
/// 属性表达式
- IStateManage OrderBy( Expression> expression );
+ IStateManager OrderBy( Expression> expression );
///
/// 设置降序排序属性
///
/// 属性表达式
- IStateManage OrderByDescending( Expression> expression );
+ IStateManager OrderByDescending( Expression> expression );
///
/// 设置相等条件
///
/// 属性名表达式
/// 属性值
- IStateManage Equal( Expression> expression, object value );
+ IStateManager Equal( Expression> expression, object value );
///
/// 设置In条件
///
/// 属性名表达式
/// 属性值
- IStateManage In( Expression> expression, IEnumerable values );
+ IStateManager In( Expression> expression, IEnumerable values );
///
/// 设置In条件
///
/// 属性名表达式
/// 属性值
- IStateManage In( Expression> expression, params object[] values );
+ IStateManager In( Expression> expression, params object[] values );
}
\ No newline at end of file
diff --git a/src/Util.Microservices/IStateManageBase.cs b/src/Util.Microservices/IStateManagerBase.cs
similarity index 82%
rename from src/Util.Microservices/IStateManageBase.cs
rename to src/Util.Microservices/IStateManagerBase.cs
index d80119e92..1ebb45ea9 100644
--- a/src/Util.Microservices/IStateManageBase.cs
+++ b/src/Util.Microservices/IStateManagerBase.cs
@@ -5,96 +5,96 @@ namespace Util.Microservices;
///
/// 状态管理
///
-public interface IStateManageBase : ITransientDependency where TStateManage : IStateManageBase {
+public interface IStateManagerBase : ITransientDependency where TStateManager : IStateManagerBase {
///
/// 设置状态组件名称
///
/// 状态组件名称
- TStateManage StoreName( string storeName );
+ TStateManager StoreName( string storeName );
///
/// 清理
///
- TStateManage Clear();
+ TStateManager Clear();
///
/// 开始事务
///
- TStateManage BeginTransaction();
+ TStateManager BeginTransaction();
///
/// 设置Json序列化配置
///
/// Json序列化配置
- TStateManage JsonSerializerOptions( JsonSerializerOptions options );
+ TStateManager JsonSerializerOptions( JsonSerializerOptions options );
///
/// 设置元数据
///
/// 键
/// 值
- TStateManage Metadata( string key, string value );
+ TStateManager Metadata( string key, string value );
///
/// 设置元数据
///
/// 元数据键值对集合
- TStateManage Metadata( IDictionary metadata );
+ TStateManager Metadata( IDictionary metadata );
///
/// 移除元数据
///
/// 键
- TStateManage RemoveMetadata( string key );
+ TStateManager RemoveMetadata( string key );
///
/// 设置内容类型: contentType
///
/// 内容类型
- TStateManage ContentType( string type );
+ TStateManager ContentType( string type );
///
/// 添加分页大小
///
/// 分页大小
- TStateManage Limit( int pageSize );
+ TStateManager Limit( int pageSize );
///
/// 添加迭代令牌,即分页跳过行数
///
/// 迭代令牌,即分页跳过行数
- TStateManage Token( int token );
+ TStateManager Token( int token );
///
/// 添加排序条件
///
/// 排序条件,范例: a,b desc
- TStateManage OrderBy( string orderBy );
+ TStateManager OrderBy( string orderBy );
///
/// 设置相等条件
///
/// 属性名
/// 属性值
- TStateManage Equal( string property, object value );
+ TStateManager Equal( string property, object value );
///
/// 根据规则添加相等查询条件
///
/// 属性名
/// 属性值
/// 该值为true时添加查询条件,否则忽略
- TStateManage EqualIf( string property, object value, bool condition );
+ TStateManager EqualIf( string property, object value, bool condition );
///
/// 设置In条件
///
/// 属性名
/// 属性值
- TStateManage In( string property, IEnumerable values );
+ TStateManager In( string property, IEnumerable values );
///
/// 设置In条件
///
/// 属性名
/// 属性值
- TStateManage In( string property, params object[] values );
+ TStateManager In( string property, params object[] values );
///
/// 设置And连接条件
///
/// 属性名
- TStateManage And( params IStateCondition[] conditions );
+ TStateManager And( params IStateCondition[] conditions );
///
/// 设置Or连接条件
///
/// 属性名
- TStateManage Or( params IStateCondition[] conditions );
+ TStateManager Or( params IStateCondition[] conditions );
///
/// 添加数据
///
@@ -182,6 +182,12 @@ public interface IStateManageBase : ITransientDependency where
/// 取消令牌
Task GetByIdAsync( string id, CancellationToken cancellationToken = default ) where TValue : IDataKey;
///
+ /// 获取指定类型的单条数据
+ ///
+ /// 值类型
+ /// 取消令牌
+ Task SingleAsync( CancellationToken cancellationToken = default ) where TValue : IDataKey;
+ ///
/// 获取指定类型的全部数据
///
/// 值类型
diff --git a/src/Util.Microservices/PubsubNameAttribute.cs b/src/Util.Microservices/PubsubNameAttribute.cs
new file mode 100644
index 000000000..ac9d4d787
--- /dev/null
+++ b/src/Util.Microservices/PubsubNameAttribute.cs
@@ -0,0 +1,38 @@
+namespace Util.Microservices;
+
+///
+/// 发布订阅名称
+///
+public class PubsubNameAttribute : Attribute {
+ ///
+ /// 发布订阅名称
+ ///
+ public string Name { get; }
+
+ ///
+ /// 初始化发布订阅名称
+ ///
+ /// 发布订阅名称
+ public PubsubNameAttribute( string name ) {
+ Name = name;
+ }
+
+ ///
+ /// 获取发布订阅名称,未设置则返回默认值: pubsub
+ ///
+ /// 类型
+ public static string GetName() {
+ return GetName( typeof( T ) );
+ }
+
+ ///
+ /// 获取发布订阅名称
+ ///
+ /// 类型
+ public static string GetName( Type type ) {
+ if ( type == null )
+ return null;
+ var attribute = type.GetCustomAttribute();
+ return attribute == null || attribute.Name.IsEmpty() ? "pubsub" : attribute.Name;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Microservices/TopicNameAttribute.cs b/src/Util.Microservices/TopicNameAttribute.cs
new file mode 100644
index 000000000..e1c16bc2b
--- /dev/null
+++ b/src/Util.Microservices/TopicNameAttribute.cs
@@ -0,0 +1,38 @@
+namespace Util.Microservices;
+
+///
+/// 事件主题名称
+///
+public class TopicNameAttribute : Attribute {
+ ///
+ /// 事件主题名称
+ ///
+ public string Name { get; }
+
+ ///
+ /// 初始化事件主题名称
+ ///
+ /// 事件主题名称
+ public TopicNameAttribute( string name ) {
+ Name = name;
+ }
+
+ ///
+ /// 获取事件主题名称
+ ///
+ /// 类型
+ public static string GetName() {
+ return GetName( typeof( T ) );
+ }
+
+ ///
+ /// 获取事件主题名称
+ ///
+ /// 类型
+ public static string GetName( Type type ) {
+ if ( type == null )
+ return null;
+ var attribute = type.GetCustomAttribute();
+ return attribute == null || attribute.Name.IsEmpty() ? type.Name : attribute.Name;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Microservices/Usings.cs b/src/Util.Microservices/Usings.cs
index 6982fdf75..6763120d6 100644
--- a/src/Util.Microservices/Usings.cs
+++ b/src/Util.Microservices/Usings.cs
@@ -6,6 +6,7 @@
global using System.Text.Json;
global using System.Linq.Expressions;
global using System.ComponentModel;
+global using System.Reflection;
global using Microsoft.AspNetCore.Http;
global using Util.Dependency;
global using Util.Data;
\ No newline at end of file
diff --git a/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs b/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs
index 6bb004283..49367c961 100644
--- a/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs
+++ b/src/Util.Security/Security/Authorization/DefaultPermissionManager.cs
@@ -1,11 +1,18 @@
-namespace Util.Security.Authorization;
+using System.Threading;
+
+namespace Util.Security.Authorization;
///
/// 默认权限管理器
///
public class DefaultPermissionManager : IPermissionManager {
///
- public Task HasPermissionAsync( string resourceUri ) {
+ public bool HasPermission( string resourceUri ) {
+ return true;
+ }
+
+ ///
+ public Task HasPermissionAsync( string resourceUri, CancellationToken cancellationToken = default ) {
return Task.FromResult( true );
}
}
\ No newline at end of file
diff --git a/src/Util.Security/Security/Authorization/IPermissionManager.cs b/src/Util.Security/Security/Authorization/IPermissionManager.cs
index ea87cb9c4..d4b9fc758 100644
--- a/src/Util.Security/Security/Authorization/IPermissionManager.cs
+++ b/src/Util.Security/Security/Authorization/IPermissionManager.cs
@@ -1,4 +1,6 @@
-namespace Util.Security.Authorization;
+using System.Threading;
+
+namespace Util.Security.Authorization;
///
/// 权限管理器
@@ -8,5 +10,11 @@ public interface IPermissionManager {
/// 检查当前用户是否具有该资源的访问权限,返回true表示允许访问,false表示拒绝访问
///
/// 资源标识
- Task HasPermissionAsync( string resourceUri );
+ bool HasPermission( string resourceUri );
+ ///
+ /// 检查当前用户是否具有该资源的访问权限,返回true表示允许访问,false表示拒绝访问
+ ///
+ /// 资源标识
+ /// 取消令牌
+ Task HasPermissionAsync( string resourceUri, CancellationToken cancellationToken = default );
}
\ No newline at end of file
diff --git a/src/Util.Security/Security/ClaimTypes.cs b/src/Util.Security/Security/ClaimTypes.cs
index 29cdbab6d..08ccf5204 100644
--- a/src/Util.Security/Security/ClaimTypes.cs
+++ b/src/Util.Security/Security/ClaimTypes.cs
@@ -37,14 +37,6 @@ public static class ClaimTypes {
///
public static string TenantId { get; set; } = "tenant_id";
///
- /// tenant_code,租户编码
- ///
- public static string TenantCode { get; set; } = "tenant_code";
- ///
- /// tenant_name,租户名称
- ///
- public static string TenantName { get; set; } = "tenant_name";
- ///
/// role,角色
///
public static string Role { get; set; } = "role";
diff --git a/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj b/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj
index 189a5f499..47ecdc330 100644
--- a/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj
+++ b/src/Util.Templates.Razor/02-Util.Templates.Razor.csproj
@@ -38,6 +38,6 @@
-
+
diff --git a/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj b/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj
new file mode 100644
index 000000000..4de393cfc
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/01-Util.Tenants.Abstractions.csproj
@@ -0,0 +1,34 @@
+
+
+
+ $(NetTargetFramework)
+ icon.jpg
+ Util.Tenants.Abstractions
+ Util.Tenants
+ Util.Tenants.Abstractions是Util应用框架多租户接口定义类库
+
+
+
+
+ .\obj\Debug\$(NetTargetFramework)\Util.Tenants.Abstractions.xml
+
+
+
+
+ .\obj\Release\$(NetTargetFramework)\Util.Tenants.Abstractions.xml
+
+
+
+
+ True
+ False
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs b/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs
new file mode 100644
index 000000000..ae37c3e09
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/ISwitchTenantManager.cs
@@ -0,0 +1,20 @@
+namespace Util.Tenants;
+
+///
+/// 切换租户管理器
+///
+public interface ISwitchTenantManager : ITransientDependency {
+ ///
+ /// 获取切换的租户标识
+ ///
+ string GetSwitchTenantId();
+ ///
+ /// 切换租户标识
+ ///
+ /// 租户标识
+ Task SwitchTenantAsync( string tenantId );
+ ///
+ /// 重置租户标识
+ ///
+ Task ResetTenantAsync();
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/ITenant.cs b/src/Util.Tenants.Abstractions/ITenant.cs
new file mode 100644
index 000000000..38e4b5ca4
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/ITenant.cs
@@ -0,0 +1,11 @@
+namespace Util.Tenants;
+
+///
+/// 租户
+///
+public interface ITenant {
+ ///
+ /// 租户标识
+ ///
+ string TenantId { get; set; }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/ITenantManager.cs b/src/Util.Tenants.Abstractions/ITenantManager.cs
new file mode 100644
index 000000000..88220611f
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/ITenantManager.cs
@@ -0,0 +1,31 @@
+namespace Util.Tenants;
+
+///
+/// 租户管理器
+///
+public interface ITenantManager : IScopeDependency {
+ ///
+ /// 是否启用多租户架构
+ ///
+ bool Enabled();
+ ///
+ /// 是否允许使用多数据库
+ ///
+ bool AllowMultipleDatabase();
+ ///
+ /// 是否平台供应商
+ ///
+ bool IsHost();
+ ///
+ /// 获取当前租户标识
+ ///
+ string GetTenantId();
+ ///
+ /// 获取当前租户配置
+ ///
+ TenantInfo GetTenant();
+ ///
+ /// 是否禁用租户过滤器
+ ///
+ bool IsDisableTenantFilter();
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/ITenantStore.cs b/src/Util.Tenants.Abstractions/ITenantStore.cs
new file mode 100644
index 000000000..7442d1744
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/ITenantStore.cs
@@ -0,0 +1,12 @@
+namespace Util.Tenants;
+
+///
+/// 租户存储器
+///
+public interface ITenantStore : IScopeDependency {
+ ///
+ /// 获取当前租户配置
+ ///
+ /// 租户标识
+ TenantInfo GetTenant( string tenantId );
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs b/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs
new file mode 100644
index 000000000..b4ebd6132
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/IViewAllTenantManager.cs
@@ -0,0 +1,19 @@
+namespace Util.Tenants;
+
+///
+/// 查看租户管理器
+///
+public interface IViewAllTenantManager : ITransientDependency {
+ ///
+ /// 是否禁用租户过滤器
+ ///
+ bool IsDisableTenantFilter();
+ ///
+ /// 启用查看所有租户数据,仅对单一数据库模式有效
+ ///
+ Task EnableViewAllAsync();
+ ///
+ /// 禁用查看所有租户数据,仅对单一数据库模式有效
+ ///
+ Task DisableViewAllAsync();
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/NullTenantManager.cs b/src/Util.Tenants.Abstractions/NullTenantManager.cs
new file mode 100644
index 000000000..35983690f
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/NullTenantManager.cs
@@ -0,0 +1,42 @@
+namespace Util.Tenants;
+
+///
+/// 空租户管理器
+///
+[Ioc( -9 )]
+public class NullTenantManager : ITenantManager {
+ ///
+ /// 租户管理器空实例
+ ///
+ public static readonly ITenantManager Instance = new NullTenantManager();
+
+ ///
+ public bool Enabled() {
+ return false;
+ }
+
+ ///
+ public bool AllowMultipleDatabase() {
+ return false;
+ }
+
+ ///
+ public bool IsHost() {
+ return false;
+ }
+
+ ///
+ public bool IsDisableTenantFilter() {
+ return false;
+ }
+
+ ///
+ public string GetTenantId() {
+ return null;
+ }
+
+ ///
+ public TenantInfo GetTenant() {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/NullTenantStore.cs b/src/Util.Tenants.Abstractions/NullTenantStore.cs
new file mode 100644
index 000000000..61372cbc0
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/NullTenantStore.cs
@@ -0,0 +1,20 @@
+namespace Util.Tenants;
+
+///
+/// 空租户存储器
+///
+[Ioc(-9)]
+public class NullTenantStore : ITenantStore {
+ ///
+ /// 空租户存储器
+ ///
+ public static readonly ITenantStore Instance = new NullTenantStore();
+
+ ///
+ /// 获取当前租户配置
+ ///
+ /// 租户标识
+ public TenantInfo GetTenant( string tenantId ) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/TenantInfo.cs b/src/Util.Tenants.Abstractions/TenantInfo.cs
new file mode 100644
index 000000000..46c00811a
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/TenantInfo.cs
@@ -0,0 +1,17 @@
+using Util.Data;
+
+namespace Util.Tenants;
+
+///
+/// 租户信息
+///
+public class TenantInfo {
+ ///
+ /// 租户标识
+ ///
+ public string TenantId { get; set; }
+ ///
+ /// 连接字符串集合
+ ///
+ public ConnectionStringCollection ConnectionStrings { get; set; }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants.Abstractions/Usings.cs b/src/Util.Tenants.Abstractions/Usings.cs
new file mode 100644
index 000000000..5c84008de
--- /dev/null
+++ b/src/Util.Tenants.Abstractions/Usings.cs
@@ -0,0 +1,10 @@
+global using System;
+global using System.Threading.Tasks;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Threading;
+global using System.Text;
+global using System.Reflection;
+global using System.Linq.Expressions;
+global using System.ComponentModel.DataAnnotations;
+global using Util.Dependency;
\ No newline at end of file
diff --git a/src/Util.Tenants/02-Util.Tenants.csproj b/src/Util.Tenants/02-Util.Tenants.csproj
new file mode 100644
index 000000000..38beea97e
--- /dev/null
+++ b/src/Util.Tenants/02-Util.Tenants.csproj
@@ -0,0 +1,34 @@
+
+
+
+ $(NetTargetFramework)
+ icon.jpg
+ Util.Tenants
+ Util.Tenants
+ Util.Tenants是Util应用框架多租户实现类库
+
+
+
+
+ .\obj\Debug\$(NetTargetFramework)\Util.Tenants.xml
+
+
+
+
+ .\obj\Release\$(NetTargetFramework)\Util.Tenants.xml
+
+
+
+
+ True
+ False
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Util.Tenants/AppBuilderExtensions.cs b/src/Util.Tenants/AppBuilderExtensions.cs
new file mode 100644
index 000000000..8fc8229a3
--- /dev/null
+++ b/src/Util.Tenants/AppBuilderExtensions.cs
@@ -0,0 +1,36 @@
+using Util.Configs;
+using Util.Tenants.Resolvers;
+
+namespace Util.Tenants;
+
+///
+/// 租户操作扩展
+///
+public static class AppBuilderExtensions {
+ ///
+ /// 配置租户操作
+ ///
+ /// 应用生成器
+ public static IAppBuilder AddTenant( this IAppBuilder builder ) {
+ return builder.AddTenant( null );
+ }
+
+ ///
+ /// 配置租户操作
+ ///
+ /// 应用生成器
+ /// 租户配置操作
+ public static IAppBuilder AddTenant( this IAppBuilder builder, Action setupAction ) {
+ builder.CheckNull( nameof( builder ) );
+ setupAction ??= tenantOptions => tenantOptions.IsEnabled = true;
+ var options = new TenantOptions {
+ IsEnabled = true
+ };
+ setupAction.Invoke( options );
+ builder.Host.ConfigureServices( ( context, services ) => {
+ services.TryAddSingleton();
+ services.Configure( setupAction );
+ } );
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/ITenantResolver.cs b/src/Util.Tenants/ITenantResolver.cs
new file mode 100644
index 000000000..afa9b42a3
--- /dev/null
+++ b/src/Util.Tenants/ITenantResolver.cs
@@ -0,0 +1,16 @@
+namespace Util.Tenants;
+
+///
+/// 租户解析器
+///
+public interface ITenantResolver {
+ ///
+ /// 解析租户标识
+ ///
+ /// Http上下文
+ Task ResolveAsync( HttpContext context );
+ ///
+ /// 优先级,值越大则优先解析
+ ///
+ int Priority { get; set; }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Managements/SwitchTenantManager.cs b/src/Util.Tenants/Managements/SwitchTenantManager.cs
new file mode 100644
index 000000000..2fb660057
--- /dev/null
+++ b/src/Util.Tenants/Managements/SwitchTenantManager.cs
@@ -0,0 +1,30 @@
+using Util.Helpers;
+
+namespace Util.Tenants.Managements;
+
+///
+/// 切换租户管理器
+///
+public class SwitchTenantManager : ISwitchTenantManager {
+ ///
+ /// 切换租户键名
+ ///
+ public const string Key = "x-switch-tenant";
+
+ ///
+ public string GetSwitchTenantId() {
+ return Web.GetCookie( Key );
+ }
+
+ ///
+ public Task SwitchTenantAsync( string tenantId ) {
+ Web.SetCookie( Key, tenantId );
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task ResetTenantAsync() {
+ Web.RemoveCookie( Key );
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Managements/TenantManager.cs b/src/Util.Tenants/Managements/TenantManager.cs
new file mode 100644
index 000000000..d787f6764
--- /dev/null
+++ b/src/Util.Tenants/Managements/TenantManager.cs
@@ -0,0 +1,105 @@
+namespace Util.Tenants.Managements;
+
+///
+/// 租户管理器
+///
+public class TenantManager : ITenantManager {
+ ///
+ /// 当前租户标识
+ ///
+ private static readonly AsyncLocal _currentTenantId;
+
+ ///
+ /// 初始化租户管理器
+ ///
+ static TenantManager() {
+ _currentTenantId = new AsyncLocal();
+ }
+
+ ///
+ /// 初始化租户管理器
+ ///
+ /// 租户存储器
+ /// 查看租户管理器
+ /// 切换租户管理器
+ /// 用户会话
+ /// 租户配置
+ public TenantManager( ITenantStore tenantStore, IViewAllTenantManager viewAllTenantManager, ISwitchTenantManager switchTenantManager,
+ Sessions.ISession session, IOptions options ) {
+ TenantStore = tenantStore ?? throw new ArgumentNullException( nameof( tenantStore ) );
+ ViewAllTenantManager = viewAllTenantManager ?? throw new ArgumentNullException( nameof( viewAllTenantManager ) );
+ SwitchTenantManager = switchTenantManager ?? throw new ArgumentNullException( nameof( switchTenantManager ) );
+ Session = session ?? throw new ArgumentNullException( nameof( session ) );
+ Options = options?.Value ?? TenantOptions.Null;
+ }
+
+ ///
+ /// 租户存储器
+ ///
+ protected ITenantStore TenantStore { get; }
+ ///
+ /// 查看租户管理器
+ ///
+ protected IViewAllTenantManager ViewAllTenantManager { get; }
+ ///
+ /// 切换租户管理器
+ ///
+ protected ISwitchTenantManager SwitchTenantManager { get; }
+ ///
+ /// 用户会话
+ ///
+ protected Sessions.ISession Session { get; }
+ ///
+ /// 租户配置
+ ///
+ protected TenantOptions Options { get; }
+
+ ///
+ /// 当前租户标识
+ ///
+ public static string CurrentTenantId {
+ get => _currentTenantId.Value;
+ set => _currentTenantId.Value = value;
+ }
+
+ ///
+ public virtual bool Enabled() {
+ return Options.IsEnabled;
+ }
+
+ ///
+ public virtual bool AllowMultipleDatabase() {
+ return Options.IsAllowMultipleDatabase;
+ }
+
+ ///
+ public virtual bool IsHost() {
+ if ( Session.IsAuthenticated == false )
+ return false;
+ if ( Session.UserId.IsEmpty() )
+ return false;
+ if ( Session.TenantId != CurrentTenantId )
+ return false;
+ return Session.TenantId == Options.DefaultTenantId;
+ }
+
+ ///
+ public virtual string GetTenantId() {
+ if ( IsHost() == false )
+ return CurrentTenantId;
+ var switchTenantId = SwitchTenantManager.GetSwitchTenantId();
+ return switchTenantId.IsEmpty() ? CurrentTenantId : switchTenantId;
+ }
+
+ ///
+ public virtual TenantInfo GetTenant() {
+ return TenantStore.GetTenant( GetTenantId() );
+ }
+
+ ///
+ public virtual bool IsDisableTenantFilter() {
+ if ( IsHost() == false )
+ return false;
+ return ViewAllTenantManager.IsDisableTenantFilter();
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Managements/ViewAllTenantManager.cs b/src/Util.Tenants/Managements/ViewAllTenantManager.cs
new file mode 100644
index 000000000..ad382e662
--- /dev/null
+++ b/src/Util.Tenants/Managements/ViewAllTenantManager.cs
@@ -0,0 +1,33 @@
+using Util.Helpers;
+
+namespace Util.Tenants.Managements;
+
+///
+/// 查看租户管理器
+///
+public class ViewAllTenantManager : IViewAllTenantManager {
+ ///
+ /// 查看所有租户键名
+ ///
+ public const string Key = "x-view-all-tenant";
+
+ ///
+ public bool IsDisableTenantFilter() {
+ var result = Web.GetCookie( Key );
+ if ( result.IsEmpty() )
+ return false;
+ return result.ToBool();
+ }
+
+ ///
+ public Task EnableViewAllAsync() {
+ Web.SetCookie( Key,"true" );
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task DisableViewAllAsync() {
+ Web.RemoveCookie( Key );
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Middlewares/TenantMiddleware.cs b/src/Util.Tenants/Middlewares/TenantMiddleware.cs
new file mode 100644
index 000000000..6cf3e0525
--- /dev/null
+++ b/src/Util.Tenants/Middlewares/TenantMiddleware.cs
@@ -0,0 +1,45 @@
+using Util.Tenants.Managements;
+
+namespace Util.Tenants.Middlewares;
+
+///
+/// 租户处理中间件
+///
+public class TenantMiddleware {
+ ///
+ /// 中间件管道
+ ///
+ private readonly RequestDelegate _next;
+
+ ///
+ /// 初始化租户处理中间件
+ ///
+ /// 中间件管道
+ public TenantMiddleware( RequestDelegate next ) {
+ _next = next;
+ }
+
+ ///
+ /// 执行管道
+ ///
+ /// Http上下文
+ public async Task InvokeAsync( HttpContext httpContext ) {
+ if ( _next == null )
+ return;
+ if ( httpContext == null ) {
+ await _next( httpContext );
+ return;
+ }
+ var log = httpContext.RequestServices.GetService>() ?? NullLogger.Instance;
+ var resolver = httpContext.RequestServices.GetRequiredService();
+ try {
+ var tenantId = await resolver.ResolveAsync( httpContext );
+ TenantManager.CurrentTenantId = tenantId;
+ await _next( httpContext );
+ }
+ catch ( Exception exception ) {
+ log.LogError( exception, "租户处理中间件发生异常" );
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/CookieTenantResolver.cs b/src/Util.Tenants/Resolvers/CookieTenantResolver.cs
new file mode 100644
index 000000000..532abe9a2
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/CookieTenantResolver.cs
@@ -0,0 +1,16 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 基于Cookie的租户解析器
+///
+public class CookieTenantResolver : TenantResolverBase {
+ ///
+ /// 解析租户标识
+ ///
+ protected override Task Resolve( HttpContext context ) {
+ var key = GetTenantKey( context );
+ var tenantId = context.Request.Cookies[key];
+ GetLog( context ).LogTrace( $"执行Cookie租户解析器,{key}={tenantId}" );
+ return Task.FromResult( tenantId );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs b/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs
new file mode 100644
index 000000000..5a25c96f7
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/DefaultTenantResolver.cs
@@ -0,0 +1,36 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 默认租户解析器
+///
+public class DefaultTenantResolver : ITenantResolver {
+ ///
+ /// 租户配置
+ ///
+ private readonly TenantOptions _options;
+ ///
+ public int Priority { get; set; }
+
+ ///
+ /// 初始化默认租户解析器
+ ///
+ /// 租户配置
+ public DefaultTenantResolver( IOptions options ) {
+ _options = options?.Value ?? TenantOptions.Null;
+ }
+
+ ///
+ public async Task ResolveAsync( HttpContext context ) {
+ if ( _options.IsEnabled == false )
+ return null;
+ var session = context.RequestServices.GetService();
+ if ( session is { IsAuthenticated: true } )
+ return session.TenantId;
+ foreach ( var resolver in _options.Resolvers.OrderByDescending( t => t.Priority ) ) {
+ var result = await resolver.ResolveAsync( context );
+ if ( result.IsEmpty() == false )
+ return result;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/DomainTenantResolver.cs b/src/Util.Tenants/Resolvers/DomainTenantResolver.cs
new file mode 100644
index 000000000..fd8ef74f3
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/DomainTenantResolver.cs
@@ -0,0 +1,115 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 基于域名的租户解析器
+///
+public class DomainTenantResolver : TenantResolverBase {
+ ///
+ /// 域名格式字符串
+ ///
+ private readonly string _domainFormat;
+ ///
+ /// 域名映射配置
+ ///
+ private readonly IDictionary _domainMap;
+
+ ///
+ /// 初始化基于域名的租户解析器
+ ///
+ public DomainTenantResolver() {
+ }
+
+ ///
+ /// 初始化基于域名的租户解析器
+ ///
+ /// 域名格式字符串,范例:{0}.a.com
+ public DomainTenantResolver( string domainFormat ) {
+ _domainFormat = domainFormat;
+ }
+
+ ///
+ /// 初始化基于域名的租户解析器
+ ///
+ /// 域名映射配置,键为域名,值为租户标识
+ public DomainTenantResolver( IDictionary domainMap ) {
+ _domainMap = domainMap;
+ }
+
+ ///
+ /// 解析租户标识
+ ///
+ protected override async Task Resolve( HttpContext context ) {
+ var host = GetDomain( context.Request.Host.Value );
+ if ( host.IsEmpty() )
+ return null;
+ var result = await Resolve( context,host );
+ GetLog( context ).LogTrace( $"执行域名租户解析器,host={host},tenantId={result}" );
+ return result;
+ }
+
+ ///
+ /// 获取域名
+ ///
+ private string GetDomain( string domain ) {
+ return domain.RemoveStart( "http://" ).RemoveStart( "https://" );
+ }
+
+ ///
+ /// 解析租户标识
+ ///
+ private async Task Resolve( HttpContext context, string host ) {
+ if ( _domainMap != null )
+ return ResolveByMap( host );
+ if ( _domainFormat.IsEmpty() == false )
+ return ResolveByFormat( host );
+ var resolver = context.RequestServices.GetService();
+ return resolver == null ? ResolveBySplit( host ) : await resolver.ResolveTenantIdAsync( host );
+ }
+
+ ///
+ /// 通过域名映射配置解析
+ ///
+ private string ResolveByMap( string host ) {
+ foreach ( var item in _domainMap ) {
+ if ( item.Key == host )
+ return item.Value;
+ }
+ return null;
+ }
+
+ ///
+ /// 通过域名格式字符串解析
+ ///
+ private string ResolveByFormat( string host ) {
+ var formats = GetDomainFormats();
+ foreach ( var format in formats ) {
+ if ( format.IsEmpty() )
+ continue;
+ var result = Util.Helpers.String.Extract( host, GetDomain( format ) ).FirstOrDefault().Value;
+ if( result.IsEmpty() == false )
+ return result;
+ }
+ return null;
+ }
+
+ ///
+ /// 获取域名格式列表
+ ///
+ private IEnumerable GetDomainFormats() {
+ if ( _domainFormat.IndexOf( ",", StringComparison.Ordinal ) >= 0 )
+ return _domainFormat.Split( ',' );
+ if ( _domainFormat.IndexOf( ";", StringComparison.Ordinal ) >= 0 )
+ return _domainFormat.Split( ';' );
+ return new[] { _domainFormat };
+ }
+
+ ///
+ /// 通过拆分解析
+ ///
+ private string ResolveBySplit( string host ) {
+ var items = host.Split( '.' );
+ if ( items.Length > 2 )
+ return items[0];
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs b/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs
new file mode 100644
index 000000000..a5cfc95d8
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/HeaderTenantResolver.cs
@@ -0,0 +1,17 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 基于请求头的租户解析器
+///
+public class HeaderTenantResolver : TenantResolverBase {
+ ///
+ /// 解析租户标识
+ ///
+ protected override Task Resolve( HttpContext context ) {
+ var key = GetTenantKey( context );
+ context.Request.Headers.TryGetValue( key, out var result );
+ var tenantId = result.FirstOrDefault();
+ GetLog( context ).LogTrace( $"执行请求头租户解析器,{key}={tenantId}" );
+ return Task.FromResult( tenantId );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs b/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs
new file mode 100644
index 000000000..47c11a064
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/IDomainTenantResolver.cs
@@ -0,0 +1,14 @@
+using Util.Dependency;
+
+namespace Util.Tenants.Resolvers;
+
+///
+/// 基于域名的租户解析器
+///
+public interface IDomainTenantResolver : ITransientDependency {
+ ///
+ /// 解析租户标识
+ ///
+ /// 域名,范例: a.test.com
+ Task ResolveTenantIdAsync( string host );
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs b/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs
new file mode 100644
index 000000000..d07cf200c
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/QueryStringTenantResolver.cs
@@ -0,0 +1,16 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 基于查询字符串的租户解析器
+///
+public class QueryStringTenantResolver : TenantResolverBase {
+ ///
+ /// 解析租户标识
+ ///
+ protected override Task Resolve( HttpContext context ) {
+ var key = GetTenantKey( context );
+ var tenantId = context.Request.Query[key].FirstOrDefault();
+ GetLog( context ).LogTrace( $"执行查询字符串租户解析器,{key}={tenantId}" );
+ return Task.FromResult( tenantId );
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Resolvers/TenantResolverBase.cs b/src/Util.Tenants/Resolvers/TenantResolverBase.cs
new file mode 100644
index 000000000..be0e03ee6
--- /dev/null
+++ b/src/Util.Tenants/Resolvers/TenantResolverBase.cs
@@ -0,0 +1,36 @@
+namespace Util.Tenants.Resolvers;
+
+///
+/// 租户解析器
+///
+public abstract class TenantResolverBase : ITenantResolver {
+ ///
+ public int Priority { get; set; }
+
+ ///
+ public async Task ResolveAsync( HttpContext context ) {
+ if ( context == null )
+ return null;
+ return await Resolve( context );
+ }
+
+ ///
+ /// 解析租户标识
+ ///
+ protected abstract Task Resolve( HttpContext context );
+
+ ///
+ /// 获取租户键名
+ ///
+ protected string GetTenantKey( HttpContext context ) {
+ var options = context.RequestServices.GetRequiredService>();
+ return options.Value.TenantKey;
+ }
+
+ ///
+ /// 获取日志记录器
+ ///
+ protected ILogger GetLog( HttpContext context ) {
+ return context.RequestServices.GetService>() ?? NullLogger.Instance;
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/TenantMiddlewareExtensions.cs b/src/Util.Tenants/TenantMiddlewareExtensions.cs
new file mode 100644
index 000000000..24cafa643
--- /dev/null
+++ b/src/Util.Tenants/TenantMiddlewareExtensions.cs
@@ -0,0 +1,16 @@
+using Util.Tenants.Middlewares;
+
+namespace Util.Tenants;
+
+///
+/// 租户处理中间件扩展
+///
+public static class TenantMiddlewareExtensions {
+ ///
+ /// 注册租户处理中间件,注意: 必须在 app.UseAuthentication() 之后调用
+ ///
+ /// 应用程序生成器
+ public static IApplicationBuilder UseTenant( this IApplicationBuilder builder ) {
+ return builder.UseMiddleware();
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/TenantOptions.cs b/src/Util.Tenants/TenantOptions.cs
new file mode 100644
index 000000000..9f365b178
--- /dev/null
+++ b/src/Util.Tenants/TenantOptions.cs
@@ -0,0 +1,56 @@
+using Util.Tenants.Resolvers;
+
+namespace Util.Tenants;
+
+///
+/// 租户配置
+///
+public class TenantOptions {
+ ///
+ /// 初始化租户配置
+ ///
+ public TenantOptions() {
+ TenantKey = DefaultTenantKey;
+ Resolvers = new TenantResolverCollection {
+ new HeaderTenantResolver{ Priority = 9 },
+ new QueryStringTenantResolver{ Priority = 7 },
+ new CookieTenantResolver{ Priority = 5 },
+ new DomainTenantResolver{ Priority = 3 }
+ };
+ }
+
+ ///
+ /// 租户配置空实例
+ ///
+ public static readonly TenantOptions Null = new();
+
+ ///
+ /// 默认租户键名,默认值: x-tenant-id
+ ///
+ public const string DefaultTenantKey = "x-tenant-id";
+
+ ///
+ /// 是否启用多租户架构
+ ///
+ public bool IsEnabled { get; set; }
+
+ ///
+ /// 是否允许使用多数据库
+ ///
+ public bool IsAllowMultipleDatabase { get; set; }
+
+ ///
+ /// 默认租户标识
+ ///
+ public string DefaultTenantId { get; set; }
+
+ ///
+ /// 租户键名,默认值: x-tenant-id
+ ///
+ public string TenantKey { get; set; }
+
+ ///
+ /// 租户解析器集合
+ ///
+ public TenantResolverCollection Resolvers { get; }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/TenantResolverCollection.cs b/src/Util.Tenants/TenantResolverCollection.cs
new file mode 100644
index 000000000..7eb08d349
--- /dev/null
+++ b/src/Util.Tenants/TenantResolverCollection.cs
@@ -0,0 +1,99 @@
+namespace Util.Tenants;
+
+///
+/// 租户解析器集合
+///
+public class TenantResolverCollection : IEnumerable {
+ ///
+ /// 租户解析器列表
+ ///
+ private readonly Dictionary _resolvers;
+
+ ///
+ /// 初始化租户解析器集合
+ ///
+ public TenantResolverCollection() {
+ _resolvers = new Dictionary();
+ }
+
+ ///
+ /// 获取枚举器
+ ///
+ IEnumerator IEnumerable.GetEnumerator() {
+ return GetEnumerator();
+ }
+
+ ///
+ /// 获取枚举器
+ ///
+ public IEnumerator GetEnumerator() {
+ return GetResolvers().GetEnumerator();
+ }
+
+ ///
+ /// 获取租户解析器列表
+ ///
+ public List GetResolvers() {
+ return _resolvers.Select( t => t.Value ).ToList();
+ }
+
+ ///
+ /// 添加租户解析器
+ ///
+ /// 租户解析器
+ public void Add( ITenantResolver resolver ) {
+ var key = resolver.GetType().FullName;
+ Add( key, resolver );
+ }
+
+ ///
+ /// 添加租户解析器
+ ///
+ /// 租户解析器键名
+ /// 租户解析器
+ public void Add( string key, ITenantResolver resolver ) {
+ if ( key.IsEmpty() )
+ return;
+ if ( resolver == null )
+ return;
+ Remove( key );
+ _resolvers.Add( key, resolver );
+ }
+
+ ///
+ /// 添加租户解析器列表
+ ///
+ /// 租户解析器列表
+ public void Add( IList resolvers ) {
+ if ( resolvers == null )
+ return;
+ foreach ( var resolver in resolvers )
+ Add( resolver );
+ }
+
+ ///
+ /// 移除租户解析器
+ ///
+ /// 租户解析器类型
+ public void Remove() where TResolver : ITenantResolver {
+ var key = typeof( TResolver ).FullName;
+ Remove( key );
+ }
+
+ ///
+ /// 移除租户解析器
+ ///
+ /// 租户解析器键名
+ public void Remove( string key ) {
+ if ( key.IsEmpty() )
+ return;
+ _resolvers.Remove( key );
+ }
+
+ ///
+ /// 清空租户解析器
+ ///
+ public void Clear() {
+ _resolvers.Clear();
+ }
+}
\ No newline at end of file
diff --git a/src/Util.Tenants/Usings.cs b/src/Util.Tenants/Usings.cs
new file mode 100644
index 000000000..6782ec2b8
--- /dev/null
+++ b/src/Util.Tenants/Usings.cs
@@ -0,0 +1,13 @@
+global using System;
+global using System.Threading.Tasks;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Collections;
+global using System.Threading;
+global using Microsoft.AspNetCore.Http;
+global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Options;
+global using Microsoft.Extensions.DependencyInjection.Extensions;
+global using Microsoft.Extensions.Logging;
+global using Microsoft.Extensions.Logging.Abstractions;
+global using Microsoft.AspNetCore.Builder;
\ No newline at end of file
diff --git a/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj b/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj
index dbc12eb27..b5b46b090 100644
--- a/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj
+++ b/test/Util.Aop.AspectCore.Tests/Util.Aop.AspectCore.Tests.csproj
@@ -7,9 +7,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj
index db13bd332..9adc9e169 100644
--- a/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj
+++ b/test/Util.Application.EntityFrameworkCore.MySql.Tests.Integration/Util.Application.EntityFrameworkCore.MySql.Tests.Integration.csproj
@@ -18,9 +18,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj
index 490dd1a58..5b1dbf784 100644
--- a/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj
+++ b/test/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration/Util.Application.EntityFrameworkCore.PostgreSql.Tests.Integration.csproj
@@ -18,9 +18,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj b/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj
index 5e28e710b..ef7ac05ec 100644
--- a/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj
+++ b/test/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration/Util.Application.EntityFrameworkCore.SqlServer.Tests.Integration.csproj
@@ -18,9 +18,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.Tests/Util.Application.Tests.csproj b/test/Util.Application.Tests/Util.Application.Tests.csproj
index 6f7a7b54f..5b4400b99 100644
--- a/test/Util.Application.Tests/Util.Application.Tests.csproj
+++ b/test/Util.Application.Tests/Util.Application.Tests.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj b/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj
index 8abf5b389..830a93103 100644
--- a/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj
+++ b/test/Util.Application.WebApi.MySql.Tests.Integration/Util.Application.WebApi.MySql.Tests.Integration.csproj
@@ -9,11 +9,11 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj b/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj
index dd79af117..9c27966b2 100644
--- a/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj
+++ b/test/Util.Application.WebApi.PostgreSql.Tests.Integration/Util.Application.WebApi.PostgreSql.Tests.Integration.csproj
@@ -9,11 +9,11 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj b/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj
index ab20e1a20..57d96acd1 100644
--- a/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj
+++ b/test/Util.Application.WebApi.SqlServer.Tests.Integration/Util.Application.WebApi.SqlServer.Tests.Integration.csproj
@@ -9,11 +9,11 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj b/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj
index 58aacec8e..c5b78a775 100644
--- a/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj
+++ b/test/Util.Application.WebApi.Tests.Integration/Util.Application.WebApi.Tests.Integration.csproj
@@ -10,10 +10,10 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs b/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs
index c9c8c5497..5be603835 100644
--- a/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs
+++ b/test/Util.AspNetCore.Tests.Integration/Controllers/Test1Controller.cs
@@ -55,4 +55,12 @@ public ActionResult> List( [FromQuery] CustomerQuery query ) {
};
return result;
}
+
+ ///
+ /// 获取cookie值
+ ///
+ [HttpGet( "cookie" )]
+ public string GetCookie() {
+ return $"code:{Request.Cookies["code"]},name:{Request.Cookies["name"]}";
+ }
}
\ No newline at end of file
diff --git a/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs b/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs
index f2163b5bd..2f220bccb 100644
--- a/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs
+++ b/test/Util.AspNetCore.Tests.Integration/Http/HttpClientServiceTest.Get.cs
@@ -145,4 +145,26 @@ public async Task TestGet_QueryString_6() {
}
#endregion
+
+ #region Cookie
+
+ ///
+ /// ԵGet - cookie - 1
+ ///
+ [Fact]
+ public async Task TestGet_Cookie_1() {
+ var result = await _client.Get( "/api/test1/cookie" ).Cookie( "code", "a" ).GetResultAsync();
+ Assert.Equal( "code:a,name:", result );
+ }
+
+ ///
+ /// ԵGet - cookie - 2
+ ///
+ [Fact]
+ public async Task TestGet_Cookie_2() {
+ var result = await _client.Get( "/api/test1/cookie" ).Cookie( "code", "a" ).Cookie( "name", "b" ).GetResultAsync();
+ Assert.Equal( "code:a,name:b", result );
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj b/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj
index bbed9c24a..224726ac0 100644
--- a/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj
+++ b/test/Util.AspNetCore.Tests.Integration/Util.AspNetCore.Tests.Integration.csproj
@@ -10,9 +10,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj b/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj
index 7cbdcf51d..4b6d7b9c4 100644
--- a/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj
+++ b/test/Util.Caching.EasyCaching.Tests.Integration/Util.Caching.EasyCaching.Tests.Integration.csproj
@@ -9,9 +9,9 @@
-
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Core.Tests.Integration/Helpers/FileTest.cs b/test/Util.Core.Tests.Integration/Helpers/FileTest.cs
index 06bee88f1..7733584eb 100644
--- a/test/Util.Core.Tests.Integration/Helpers/FileTest.cs
+++ b/test/Util.Core.Tests.Integration/Helpers/FileTest.cs
@@ -1,28 +1,28 @@
-using System.Threading.Tasks;
-using Util.Helpers;
-using Xunit;
-
-namespace Util.Tests.Helpers;
-
-///
-/// 文件流操作
-///
-public class FileTest {
- ///
- /// 测试读取文件到字符串
- ///
- [Fact]
- public void TestReadToString() {
- var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" );
- Assert.Equal( "test", File.ReadToString( filePath ) );
- }
-
- ///
- /// 测试读取文件到字符串
- ///
- [Fact]
- public async Task TestReadToStringAsync() {
- var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" );
- Assert.Equal( "test",await File.ReadToStringAsync( filePath ) );
- }
+using System.Threading.Tasks;
+using Util.Helpers;
+using Xunit;
+
+namespace Util.Tests.Helpers;
+
+///
+/// 文件流操作
+///
+public class FileTest {
+ ///
+ /// 测试读取文件到字符串
+ ///
+ [Fact]
+ public void TestReadToString() {
+ var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" );
+ Assert.Equal( "test", File.ReadToString( filePath ) );
+ }
+
+ ///
+ /// 测试读取文件到字符串
+ ///
+ [Fact]
+ public async Task TestReadToStringAsync() {
+ var filePath = Util.Helpers.Common.GetPhysicalPath( "/Samples/FileSample.txt" );
+ Assert.Equal( "test",await File.ReadToStringAsync( filePath ) );
+ }
}
\ No newline at end of file
diff --git a/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj b/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj
index 1d08f0f96..1f84da100 100644
--- a/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj
+++ b/test/Util.Core.Tests.Integration/Util.Core.Tests.Integration.csproj
@@ -20,7 +20,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/test/Util.Core.Tests/Helpers/StringTest.cs b/test/Util.Core.Tests/Helpers/StringTest.cs
index f8b1c7b9b..0a50da165 100644
--- a/test/Util.Core.Tests/Helpers/StringTest.cs
+++ b/test/Util.Core.Tests/Helpers/StringTest.cs
@@ -2,35 +2,43 @@
using System.Collections.Generic;
using System.Text;
using Xunit;
+using String = Util.Helpers.String;
-namespace Util.Tests.Helpers;
+namespace Util.Tests.Helpers;
///
/// 测试字符串操作 - 工具库
///
public class StringTest {
+
+ #region Join
+
///
/// 测试将集合连接为带分隔符的字符串
///
[Fact]
public void TestJoin() {
- Assert.Equal( "1,2,3", Util.Helpers.String.Join( new List { 1, 2, 3 } ) );
- Assert.Equal( "'1','2','3'", Util.Helpers.String.Join( new List { 1, 2, 3 }, "'" ) );
- Assert.Equal( "123", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", "" ) );
- Assert.Equal( "\"1\",\"2\",\"3\"", Util.Helpers.String.Join( new List { 1, 2, 3 }, "\"" ) );
- Assert.Equal( "1 2 3", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", " " ) );
- Assert.Equal( "1;2;3", Util.Helpers.String.Join( new List { 1, 2, 3 }, "", ";" ) );
- Assert.Equal( "1,2,3", Util.Helpers.String.Join( new List { "1", "2", "3" } ) );
- Assert.Equal( "'1','2','3'", Util.Helpers.String.Join( new List