diff --git a/Directory.Packages.props b/Directory.Packages.props
index f045baf184..f6db56e778 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -80,6 +80,7 @@
+
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
index 82aabed272..ed7781b476 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
@@ -251,6 +251,7 @@
"VideoCourses": "Essential Videos",
"DoYouAgreePrivacyPolicy": "By clicking Subscribe button you agree to the Terms & Conditions and Privacy Policy.",
"AbpConferenceDescription": "ABP Conference is a virtual event for .NET developers to learn and connect with the community.",
- "Mobile": "Mobile"
+ "Mobile": "Mobile",
+ "MetaTwitterCard": "summary_large_image"
}
}
\ No newline at end of file
diff --git a/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md
new file mode 100644
index 0000000000..c004d45eba
--- /dev/null
+++ b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/Post.md
@@ -0,0 +1,99 @@
+### ASP.NET Core SignalR New Features — Summary
+
+In this article, I’ll highlight the latest .**NET 9 SignalR updates** for ASP.NET Core 9.0.
+
+
+
+
+
+### SignalR Hub Accepts Base Classes
+
+SignalR `Hub` class can now get a base class of a polymorphic class. As you see in the example below, I can send `Animal` to `Process` method. Before .NET 9, we could only pass the derived classes: `Cat` and `Dog`.
+
+```csharp
+/*** My Base Class is Animal ***/
+[JsonPolymorphic]
+[JsonDerivedType(typeof(Cat), nameof(Cat))]
+[JsonDerivedType(typeof(Dog), nameof(Dog))]
+private class Animal
+{
+ public string Name { get; set; }
+}
+
+/*** CAT derived from Animal ***/
+private class Cat : Animal
+{
+ public CatTypes CatType { get; set; }
+}
+
+/*** DOG derived from Animal ***/
+private class Dog : Animal
+{
+ public DogTypes DogType { get; set; }
+}
+
+
+public class MyHub : Hub
+{
+ /*** We can use the base type Animal here ***/
+ public void Process(Animal animal)
+ {
+ if (animal is Cat) { ... }
+ else if (animal is Dog) { ... }
+ }
+}
+
+```
+
+
+
+### Better Diagnostics and Telemetry
+
+Microsoft focuses mainly on .NET Aspire nowadays. That’s why SignalR now integrates more deeply with the .NET Activity API, which is commonly used for distributed tracing. The enhancement is implemented for better monitoring in [.NET Aspire Dashboard](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/overview?tabs=bash#using-the-dashboard-with-net-aspire-projects). To support this feature:
+
+1- Add these packages to your`csproj`:
+
+```xml
+
+
+
+```
+
+2- Add the following startup code to your host project:
+
+```csharp
+builder.Services.AddSignalR();
+/* After AddSignalR use AddOpenTelemetry() */
+builder
+ .Services
+ .AddOpenTelemetry()
+ .WithTracing(tracing =>
+ {
+ if (builder.Environment.IsDevelopment())
+ {
+ tracing.SetSampler(new AlwaysOnSampler()); //for dev env monitor all traces
+ }
+
+ tracing.AddAspNetCoreInstrumentation();
+ tracing.AddSource("Microsoft.AspNetCore.SignalR.Server");
+ });
+
+builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());
+```
+
+Finally, you’ll see the **SignalR Hub** events on the [Aspire Dashboard](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/overview):
+
+
+
+
+
+### Trimming and Native AOT Support
+
+With .NET 9, **trimming** and **native** **Ahead Of Time** compilation are **supported**. This will improve our application performance. To support AOT, your SignalR object serialization needs to be JSON, and you must use the `System.Text.Json` s[ource generator](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation). Also on the server side, [you shouldn't use](https://github.com/dotnet/aspnetcore/issues/56179) `IAsyncEnumerable` and `ChannelReader` where `T` is a ValueType (`struct`) for Hub method arguments. One more limitation; [Strongly typed hubs](https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-8.0#strongly-typed-hubs) aren't supported with Native AOT (`PublishAot`). And you should use only `Task`, `Task`, `ValueTask`, `ValueTask` for `async` return types.
+
+
+
+---
+
+That's all the new features coming to SignalR in .NET 9!
+Happy coding 🧑🏽💻
diff --git a/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/cover.png b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/cover.png
new file mode 100644
index 0000000000..d5e783520a
Binary files /dev/null and b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/cover.png differ
diff --git a/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/signalr-activity-dashboard.png b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/signalr-activity-dashboard.png
new file mode 100644
index 0000000000..44f167c603
Binary files /dev/null and b/docs/en/Community-Articles/2024-10-01-SignalR-9-New-Features/signalr-activity-dashboard.png differ
diff --git a/docs/en/framework/fundamentals/dynamic-claims.md b/docs/en/framework/fundamentals/dynamic-claims.md
index 44bebd2aea..f326ac444d 100644
--- a/docs/en/framework/fundamentals/dynamic-claims.md
+++ b/docs/en/framework/fundamentals/dynamic-claims.md
@@ -1,8 +1,8 @@
# Dynamic Claims
-When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. For most of the claims, that may not be a problem since claims are not frequently changing values. However, some claims may be changed and these changes should be reflected to the current session immediately. For example, we can revoke a role from a user and that should be immediately effective, otherwise user will continue to use that role's permissions until re-login to the application.
+When a client authenticates and obtains an access token or an authentication cookie, the claims in that token or cookie are not changed unless it re-authenticates. That is not a problem for most claims since the claim values do not frequently change. However, for some claims, it may be required to immediately see the impact after the claim values change in the current session. For example, if a role is revoked from a user, you want to see its effect in the next request. Otherwise, the user will continue to use that role's permissions until re-login to the application.
-ABP's dynamic claims feature is used to automatically and dynamically override the configured claim values in the client's authentication token/cookie by the latest values of these claims.
+ABP's dynamic claims feature dynamically overrides the configured claim values in the client's authentication token/cookie with the latest values of these claims.
## How to Use
@@ -10,7 +10,7 @@ This feature is disabled by default. You should enable it for your application a
> **Beginning from the v8.0, all the [startup templates](../../solution-templates) are pre-configured and the dynamic claims feature is enabled by default. So, if you have created a solution with v8.0 and above, you don't need to make any configuration. Follow the instructions only if you've upgraded from a version lower than 8.0.**
-### Enabling the Dynamic Claims
+### Enabling / Disabling the Dynamic Claims
You can enable it by the following code:
@@ -19,7 +19,7 @@ public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Configure(options =>
{
- options.IsDynamicClaimsEnabled = true;
+ options.IsDynamicClaimsEnabled = true; //set it "true" to enable "Dynamic Claims" or "false" to disable it.
});
}
````
diff --git a/docs/en/modules/docs.md b/docs/en/modules/docs.md
index 6ab4f566c0..dc04339e78 100644
--- a/docs/en/modules/docs.md
+++ b/docs/en/modules/docs.md
@@ -367,7 +367,7 @@ You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to conf
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
```mssql
-INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'', N'12f21123e08e4f15bedbae0b2d939659')
+INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (GitHub)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'GitHub', N'{"GitHubRootUrl":"https://github.com/abpframework/abp/tree/{version}/docs","GitHubAccessToken":"","GitHubUserAgent":""}', N'/', N'dev', N'', N'12f21123e08e4f15bedbae0b2d939659')
```
Be aware that `GitHubAccessToken` is masked. It's a private token and you must get your own token and replace the `***` string.
@@ -407,7 +407,7 @@ You can use [ABP](https://github.com/abpframework/abp/) GitHub documents to conf
For `SQL` databases, you can use the below `T-SQL` command to insert the specified sample into your `DocsProjects` table:
```mssql
-INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'')
+INSERT [dbo].[DocsProjects] ([Id], [Name], [ShortName], [Format], [DefaultDocumentName], [NavigationDocumentName], [MinimumVersion], [DocumentStoreType], [ExtraProperties], [MainWebsiteUrl], [LatestVersionBranchName], [ParametersDocumentName], [ConcurrencyStamp]) VALUES (N'12f21123-e08e-4f15-bedb-ae0b2d939659', N'ABP (FileSystem)', N'abp', N'md', N'Index', N'docs-nav.json', NULL, N'FileSystem', N'{"Path":"C:\\Github\\abp\\docs"}', N'/', NULL, N'', N'12f21123e08e4f15bedbae0b2d939659')
```
Add one of the sample projects above and run the application. In the menu you will see `Documents` link, click the menu link to open the documents page.
diff --git a/docs/en/release-info/migration-guides/abp-8-2.md b/docs/en/release-info/migration-guides/abp-8-2.md
index cd904f052e..15de922d4f 100644
--- a/docs/en/release-info/migration-guides/abp-8-2.md
+++ b/docs/en/release-info/migration-guides/abp-8-2.md
@@ -16,9 +16,6 @@ This document is a guide for upgrading ABP v8.x solutions to ABP v8.2. There are
Before this version, all of the projects above were targeting multiple frameworks (**netstandard2.0**, **netstandard2.1** and **net8.0**), with this version, we started to only target **net8.0** for these template projects. Note that, all other shared libraries still target multiple frameworks.
> This change should not affect your pre-existing solutions and you don't need to make any changes in your application. See the PR for more info: https://github.com/abpframework/abp/pull/19565
-## Upgraded AutoMapper to 13.0.1
-
-In this version, **AutoMapper** library version upgraded to 13.0.1. See [the release notes of AutoMapper v13.0.1](https://github.com/AutoMapper/AutoMapper/releases/tag/v13.0.1) for more information.
## Added default padding to `.tab-content` class for Basic Theme
@@ -60,4 +57,62 @@ In this version, the Angular UI has been updated to use the Angular version 17.3
The **Session Management** feature allows you to prevent concurrent login and manage user sessions.
-In this version, a new entity called `IdentitySession` has been added to the framework and you should create a new migration and apply it to your database.
\ No newline at end of file
+In this version, a new entity called `IdentitySession` has been added to the framework and you should create a new migration and apply it to your database.
+
+## Upgraded NuGet Dependencies
+
+You can see the following list of NuGet libraries that have been upgraded with this release, if you are using one of these packages explicitly, you may consider upgrading them in your solution:
+
+| Package | Old Version | New Version |
+| ---------------------------------------------------------- | ----------- | ----------- |
+| AutoMapper | 12.0.1 | 13.0.1 |
+| Blazorise | 1.4.1 | 1.5.2 |
+| Blazorise.Bootstrap5 | 1.4.1 | 1.5.2 |
+| Blazorise.Icons.FontAwesome | 1.4.1 | 1.5.2 |
+| Blazorise.Components | 1.4.1 | 1.5.2 |
+| Blazorise.DataGrid | 1.4.1 | 1.5.2 |
+| Blazorise.Snackbar | 1.4.1 | 1.5.2 |
+| Hangfire.AspNetCore | 1.8.6 | 1.8.14 |
+| Hangfire.SqlServer | 1.8.6 | 1.8.14 |
+| Microsoft.AspNetCore.Authentication.JwtBearer | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Authentication.OpenIdConnect | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Authorization | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.Authorization | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.Web | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.WebAssembly | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.WebAssembly.Server | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.WebAssembly.Authentication | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Components.WebAssembly.DevServer | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.DataProtection.StackExchangeRedis | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Mvc.NewtonsoftJson | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.Mvc.Testing | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.TestHost | 8.0.0 | 8.0.4 |
+| Microsoft.AspNetCore.WebUtilities | 8.0.0 | 8.0.4 |
+| Microsoft.Data.SqlClient | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.Design | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.InMemory | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.Proxies | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.Relational | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.Sqlite | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.SqlServer | 8.0.0 | 8.0.4 |
+| Microsoft.EntityFrameworkCore.Tools | 8.0.0 | 8.0.4 |
+| Microsoft.Extensions.DependencyInjection.Abstractions | 8.0.0 | 8.0.1 |
+| Microsoft.Extensions.FileProviders.Embedded | 8.0.0 | 8.0.4 |
+| Microsoft.Extensions.Logging.Abstractions | 8.0.0 | 8.0.1 |
+| Microsoft.Extensions.Options | 8.0.0 | 8.0.2 |
+| Microsoft.IdentityModel.Protocols.OpenIdConnect | - | 7.5.1 |
+| Microsoft.IdentityModel.Tokens | - | 7.5.1 |
+| Microsoft.IdentityModel.JsonWebTokens | - | 7.5.1 |
+| System.IdentityModel.Tokens.Jwt | - | 7.5.1 |
+| OpenIddict.Abstractions | 5.1.0 | 5.5.0 |
+| OpenIddict.Core | 5.1.0 | 5.5.0 |
+| OpenIddict.Server.AspNetCore | 5.1.0 | 5.5.0 |
+| OpenIddict.Validation.AspNetCore | 5.1.0 | 5.5.0 |
+| OpenIddict.Validation.ServerIntegration | 5.1.0 | 5.5.0 |
+| Oracle.EntityFrameworkCore | 8.21.121 | 8.23.40 |
+| Pomelo.EntityFrameworkCore.MySql | 8.0.0 | 8.0.2 |
+| SixLabors.ImageSharp | 3.0.2 | 3.1.4 |
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs
index 7852848e30..9d303579da 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Microsoft/AspNetCore/Authentication/Cookies/CookieAuthenticationOptionsExtensions.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Volo.Abp.Threading;
namespace Microsoft.AspNetCore.Authentication.Cookies;
@@ -70,7 +71,8 @@ public static class CookieAuthenticationOptionsExtensions
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme);
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
- openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted);
+ var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService();
+ openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(cancellationTokenProvider.Token);
}
return openIdConnectOptions;
diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
index 95efaf2d0c..7ac6f41176 100644
--- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
+++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyOptions.cs
@@ -19,6 +19,7 @@ using Microsoft.Net.Http.Headers;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
+using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.MultiTenancy;
@@ -92,15 +93,16 @@ public class AbpAspNetCoreMultiTenancyOptions
context.Response.ContentType = resolvedContentType;
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ var cancellationTokenProvider = context.RequestServices.GetRequiredService();
var responseStream = context.Response.Body;
if (resolvedContentTypeEncoding.CodePage == Encoding.UTF8.CodePage)
{
try
{
- await JsonSerializer.SerializeAsync(responseStream, error, error.GetType(), jsonSerializerOptions, context.RequestAborted);
- await responseStream.FlushAsync(context.RequestAborted);
+ await JsonSerializer.SerializeAsync(responseStream, error, error.GetType(), jsonSerializerOptions, cancellationTokenProvider.Token);
+ await responseStream.FlushAsync(cancellationTokenProvider.Token);
}
- catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested) { }
+ catch (OperationCanceledException) when (cancellationTokenProvider.Token.IsCancellationRequested) { }
}
else
{
@@ -108,10 +110,10 @@ public class AbpAspNetCoreMultiTenancyOptions
ExceptionDispatchInfo? exceptionDispatchInfo = null;
try
{
- await JsonSerializer.SerializeAsync(transcodingStream, error, error.GetType(), jsonSerializerOptions, context.RequestAborted);
- await transcodingStream.FlushAsync(context.RequestAborted);
+ await JsonSerializer.SerializeAsync(transcodingStream, error, error.GetType(), jsonSerializerOptions, cancellationTokenProvider.Token);
+ await transcodingStream.FlushAsync(cancellationTokenProvider.Token);
}
- catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested) { }
+ catch (OperationCanceledException) when (cancellationTokenProvider.Token.IsCancellationRequested) { }
catch (Exception ex)
{
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
index 4a6a04a554..9512a82698 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
@@ -1,11 +1,13 @@
using System;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Filters;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc.Uow;
@@ -37,6 +39,7 @@ public class AbpUowActionFilter : IAsyncActionFilter, IAbpFilter, ITransientDepe
var options = CreateOptions(context, unitOfWorkAttr);
var unitOfWorkManager = context.GetRequiredService();
+ var cancellationTokenProvider = context.GetRequiredService();
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
if (unitOfWorkManager.TryBeginReserved(UnitOfWork.UnitOfWorkReservationName, options))
@@ -44,11 +47,11 @@ public class AbpUowActionFilter : IAsyncActionFilter, IAbpFilter, ITransientDepe
var result = await next();
if (Succeed(result))
{
- await SaveChangesAsync(context, unitOfWorkManager);
+ await SaveChangesAsync(context, unitOfWorkManager, cancellationTokenProvider.Token);
}
else
{
- await RollbackAsync(context, unitOfWorkManager);
+ await RollbackAsync(context, unitOfWorkManager, cancellationTokenProvider.Token);
}
return;
@@ -59,11 +62,11 @@ public class AbpUowActionFilter : IAsyncActionFilter, IAbpFilter, ITransientDepe
var result = await next();
if (Succeed(result))
{
- await uow.CompleteAsync(context.HttpContext.RequestAborted);
+ await uow.CompleteAsync(cancellationTokenProvider.Token);
}
else
{
- await uow.RollbackAsync(context.HttpContext.RequestAborted);
+ await uow.RollbackAsync(cancellationTokenProvider.Token);
}
}
}
@@ -85,27 +88,27 @@ public class AbpUowActionFilter : IAsyncActionFilter, IAbpFilter, ITransientDepe
return options;
}
- private async Task RollbackAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
+ private async Task RollbackAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager, CancellationToken cancellationToken)
{
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
- await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
+ await currentUow.RollbackAsync(cancellationToken);
}
}
- private async Task SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
+ private async Task SaveChangesAsync(ActionExecutingContext context, IUnitOfWorkManager unitOfWorkManager, CancellationToken cancellationToken)
{
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
try
{
- await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
+ await currentUow.SaveChangesAsync(cancellationToken);
}
catch (Exception e)
{
- await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
+ await currentUow.RollbackAsync(cancellationToken);
throw;
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
index f4a7be3333..5c69c07797 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowPageFilter.cs
@@ -1,5 +1,6 @@
using System;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
@@ -7,6 +8,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Filters;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc.Uow;
@@ -43,6 +45,7 @@ public class AbpUowPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDependen
var options = CreateOptions(context, unitOfWorkAttr);
var unitOfWorkManager = context.GetRequiredService();
+ var cancellationTokenProvider = context.GetRequiredService();
//Trying to begin a reserved UOW by AbpUnitOfWorkMiddleware
if (unitOfWorkManager.TryBeginReserved(UnitOfWork.UnitOfWorkReservationName, options))
@@ -50,11 +53,11 @@ public class AbpUowPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDependen
var result = await next();
if (Succeed(result))
{
- await SaveChangesAsync(context, unitOfWorkManager);
+ await SaveChangesAsync(context, unitOfWorkManager, cancellationTokenProvider.Token);
}
else
{
- await RollbackAsync(context, unitOfWorkManager);
+ await RollbackAsync(context, unitOfWorkManager, cancellationTokenProvider.Token);
}
return;
@@ -65,11 +68,11 @@ public class AbpUowPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDependen
var result = await next();
if (Succeed(result))
{
- await uow.CompleteAsync(context.HttpContext.RequestAborted);
+ await uow.CompleteAsync(cancellationTokenProvider.Token);
}
else
{
- await uow.RollbackAsync(context.HttpContext.RequestAborted);
+ await uow.RollbackAsync(cancellationTokenProvider.Token);
}
}
}
@@ -91,27 +94,27 @@ public class AbpUowPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDependen
return options;
}
- private async Task RollbackAsync(PageHandlerExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
+ private async Task RollbackAsync(PageHandlerExecutingContext context, IUnitOfWorkManager unitOfWorkManager, CancellationToken cancellationToken)
{
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
- await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
+ await currentUow.RollbackAsync(cancellationToken);
}
}
- private async Task SaveChangesAsync(PageHandlerExecutingContext context, IUnitOfWorkManager unitOfWorkManager)
+ private async Task SaveChangesAsync(PageHandlerExecutingContext context, IUnitOfWorkManager unitOfWorkManager, CancellationToken cancellationToken)
{
var currentUow = unitOfWorkManager.Current;
if (currentUow != null)
{
try
{
- await currentUow.SaveChangesAsync(context.HttpContext.RequestAborted);
+ await currentUow.SaveChangesAsync(cancellationToken);
}
catch (Exception e)
{
- await currentUow.RollbackAsync(context.HttpContext.RequestAborted);
+ await currentUow.RollbackAsync(cancellationToken);
throw;
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs
index 15e353431e..74def27c8e 100644
--- a/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/CookieAuthenticationOptionsExtensions.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Volo.Abp.Threading;
namespace Microsoft.Extensions.DependencyInjection;
@@ -84,9 +85,10 @@ public static class CookieAuthenticationOptionsExtensions
private async static Task GetOpenIdConnectOptions(CookieValidatePrincipalContext principalContext, string oidcAuthenticationScheme)
{
var openIdConnectOptions = principalContext.HttpContext.RequestServices.GetRequiredService>().Get(oidcAuthenticationScheme);
+ var cancellationTokenProvider = principalContext.HttpContext.RequestServices.GetRequiredService();
if (openIdConnectOptions.Configuration == null && openIdConnectOptions.ConfigurationManager != null)
{
- openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(principalContext.HttpContext.RequestAborted);
+ openIdConnectOptions.Configuration = await openIdConnectOptions.ConfigurationManager.GetConfigurationAsync(cancellationTokenProvider.Token);
}
return openIdConnectOptions;
diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs
index a3c98978f0..74833bf748 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Middleware;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Uow;
@@ -14,13 +15,16 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly AbpAspNetCoreUnitOfWorkOptions _options;
+ private readonly ICancellationTokenProvider _cancellationTokenProvider;
public AbpUnitOfWorkMiddleware(
IUnitOfWorkManager unitOfWorkManager,
- IOptions options)
+ IOptions options,
+ ICancellationTokenProvider cancellationTokenProvider)
{
_unitOfWorkManager = unitOfWorkManager;
_options = options.Value;
+ _cancellationTokenProvider = cancellationTokenProvider;
}
public async override Task InvokeAsync(HttpContext context, RequestDelegate next)
@@ -34,7 +38,7 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency
using (var uow = _unitOfWorkManager.Reserve(UnitOfWork.UnitOfWorkReservationName))
{
await next(context);
- await uow.CompleteAsync(context.RequestAborted);
+ await uow.CompleteAsync(_cancellationTokenProvider.Token);
}
}
diff --git a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
index 01a64c4909..cf14047faf 100644
--- a/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
+++ b/framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
@@ -17,6 +17,7 @@
+
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs
index 769cc75a8d..de5f60ee4e 100644
--- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/AbpCachingModule.cs
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using System;
+using Microsoft.Extensions.Caching.Hybrid;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Volo.Abp.Caching.Hybrid;
using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
@@ -25,6 +28,10 @@ public class AbpCachingModule : AbpModule
context.Services.AddSingleton(typeof(IDistributedCache<>), typeof(DistributedCache<>));
context.Services.AddSingleton(typeof(IDistributedCache<,>), typeof(DistributedCache<,>));
+ context.Services.AddHybridCache().AddSerializerFactory();
+ context.Services.AddSingleton(typeof(IHybridCache<>), typeof(AbpHybridCache<>));
+ context.Services.AddSingleton(typeof(IHybridCache<,>), typeof(AbpHybridCache<,>));
+
context.Services.Configure(cacheOptions =>
{
cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromMinutes(20);
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCache.cs
new file mode 100644
index 0000000000..2894af9f3d
--- /dev/null
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCache.cs
@@ -0,0 +1,459 @@
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Hybrid;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Volo.Abp.ExceptionHandling;
+using Volo.Abp.MultiTenancy;
+using Volo.Abp.Threading;
+using Volo.Abp.Uow;
+
+namespace Volo.Abp.Caching.Hybrid;
+
+///
+/// Represents a hybrid cache of items.
+///
+/// The type of the cache item being cached.
+public class AbpHybridCache : IHybridCache
+ where TCacheItem : class
+{
+ public IHybridCache InternalCache { get; }
+
+ public AbpHybridCache(IHybridCache internalCache)
+ {
+ InternalCache = internalCache;
+ }
+
+ public virtual async Task GetOrCreateAsync(string key, Func> factory, Func? optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ return await InternalCache.GetOrCreateAsync(key, factory, optionsFactory, hideErrors, considerUow, token);
+ }
+
+ public virtual async Task SetAsync(string key, TCacheItem value, HybridCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ await InternalCache.SetAsync(key, value, options, hideErrors, considerUow, token);
+ }
+
+ public virtual async Task RemoveAsync(string key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ await InternalCache.RemoveAsync(key, hideErrors, considerUow, token);
+ }
+
+ public virtual async Task RemoveManyAsync(IEnumerable keys, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default)
+ {
+ await InternalCache.RemoveManyAsync(keys, hideErrors, considerUow, token);
+ }
+}
+
+///
+/// Represents a hybrid cache of items.
+/// Uses as the key type.
+///
+/// The type of cache item being cached.
+/// The type of cache key being used.
+public class AbpHybridCache : IHybridCache
+ where TCacheItem : class
+ where TCacheKey : notnull
+{
+ public const string UowCacheName = "AbpHybridCache";
+
+ public ILogger> Logger { get; set; }
+
+ protected string CacheName { get; set; } = default!;
+
+ protected bool IgnoreMultiTenancy { get; set; }
+
+ protected IServiceProvider ServiceProvider { get; }
+
+ protected HybridCache HybridCache { get; }
+
+ protected IDistributedCache DistributedCacheCache { get; }
+
+ protected ICancellationTokenProvider CancellationTokenProvider { get; }
+
+ protected IDistributedCacheKeyNormalizer KeyNormalizer { get; }
+
+ protected IServiceScopeFactory ServiceScopeFactory { get; }
+
+ protected IUnitOfWorkManager UnitOfWorkManager { get; }
+
+ protected SemaphoreSlim SyncSemaphore { get; }
+
+ protected HybridCacheEntryOptions DefaultCacheOptions = default!;
+
+ protected AbpHybridCacheOptions DistributedCacheOption { get; }
+
+ public AbpHybridCache(
+ IServiceProvider serviceProvider,
+ IOptions distributedCacheOption,
+ HybridCache hybridCache,
+ IDistributedCache distributedCache,
+ ICancellationTokenProvider cancellationTokenProvider,
+ IDistributedCacheSerializer serializer,
+ IDistributedCacheKeyNormalizer keyNormalizer,
+ IServiceScopeFactory serviceScopeFactory,
+ IUnitOfWorkManager unitOfWorkManager)
+ {
+ ServiceProvider = serviceProvider;
+ DistributedCacheOption = distributedCacheOption.Value;
+ HybridCache = hybridCache;
+ DistributedCacheCache = distributedCache;
+ CancellationTokenProvider = cancellationTokenProvider;
+ Logger = NullLogger>.Instance;
+ KeyNormalizer = keyNormalizer;
+ ServiceScopeFactory = serviceScopeFactory;
+ UnitOfWorkManager = unitOfWorkManager;
+
+ SyncSemaphore = new SemaphoreSlim(1, 1);
+
+ SetDefaultOptions();
+ }
+
+ protected virtual string NormalizeKey(TCacheKey key)
+ {
+ return KeyNormalizer.NormalizeKey(
+ new DistributedCacheKeyNormalizeArgs(
+ key.ToString()!,
+ CacheName,
+ IgnoreMultiTenancy
+ )
+ );
+ }
+
+ protected virtual HybridCacheEntryOptions GetDefaultCacheEntryOptions()
+ {
+ foreach (var configure in DistributedCacheOption.CacheConfigurators)
+ {
+ var options = configure.Invoke(CacheName);
+ if (options != null)
+ {
+ return options;
+ }
+ }
+
+ return DistributedCacheOption.GlobalHybridCacheEntryOptions;
+ }
+
+ protected virtual void SetDefaultOptions()
+ {
+ CacheName = CacheNameAttribute.GetCacheName(typeof(TCacheItem));
+
+ //IgnoreMultiTenancy
+ IgnoreMultiTenancy = typeof(TCacheItem).IsDefined(typeof(IgnoreMultiTenancyAttribute), true);
+
+ //Configure default cache entry options
+ DefaultCacheOptions = GetDefaultCacheEntryOptions();
+ }
+
+ ///
+ /// Gets or Creates a cache item with the given key. If no cache item is found for the given key then adds a cache item
+ /// provided by delegate and returns the provided cache item.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// The factory delegate is used to provide the cache item when no cache item is found for the given .
+ /// The cache options for the factory delegate.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The cache item.
+ public virtual async Task GetOrCreateAsync(
+ TCacheKey key,
+ Func> factory,
+ Func? optionsFactory = null,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default)
+ {
+ token = CancellationTokenProvider.FallbackToProvider(token);
+ hideErrors ??= DistributedCacheOption.HideErrors;
+
+ TCacheItem? value = null;
+
+ if (!considerUow)
+ {
+ try
+ {
+ value = await HybridCache.GetOrCreateAsync(
+ key: NormalizeKey(key),
+ factory: async cancel => await factory(),
+ options: optionsFactory?.Invoke(),
+ tags: null,
+ cancellationToken: token);
+ }
+ catch (Exception ex)
+ {
+ if (hideErrors == true)
+ {
+ await HandleExceptionAsync(ex);
+ return null;
+ }
+
+ throw;
+ }
+
+ return value;
+ }
+
+ try
+ {
+ using (await SyncSemaphore.LockAsync(token))
+ {
+ if (ShouldConsiderUow(considerUow))
+ {
+ value = GetUnitOfWorkCache().GetOrDefault(key)?.GetUnRemovedValueOrNull();
+ if (value != null)
+ {
+ return value;
+ }
+ }
+
+ var bytes = await DistributedCacheCache.GetAsync(NormalizeKey(key), token);
+ if (bytes != null)
+ {
+ return ResolveSerializer().Deserialize(new ReadOnlySequence(bytes, 0, bytes.Length));;
+ }
+
+ value = await factory();
+
+ if (ShouldConsiderUow(considerUow))
+ {
+ var uowCache = GetUnitOfWorkCache();
+ if (uowCache.TryGetValue(key, out var item))
+ {
+ item.SetValue(value);
+ }
+ else
+ {
+ uowCache.Add(key, new UnitOfWorkCacheItem(value));
+ }
+ }
+
+ await SetAsync(key, value, optionsFactory?.Invoke(), hideErrors, considerUow, token);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (hideErrors == true)
+ {
+ await HandleExceptionAsync(ex);
+ return null;
+ }
+
+ throw;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Sets the cache item value for the provided key.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// The cache item value to set in the cache.
+ /// The cache options for the value.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ public virtual async Task SetAsync(
+ TCacheKey key,
+ TCacheItem value,
+ HybridCacheEntryOptions? options = null,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default)
+ {
+ async Task SetRealCache()
+ {
+ token = CancellationTokenProvider.FallbackToProvider(token);
+ hideErrors ??= DistributedCacheOption.HideErrors;
+
+ try
+ {
+ await HybridCache.SetAsync(
+ key: NormalizeKey(key),
+ value: value,
+ options: options ?? DefaultCacheOptions,
+ tags: null,
+ cancellationToken: token
+ );
+ }
+ catch (Exception ex)
+ {
+ if (hideErrors == true)
+ {
+ await HandleExceptionAsync(ex);
+ return;
+ }
+
+ throw;
+ }
+ }
+
+ if (ShouldConsiderUow(considerUow))
+ {
+ var uowCache = GetUnitOfWorkCache();
+ if (uowCache.TryGetValue(key, out _))
+ {
+ uowCache[key].SetValue(value);
+ }
+ else
+ {
+ uowCache.Add(key, new UnitOfWorkCacheItem(value));
+ }
+
+ UnitOfWorkManager.Current?.OnCompleted(SetRealCache);
+ }
+ else
+ {
+ await SetRealCache();
+ }
+ }
+
+ ///
+ /// Removes the cache item for given key from cache.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ public virtual async Task RemoveAsync(
+ TCacheKey key,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default)
+ {
+ await RemoveManyAsync(new[] { key }, hideErrors, considerUow, token);
+ }
+
+ ///
+ /// Removes the cache items for given keys from cache.
+ ///
+ /// The keys of cached items to be retrieved from the cache.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ public async Task RemoveManyAsync(
+ IEnumerable keys,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default)
+ {
+ var keyArray = keys.ToArray();
+
+ async Task RemoveRealCache()
+ {
+ hideErrors ??= DistributedCacheOption.HideErrors;
+
+ try
+ {
+ await HybridCache.RemoveAsync(
+ keyArray.Select(NormalizeKey), token);
+ }
+ catch (Exception ex)
+ {
+ if (hideErrors == true)
+ {
+ await HandleExceptionAsync(ex);
+ return;
+ }
+
+ throw;
+ }
+ }
+
+ if (ShouldConsiderUow(considerUow))
+ {
+ var uowCache = GetUnitOfWorkCache();
+
+ foreach (var key in keyArray)
+ {
+ if (uowCache.TryGetValue(key, out _))
+ {
+ uowCache[key].RemoveValue();
+ }
+ }
+
+ UnitOfWorkManager.Current?.OnCompleted(RemoveRealCache);
+ }
+ else
+ {
+ await RemoveRealCache();
+ }
+ }
+
+ protected virtual async Task HandleExceptionAsync(Exception ex)
+ {
+ Logger.LogException(ex, LogLevel.Warning);
+
+ using (var scope = ServiceScopeFactory.CreateScope())
+ {
+ await scope.ServiceProvider
+ .GetRequiredService()
+ .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning));
+ }
+ }
+
+ protected virtual bool ShouldConsiderUow(bool considerUow)
+ {
+ return considerUow && UnitOfWorkManager.Current != null;
+ }
+
+ protected virtual string GetUnitOfWorkCacheKey()
+ {
+ return UowCacheName + CacheName;
+ }
+
+ protected virtual Dictionary> GetUnitOfWorkCache()
+ {
+ if (UnitOfWorkManager.Current == null)
+ {
+ throw new AbpException($"There is no active UOW.");
+ }
+
+ return UnitOfWorkManager.Current.GetOrAddItem(GetUnitOfWorkCacheKey(),
+ key => new Dictionary>());
+ }
+
+ private readonly ConcurrentDictionary _serializersCache = new();
+
+ protected virtual IHybridCacheSerializer ResolveSerializer()
+ {
+ if (_serializersCache.TryGetValue(typeof(TCacheItem), out var serializer))
+ {
+ return serializer.As>();
+ }
+
+ serializer = ServiceProvider.GetService>();
+ if (serializer is null)
+ {
+ var factories = ServiceProvider.GetServices().ToArray();
+ Array.Reverse(factories);
+ foreach (var factory in factories)
+ {
+ if (factory.TryCreateSerializer(out var current))
+ {
+ serializer = current;
+ break;
+ }
+ }
+ }
+
+ if (serializer is null)
+ {
+ throw new InvalidOperationException($"No {nameof(IHybridCacheSerializer)} configured for type '{typeof(TCacheItem).Name}'");
+ }
+
+ return serializer.As>();
+ }
+}
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializer.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializer.cs
new file mode 100644
index 0000000000..b94d1a2ea2
--- /dev/null
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializer.cs
@@ -0,0 +1,27 @@
+using System.Buffers;
+using System.Text.Json;
+using Microsoft.Extensions.Caching.Hybrid;
+
+namespace Volo.Abp.Caching.Hybrid;
+
+public class AbpHybridCacheJsonSerializer : IHybridCacheSerializer
+{
+ protected JsonSerializerOptions JsonSerializerOptions { get; }
+
+ public AbpHybridCacheJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
+ {
+ JsonSerializerOptions = jsonSerializerOptions;
+ }
+
+ public virtual T Deserialize(ReadOnlySequence source)
+ {
+ var reader = new Utf8JsonReader(source);
+ return JsonSerializer.Deserialize(ref reader, JsonSerializerOptions)!;
+ }
+
+ public virtual void Serialize(T value, IBufferWriter target)
+ {
+ using var writer = new Utf8JsonWriter(target);
+ JsonSerializer.Serialize(writer, value, JsonSerializerOptions);
+ }
+}
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializerFactory.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializerFactory.cs
new file mode 100644
index 0000000000..65965ecf87
--- /dev/null
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheJsonSerializerFactory.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.Caching.Hybrid;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Json.SystemTextJson;
+
+namespace Volo.Abp.Caching.Hybrid;
+
+public class AbpHybridCacheJsonSerializerFactory : IHybridCacheSerializerFactory
+{
+ protected IOptions Options { get; }
+
+ public AbpHybridCacheJsonSerializerFactory(IOptions options)
+ {
+ Options = options;
+ }
+
+ public bool TryCreateSerializer(out IHybridCacheSerializer? serializer)
+ {
+ if (typeof(T) == typeof(string) || typeof(T) == typeof(byte[]))
+ {
+ serializer = null;
+ return false;
+ }
+
+ serializer = new AbpHybridCacheJsonSerializer(Options.Value.JsonSerializerOptions);
+ return true;
+ }
+}
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheOptions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheOptions.cs
new file mode 100644
index 0000000000..039a5e3142
--- /dev/null
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/AbpHybridCacheOptions.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Hybrid;
+
+namespace Volo.Abp.Caching.Hybrid;
+
+public class AbpHybridCacheOptions
+{
+ ///
+ /// Throw or hide exceptions for the distributed cache.
+ ///
+ public bool HideErrors { get; set; } = true;
+
+ ///
+ /// Cache key prefix.
+ ///
+ public string KeyPrefix { get; set; }
+
+ ///
+ /// Global Cache entry options.
+ ///
+ public HybridCacheEntryOptions GlobalHybridCacheEntryOptions { get; set; }
+
+ ///
+ /// List of all cache configurators.
+ /// (func argument:Name of cache)
+ ///
+ public List> CacheConfigurators { get; set; } //TODO: use a configurator interface instead?
+
+ public AbpHybridCacheOptions()
+ {
+ CacheConfigurators = new List>();
+ GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions();
+ KeyPrefix = "";
+ }
+
+ public void ConfigureCache(HybridCacheEntryOptions? options)
+ {
+ ConfigureCache(typeof(TCacheItem), options);
+ }
+
+ public void ConfigureCache(Type cacheItemType, HybridCacheEntryOptions? options)
+ {
+ ConfigureCache(CacheNameAttribute.GetCacheName(cacheItemType), options);
+ }
+
+ public void ConfigureCache(string cacheName, HybridCacheEntryOptions? options)
+ {
+ CacheConfigurators.Add(name => cacheName != name ? null : options);
+ }
+}
diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/IHybridCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/IHybridCache.cs
new file mode 100644
index 0000000000..4fb61a6e75
--- /dev/null
+++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/Hybrid/IHybridCache.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Caching.Hybrid;
+
+namespace Volo.Abp.Caching.Hybrid;
+
+///
+/// Represents a hybrid cache of items.
+///
+/// The type of the cache item being cached.
+public interface IHybridCache : IHybridCache
+ where TCacheItem : class
+{
+ IHybridCache InternalCache { get; }
+}
+
+///
+/// Represents a hybrid cache of items.
+/// Uses as the key type.
+///
+/// The type of cache item being cached.
+/// The type of cache key being used.
+public interface IHybridCache
+ where TCacheItem : class
+{
+ ///
+ /// Gets or Creates a cache item with the given key. If no cache item is found for the given key then adds a cache item
+ /// provided by delegate and returns the provided cache item.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// The factory delegate is used to provide the cache item when no cache item is found for the given .
+ /// The cache options for the factory delegate.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The cache item.
+ Task GetOrCreateAsync(
+ [NotNull]TCacheKey key,
+ Func> factory,
+ Func? optionsFactory = null,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default);
+
+ ///
+ /// Sets the cache item value for the provided key.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// The cache item value to set in the cache.
+ /// The cache options for the value.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ Task SetAsync(
+ [NotNull]TCacheKey key,
+ TCacheItem value,
+ HybridCacheEntryOptions? options = null,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default);
+
+ ///
+ /// Removes the cache item for given key from cache.
+ ///
+ /// The key of cached item to be retrieved from the cache.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ Task RemoveAsync(
+ [NotNull]TCacheKey key,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default);
+
+ ///
+ /// Removes the cache items for given keys from cache.
+ ///
+ /// The keys of cached items to be retrieved from the cache.
+ /// Indicates to throw or hide the exceptions for the distributed cache.
+ /// This will store the cache in the current unit of work until the end of the current unit of work does not really affect the cache.
+ /// The for the task.
+ /// The indicating that the operation is asynchronous.
+ Task RemoveManyAsync(
+ IEnumerable keys,
+ bool? hideErrors = null,
+ bool considerUow = false,
+ CancellationToken token = default);
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs
index 30e5e81be2..f27fb4c591 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Http/UrlHelpers.cs
@@ -6,6 +6,10 @@ public static class UrlHelpers
{
private const string WildcardSubdomain = "*.";
+ ///
+ /// Check if the subdomain is a subdomain of the domain.
+ /// The Uri must be absolute URI and the scheme, port, and host must be the same.
+ ///
public static bool IsSubdomainOf(string subdomain, string domain)
{
if (Uri.TryCreate(subdomain, UriKind.Absolute, out var subdomainUri) &&
@@ -17,6 +21,10 @@ public static class UrlHelpers
return false;
}
+ ///
+ /// Check if the subdomain is a subdomain of the domain.
+ /// The Uri must be absolute URI and the scheme, port, and host must be the same.
+ ///
public static bool IsSubdomainOf(Uri subdomain, Uri domain)
{
return subdomain.IsAbsoluteUri
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
index 15887933ba..abcdb97a5a 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
@@ -263,7 +263,10 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext,
{
EntityHistoryHelper.UpdateChangeList(entityChangeList);
auditLog!.EntityChanges.AddRange(entityChangeList);
- Logger.LogDebug($"Added {entityChangeList.Count} entity changes to the current audit log");
+ if (entityChangeList.Count > 0)
+ {
+ Logger.LogDebug($"Added {entityChangeList.Count} entity changes to the current audit log");
+ }
}
return result;
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
index 5d002e87fa..cfaee16e6a 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
@@ -60,9 +61,11 @@ public static class AbpEntityTypeBuilderExtensions
return;
}
+ var type = typeof(ExtraPropertiesValueConverter<>).MakeGenericType(b.Metadata.ClrType);
+ var extraPropertiesValueConverter = Activator.CreateInstance(type)!.As>();
b.Property(nameof(IHasExtraProperties.ExtraProperties))
.HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
- .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
+ .HasConversion(extraPropertiesValueConverter)
.Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer());
b.TryConfigureObjectExtensions();
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
index 980d926c3b..a24284356c 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
+++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
@@ -8,22 +8,23 @@ using Volo.Abp.ObjectExtending;
namespace Volo.Abp.EntityFrameworkCore.ValueConverters;
-public class ExtraPropertiesValueConverter : ValueConverter
+public class ExtraPropertiesValueConverter : ValueConverter
{
- public ExtraPropertiesValueConverter(Type entityType)
+ public ExtraPropertiesValueConverter()
: base(
- d => SerializeObject(d, entityType),
- s => DeserializeObject(s, entityType))
+ d => SerializeObject(d),
+ s => DeserializeObject(s))
{
}
public readonly static JsonSerializerOptions SerializeOptions = new JsonSerializerOptions();
- private static string SerializeObject(ExtraPropertyDictionary extraProperties, Type? entityType)
+ private static string SerializeObject(ExtraPropertyDictionary extraProperties)
{
var copyDictionary = new Dictionary(extraProperties);
+ var entityType = typeof(TEntityType);
if (entityType != null)
{
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
@@ -50,7 +51,7 @@ public class ExtraPropertiesValueConverter : ValueConverter(extraPropertiesAsJson, DeserializeOptions) ??
new ExtraPropertyDictionary();
+ var entityType = typeof(TEntityType);
if (entityType != null)
{
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs
index b45cded03c..21f39bdc77 100644
--- a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs
+++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/AbpCachingTestModule.cs
@@ -1,7 +1,9 @@
using Microsoft.Extensions.Caching.Distributed;
using System;
+using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Volo.Abp.Caching.Hybrid;
using Volo.Abp.Modularity;
namespace Volo.Abp.Caching;
@@ -29,6 +31,29 @@ public class AbpCachingTestModule : AbpModule
option.GlobalCacheEntryOptions.SetSlidingExpiration(TimeSpan.FromMinutes(20));
});
+ Configure(option =>
+ {
+ option.CacheConfigurators.Add(cacheName =>
+ {
+ if (cacheName == CacheNameAttribute.GetCacheName(typeof(Sail.Testing.Caching.PersonCacheItem)))
+ {
+ return new HybridCacheEntryOptions()
+ {
+ Expiration = TimeSpan.FromMinutes(10),
+ LocalCacheExpiration = TimeSpan.FromMinutes(5)
+ };
+ }
+
+ return null;
+ });
+
+ option.GlobalHybridCacheEntryOptions = new HybridCacheEntryOptions()
+ {
+ Expiration = TimeSpan.FromMinutes(20),
+ LocalCacheExpiration = TimeSpan.FromMinutes(10)
+ };
+ });
+
context.Services.Replace(ServiceDescriptor.Singleton());
}
}
diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/HybridCache_Tests.cs b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/HybridCache_Tests.cs
new file mode 100644
index 0000000000..04d66903f7
--- /dev/null
+++ b/framework/test/Volo.Abp.Caching.Tests/Volo/Abp/Caching/HybridCache_Tests.cs
@@ -0,0 +1,519 @@
+using System;
+using System.Threading.Tasks;
+using Shouldly;
+using Volo.Abp.Caching.Hybrid;
+using Volo.Abp.Testing;
+using Volo.Abp.Uow;
+using Xunit;
+
+namespace Volo.Abp.Caching;
+
+public class HybridCache_Tests : AbpIntegratedTest
+{
+ [Fact]
+ public async Task Should_GetOrCreate_Set_And_Remove_Cache_Items()
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheKey = Guid.NewGuid().ToString();
+
+ //GetOrCreateAsync
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("john nash")));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("john nash");
+
+ //SetAsync
+ await personCache.SetAsync(cacheKey, new PersonCacheItem("baris"));
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("john nash")));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("baris");
+
+ //RemoveAsync
+ await personCache.RemoveAsync(cacheKey);
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas")));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("lucas");
+ }
+
+ [Fact]
+ public async Task GetOrCreateAsync()
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheKey = Guid.NewGuid().ToString();
+ const string personName = "john nash";
+
+ //Will execute the factory method to create the cache item
+
+ bool factoryExecuted = false;
+
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey,
+ () =>
+ {
+ factoryExecuted = true;
+ return Task.FromResult(new PersonCacheItem(personName));
+ });
+
+ factoryExecuted.ShouldBeTrue();
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+
+ //This time, it will not execute the factory
+
+ factoryExecuted = false;
+
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey,
+ () =>
+ {
+ factoryExecuted = true;
+ return Task.FromResult(new PersonCacheItem(personName));
+ });
+
+ factoryExecuted.ShouldBeFalse();
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+ }
+
+ [Fact]
+ public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache()
+ {
+ var personCache = GetRequiredService>();
+ var otherPersonCache = GetRequiredService>();
+
+ var cacheKey = Guid.NewGuid().ToString();
+ const string personName = "john nash";
+
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+ var cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName)));
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe(personName);
+
+ await personCache.RemoveAsync(cacheKey);
+
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName + "1")));
+ cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName + "1")));
+
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName + "1");
+
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe(personName);
+ }
+
+ [Fact]
+ public async Task Should_Set_Get_And_Remove_Cache_Items_With_Integer_Type_CacheKey()
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheKey = 42;
+ const string personName = "john nash";
+
+ //GetOrCreateAsync
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+
+ //SetAsync
+ await personCache.SetAsync(cacheKey, new PersonCacheItem("baris"));
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("baris");
+
+ //RemoveAsync
+ await personCache.RemoveAsync(cacheKey);
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas")));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("lucas");
+ }
+
+ [Fact]
+ public async Task GetOrAddAsync_With_Integer_Type_CacheKey()
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheKey = 42;
+ const string personName = "john nash";
+
+ //Will execute the factory method to create the cache item
+
+ bool factoryExecuted = false;
+
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey,
+ () =>
+ {
+ factoryExecuted = true;
+ return Task.FromResult(new PersonCacheItem(personName));
+ });
+
+ factoryExecuted.ShouldBeTrue();
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+
+ //This time, it will not execute the factory
+
+ factoryExecuted = false;
+
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey,
+ () =>
+ {
+ factoryExecuted = true;
+ return Task.FromResult(new PersonCacheItem(personName));
+ });
+
+ factoryExecuted.ShouldBeFalse();
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+ }
+
+ [Fact]
+ public async Task SameClassName_But_DiffNamespace_Should_Not_Use_Same_Cache_With_Integer_CacheKey()
+ {
+ var personCache = GetRequiredService>();
+ var otherPersonCache = GetRequiredService>();
+
+ var cacheKey = 42;
+ const string personName = "john nash";
+
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+ var cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName)));
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe(personName);
+
+ await personCache.RemoveAsync(cacheKey);
+
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName + "1")));
+ cacheItem1 = await otherPersonCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new Sail.Testing.Caching.PersonCacheItem(personName + "1")));
+
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName + "1");
+
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe(personName);
+ }
+
+ [Fact]
+ public async Task Should_Set_Get_And_Remove_Cache_Items_With_Object_Type_CacheKey()
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheKey = new ComplexObjectAsCacheKey { Name = "DummyData", Age = 42 };
+ const string personName = "john nash";
+
+ //GetOrCreateAsync
+ var cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe(personName);
+
+ //SetAsync
+ await personCache.SetAsync(cacheKey, new PersonCacheItem("baris"));
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("baris");
+
+ //RemoveAsync
+ await personCache.RemoveAsync(cacheKey);
+
+ //GetOrCreateAsync
+ cacheItem = await personCache.GetOrCreateAsync(cacheKey, () => Task.FromResult(new PersonCacheItem("lucas")));
+ cacheItem.ShouldNotBeNull();
+ cacheItem.Name.ShouldBe("lucas");
+ }
+
+ [Fact]
+ public async Task Should_Set_Get_And_Remove_Cache_Items_For_Same_Object_Type_With_Different_CacheKeys()
+ {
+ var personCache = GetRequiredService>();
+
+ var cache1Key = new ComplexObjectAsCacheKey { Name = "John", Age = 42 };
+ var cache2Key = new ComplexObjectAsCacheKey { Name = "Jenny", Age = 24 };
+ const string personName = "john nash";
+
+ //GetOrCreateAsync
+ var cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem(personName)));
+ var cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe(personName);
+ cacheItem2.ShouldNotBeNull();
+ cacheItem2.Name.ShouldBe(personName);
+
+ //SetAsync
+ cacheItem1 = new PersonCacheItem("baris");
+ cacheItem2 = new PersonCacheItem("jack");
+ await personCache.SetAsync(cache1Key, cacheItem1);
+ await personCache.SetAsync(cache2Key, cacheItem2);
+
+ //GetOrCreateAsync
+ cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem(personName)));
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe("baris");
+ cacheItem2.ShouldNotBeNull();
+ cacheItem2.Name.ShouldBe("jack");
+
+ //Remove
+ await personCache.RemoveAsync(cache1Key);
+ await personCache.RemoveAsync(cache2Key);
+
+ //Get (not exists since removed)
+ cacheItem1 = await personCache.GetOrCreateAsync(cache1Key, () => Task.FromResult(new PersonCacheItem("lucas")));
+ cacheItem2 = await personCache.GetOrCreateAsync(cache2Key, () => Task.FromResult(new PersonCacheItem("peter")));
+ cacheItem1.ShouldNotBeNull();
+ cacheItem1.Name.ShouldBe("lucas");
+ cacheItem2.ShouldNotBeNull();
+ cacheItem2.Name.ShouldBe("peter");
+ }
+
+ [Fact]
+ public async Task Cache_Should_Only_Available_In_Uow_For_GetOrCreateAsync()
+ {
+ const string key = "testkey";
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ var personCache = GetRequiredService>();
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ await personCache.SetAsync(key, new PersonCacheItem("lucas"), considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("lucas");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john3");
+
+ uow.OnCompleted(async () =>
+ {
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("lucas");
+ });
+
+ await uow.CompleteAsync();
+ }
+ }
+
+ [Fact]
+ public async Task Cache_Should_Rollback_With_Uow_For_GetOrCreateAsync()
+ {
+ const string key = "testkey";
+ var personCache = GetRequiredService>();
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ await personCache.SetAsync(key, new PersonCacheItem("john3"), considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john3");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john6")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ [Fact]
+ public async Task Cache_Should_Only_Available_In_Uow_For_SetAsync()
+ {
+ const string key = "testkey";
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ var personCache = GetRequiredService>();
+
+ await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true);
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john3");
+
+ uow.OnCompleted(async () =>
+ {
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ });
+
+ await uow.CompleteAsync();
+ }
+ }
+
+ [Fact]
+ public async Task Cache_Should_Rollback_With_Uow_For_SetAsync()
+ {
+ const string key = "testkey";
+ var personCache = GetRequiredService>();
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ await personCache.SetAsync(key, new PersonCacheItem("john2"), considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john2");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ [Fact]
+ public async Task Cache_Should_Only_Available_In_Uow_For_RemoveAsync()
+ {
+ const string key = "testkey";
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ var personCache = GetRequiredService>();
+
+ await personCache.SetAsync(key, new PersonCacheItem("john"), considerUow: true);
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john2")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ await personCache.RemoveAsync(key, considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john3");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john4")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john4");
+
+ uow.OnCompleted(async () =>
+ {
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john5")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john3");
+ });
+
+ await uow.CompleteAsync();
+ }
+ }
+
+ public async Task Cache_Should_Rollback_With_Uow_For_RemoveAsync()
+ {
+ const string key = "testkey";
+ var personCache = GetRequiredService>();
+
+ var cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ using (var uow = GetRequiredService().Begin())
+ {
+ await personCache.SetAsync(key, new PersonCacheItem("john2"), considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john2");
+
+ await personCache.RemoveAsync(key, considerUow: true);
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: true);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john3")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ cacheValue = await personCache.GetOrCreateAsync(key, () => Task.FromResult(new PersonCacheItem("john")), considerUow: false);
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+ }
+
+ [Fact]
+ public async Task Should_Remove_Multiple_Items_Async()
+ {
+ var testkey = "testkey";
+ var testkey2 = "testkey2";
+ var testkey3 = new[] { testkey, testkey2 };
+
+ var personCache = GetRequiredService>();
+
+ var cacheValue = await personCache.GetOrCreateAsync(testkey, () => Task.FromResult(new PersonCacheItem("john")));
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john");
+
+ cacheValue = await personCache.GetOrCreateAsync(testkey2, () => Task.FromResult(new PersonCacheItem("jack")));
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("jack");
+
+ await personCache.RemoveManyAsync(testkey3);
+
+ cacheValue = await personCache.GetOrCreateAsync(testkey, () => Task.FromResult(new PersonCacheItem("john2")));
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("john2");
+
+ cacheValue = await personCache.GetOrCreateAsync(testkey2, () => Task.FromResult(new PersonCacheItem("jack2")));
+ cacheValue.ShouldNotBeNull();
+ cacheValue.Name.ShouldBe("jack2");
+ }
+
+ [Fact]
+ public async Task Should_Get_Same_Cache_Set_When_Resolve_With_Or_Without_Key()
+ {
+ var cache1 = GetRequiredService>();
+ var cache2 = GetRequiredService>();
+
+ cache1.InternalCache.ShouldBe(cache2);
+
+ await cache1.SetAsync("john", new PersonCacheItem("john"));
+
+ var item1 = await cache1.GetOrCreateAsync("john", () => Task.FromResult(new PersonCacheItem("john2")));
+ item1.ShouldNotBeNull();
+ item1.Name.ShouldBe("john");
+
+ var item2 = await cache1.GetOrCreateAsync("john", () => Task.FromResult(new PersonCacheItem("john3")));
+ item2.ShouldNotBeNull();
+ item2.Name.ShouldBe("john");
+ }
+}
diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs
index c6fd04949a..9cb1c003ba 100644
--- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs
+++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/ValueConverters/EFCore_DateTimeKindTests.cs
@@ -11,7 +11,7 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters;
public abstract class EFCore_DateTimeKindTests : DateTimeKind_Tests
{
- [Fact]
+ [Fact(Skip = "https://github.com/dotnet/efcore/issues/34760")]
public async Task DateTime_Kind_Should_Be_Normalized_In_View_Query_Test()
{
var personName = "bob lee";
diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs
index e260e3debe..adf650c2ba 100644
--- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs
+++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs
@@ -64,7 +64,7 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
.ShouldBeTrue();
});
}
-
+
[Theory]
[InlineData("https://abp.io/features")]
public async Task CreateAsync_ShouldCreateComment_If_Url_Allowed(string text)
@@ -84,8 +84,8 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
}
[Theory]
- [InlineData("[ABP Community](https://abp.io/community/)")]
- [InlineData("abp.io/docs")]
+ [InlineData("[ABP Community](https://community.abp.io/)")]
+ [InlineData("docs.abp.io")]
public async Task CreateAsync_ShouldThrowUserFriendlyException_If_Url_UnAllowed(string text)
{
_currentUser.Id.Returns(_cmsKitTestData.User2Id);
@@ -94,7 +94,7 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
await _commentAppService.CreateAsync(
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,
- new CreateCommentInput
+ new CreateCommentInput
{
RepliedCommentId = null,
Text = text, //not allowed URL
@@ -104,7 +104,7 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
}
[Fact]
- public async Task CreateAsync_ShouldThrowUserFriendlyException_If_IdempotencyToken_Not_Unique()
+ public async Task CreateAsync_ShouldThrowUserFriendlyException_If_IdempotencyToken_Not_Unique()
{
_currentUser.Id.Returns(_cmsKitTestData.User2Id);
@@ -112,10 +112,10 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
await _commentAppService.CreateAsync(
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,
- new CreateCommentInput
+ new CreateCommentInput
{
RepliedCommentId = null,
- Text = "",
+ Text = "",
IdempotencyToken = _cmsKitTestData.IdempotencyToken_1
}
));
@@ -139,7 +139,7 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
comment.Text.ShouldBe("I'm Updated");
});
}
-
+
[Fact]
public async Task UpdateAsync_ShouldThrowUserFriendlyException_If_Url_UnAllowed()
{
@@ -148,9 +148,9 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
await Should.ThrowAsync(async () =>
await _commentAppService.UpdateAsync(
_cmsKitTestData.CommentWithChildId,
- new UpdateCommentInput
+ new UpdateCommentInput
{
- Text = "[ABP Community - Update](https://abp.io/community/)", //not allowed URL
+ Text = "[ABP Community - Update](https://community.abp.io/)", //not allowed URL
}
));
}
diff --git a/modules/docs/app/VoloDocs.Web/Program.cs b/modules/docs/app/VoloDocs.Web/Program.cs
index 5a0860f727..c0373525db 100644
--- a/modules/docs/app/VoloDocs.Web/Program.cs
+++ b/modules/docs/app/VoloDocs.Web/Program.cs
@@ -1,6 +1,9 @@
using System;
using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
@@ -9,7 +12,7 @@ namespace VoloDocs.Web
{
public class Program
{
- public static int Main(string[] args)
+ public async static Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() //TODO: Should be configurable!
@@ -22,7 +25,14 @@ namespace VoloDocs.Web
try
{
Log.Information("Starting web host.");
- CreateHostBuilder(args).Build().Run();
+ var builder = WebApplication.CreateBuilder(args);
+ builder.Host
+ .UseAutofac()
+ .UseSerilog();
+ await builder.AddApplicationAsync();
+ var app = builder.Build();
+ await app.InitializeApplicationAsync();
+ await app.RunAsync();
return 0;
}
catch (Exception ex)
@@ -32,17 +42,8 @@ namespace VoloDocs.Web
}
finally
{
- Log.CloseAndFlush();
+ await Log.CloseAndFlushAsync();
}
}
-
- internal static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseStartup();
- })
- .UseAutofac()
- .UseSerilog();
}
}
diff --git a/modules/docs/app/VoloDocs.Web/Properties/launchSettings.json b/modules/docs/app/VoloDocs.Web/Properties/launchSettings.json
index 08e2199ddb..2d88b20c15 100644
--- a/modules/docs/app/VoloDocs.Web/Properties/launchSettings.json
+++ b/modules/docs/app/VoloDocs.Web/Properties/launchSettings.json
@@ -3,8 +3,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
- "applicationUrl": "https://localhost:44333/",
- "sslPort": 0
+ "applicationUrl": "https://localhost:5001/",
+ "sslPort": 5001
}
},
"profiles": {
@@ -21,7 +21,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
- "applicationUrl": "https://localhost:44333/"
+ "applicationUrl": "https://localhost:5001/"
}
}
}
diff --git a/modules/docs/app/VoloDocs.Web/Startup.cs b/modules/docs/app/VoloDocs.Web/Startup.cs
deleted file mode 100644
index 22d7b980c5..0000000000
--- a/modules/docs/app/VoloDocs.Web/Startup.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Volo.Abp;
-
-namespace VoloDocs.Web
-{
- public class Startup
- {
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddApplication();
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
- {
- app.InitializeApplication();
- }
- }
-}
diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs
index aba271272a..e16f81d2c9 100644
--- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs
+++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs
@@ -32,6 +32,8 @@ namespace Volo.Docs.FileSystem.Documents
localDirectory = documentName.Substring(0, documentName.LastIndexOf('/'));
}
+ version = File.GetLastWriteTime(path).ToString("yyyyMMddHHmmss");
+
return new Document(GuidGenerator.Create(),
project.Id,
documentName,
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
index 96cb8df5c9..ca047730ee 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySessionConsts.cs
@@ -10,5 +10,5 @@ public class IdentitySessionConsts
public static int MaxClientIdLength { get; set; } = 64;
- public static int MaxIpAddressesLength { get; set; } = 256;
+ public static int MaxIpAddressesLength { get; set; } = 2048;
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs
index 4bcf4f01e3..bb8386dda9 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConfiguration.cs
@@ -40,4 +40,13 @@ public class IdentityModuleExtensionConfiguration : ModuleExtensionConfiguration
configureAction
);
}
+
+ public IdentityModuleExtensionConfiguration ConfigureIdentitySession(
+ Action configureAction)
+ {
+ return this.ConfigureEntity(
+ IdentityModuleExtensionConsts.EntityNames.IdentitySession,
+ configureAction
+ );
+ }
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs
index 5135cf22b8..4e3c5ab4a2 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/ObjectExtending/IdentityModuleExtensionConsts.cs
@@ -13,6 +13,8 @@ public static class IdentityModuleExtensionConsts
public const string ClaimType = "ClaimType";
public const string OrganizationUnit = "OrganizationUnit";
+
+ public const string IdentitySession = "IdentitySession";
}
public static class ConfigurationNames
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs
index 49f353153b..baf0871c58 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs
@@ -99,6 +99,12 @@ public class AbpIdentityDomainModule : AbpModule
IdentityModuleExtensionConsts.EntityNames.OrganizationUnit,
typeof(OrganizationUnit)
);
+
+ ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity(
+ IdentityModuleExtensionConsts.ModuleName,
+ IdentityModuleExtensionConsts.EntityNames.IdentitySession,
+ typeof(IdentitySession)
+ );
});
}
}
diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
index c25a6b2e82..717bc7f989 100644
--- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
+++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySession.cs
@@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity;
-public class IdentitySession : BasicAggregateRoot, IMultiTenant
+public class IdentitySession : AggregateRoot, IMultiTenant
{
public virtual string SessionId { get; protected set; }
@@ -80,7 +80,22 @@ public class IdentitySession : BasicAggregateRoot, IMultiTenant
private static string JoinAsString(IEnumerable list)
{
var serialized = string.Join(",", list);
- return serialized.IsNullOrWhiteSpace() ? null : serialized;
+ if (serialized.IsNullOrWhiteSpace())
+ {
+ return null;
+ }
+
+ while (serialized.Length > IdentitySessionConsts.MaxIpAddressesLength)
+ {
+ var lastCommaIndex = serialized.IndexOf(',');
+ if (lastCommaIndex < 0)
+ {
+ return serialized;
+ }
+ serialized = serialized.Substring(lastCommaIndex + 1);
+ }
+
+ return serialized;
}
private string[] GetArrayFromString(string str)
diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.Designer.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.Designer.cs
similarity index 95%
rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.Designer.cs
rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.Designer.cs
index dfea4e41ba..158f345fbe 100644
--- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.Designer.cs
+++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.Designer.cs
@@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace OpenIddict.Demo.Server.Migrations
{
[DbContext(typeof(ServerDbContext))]
- [Migration("20240427010513_Initial")]
+ [Migration("20240829013142_Initial")]
partial class Initial
{
///
@@ -22,7 +22,7 @@ namespace OpenIddict.Demo.Server.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
- .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -990,41 +990,11 @@ namespace OpenIddict.Demo.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExtraProperties")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Properties")
.HasColumnType("nvarchar(max)");
@@ -1149,22 +1119,6 @@ namespace OpenIddict.Demo.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExpirationDate")
.HasColumnType("datetime2");
@@ -1173,20 +1127,6 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Payload")
.HasColumnType("nvarchar(max)");
diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.cs
similarity index 97%
rename from modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.cs
rename to modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.cs
index dabfc5ffa3..537745685f 100644
--- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.cs
+++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240829013142_Initial.cs
@@ -614,14 +614,7 @@ namespace OpenIddict.Demo.Server.Migrations
Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true),
Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false),
- ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false),
- CreationTime = table.Column(type: "datetime2", nullable: false),
- CreatorId = table.Column(type: "uniqueidentifier", nullable: true),
- LastModificationTime = table.Column(type: "datetime2", nullable: true),
- LastModifierId = table.Column(type: "uniqueidentifier", nullable: true),
- IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false),
- DeleterId = table.Column(type: "uniqueidentifier", nullable: true),
- DeletionTime = table.Column(type: "datetime2", nullable: true)
+ ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false)
},
constraints: table =>
{
@@ -650,14 +643,7 @@ namespace OpenIddict.Demo.Server.Migrations
Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true),
Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false),
- ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false),
- CreationTime = table.Column(type: "datetime2", nullable: false),
- CreatorId = table.Column(type: "uniqueidentifier", nullable: true),
- LastModificationTime = table.Column(type: "datetime2", nullable: true),
- LastModifierId = table.Column(type: "uniqueidentifier", nullable: true),
- IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false),
- DeleterId = table.Column(type: "uniqueidentifier", nullable: true),
- DeletionTime = table.Column(type: "datetime2", nullable: true)
+ ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false)
},
constraints: table =>
{
diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
index a2be3405db..bdbb1fca9b 100644
--- a/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
+++ b/modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
@@ -19,7 +19,7 @@ namespace OpenIddict.Demo.Server.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
- .HasAnnotation("ProductVersion", "8.0.0")
+ .HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -987,41 +987,11 @@ namespace OpenIddict.Demo.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExtraProperties")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Properties")
.HasColumnType("nvarchar(max)");
@@ -1146,22 +1116,6 @@ namespace OpenIddict.Demo.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExpirationDate")
.HasColumnType("datetime2");
@@ -1170,20 +1124,6 @@ namespace OpenIddict.Demo.Server.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Payload")
.HasColumnType("nvarchar(max)");
diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs
index e73bb0e77f..b6e262ee32 100644
--- a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs
+++ b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddictServerModule.cs
@@ -34,6 +34,7 @@ using Volo.Abp.SettingManagement.Web;
using Volo.Abp.TenantManagement;
using Volo.Abp.TenantManagement.EntityFrameworkCore;
using Volo.Abp.TenantManagement.Web;
+using Volo.Abp.Uow;
namespace OpenIddict.Demo.Server;
@@ -116,7 +117,7 @@ public class OpenIddictServerModule : AbpModule
PreConfigure(options =>
{
options.EnableWildcardDomainSupport = true;
- options.WildcardDomainsFormat.Add("https://{0}.abp.io/signin-oidc");
+ options.WildcardDomainsFormat.Add("https://*.abp.io");
});
PreConfigure(builder =>
@@ -155,8 +156,19 @@ public class OpenIddictServerModule : AbpModule
});
}
- public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
+ public async override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
+ using var uow = context.ServiceProvider.GetRequiredService().Begin();
+ {
+ var dbContext = await context.ServiceProvider.GetRequiredService>().GetDbContextAsync();
+ if ((await dbContext.Database.GetPendingMigrationsAsync()).Any())
+ {
+ await dbContext.Database.MigrateAsync();
+ }
+
+ await uow.CompleteAsync();
+ }
+
await context.ServiceProvider
.GetRequiredService()
.SeedAsync();
diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs
index c3ba2c6bce..ccecad07c1 100644
--- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs
+++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainBase.cs
@@ -1,10 +1,12 @@
-using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
using OpenIddict.Server;
-using Volo.Abp.Text.Formatting;
+using Volo.Abp.Http;
namespace Volo.Abp.OpenIddict.WildcardDomains;
@@ -29,30 +31,24 @@ public abstract class AbpOpenIddictWildcardDomainBase CheckWildcardDomainAsync(string url)
{
- Logger.LogDebug("Checking wildcard domain for url: {url}", url);
-
- foreach (var domainFormat in WildcardDomainOptions.WildcardDomainsFormat)
+ if (WildcardDomainOptions.WildcardDomainsFormat.IsNullOrEmpty())
{
- Logger.LogDebug("Checking wildcard domain format: {domainFormat}", domainFormat);
- var extractResult = FormattedStringValueExtracter.Extract(url, domainFormat, ignoreCase: true);
- if (extractResult.IsMatch)
- {
- Logger.LogDebug("Wildcard domain found for url: {url}", url);
- return Task.FromResult(true);
- }
+ Logger.LogDebug("No wildcard domain format configured.");
+ return Task.FromResult(false);
}
- foreach (var domainFormat in WildcardDomainOptions.WildcardDomainsFormat)
+ Logger.LogDebug("Checking wildcard domain for url: {url}", url);
+ foreach (var domain in WildcardDomainOptions.WildcardDomainsFormat.Select(domainFormat => domainFormat.Replace("{0}", "*")))
{
- Logger.LogDebug("Checking wildcard domain format: {domainFormat}", domainFormat);
- if (domainFormat.Replace("{0}.", "").Equals(url, StringComparison.OrdinalIgnoreCase))
+ Logger.LogDebug("Checking wildcard domain format: {domain}", domain);
+ if (UrlHelpers.IsSubdomainOf(url, domain))
{
- Logger.LogDebug("Wildcard domain found for url: {url}", url);
+ Logger.LogDebug("The url: {url} is a wildcard domain of: {domain}", url, domain);
return Task.FromResult(true);
}
}
- Logger.LogDebug("Wildcard domain not found for url: {url}", url);
+ Logger.LogDebug("No wildcard domain found for url: {url}", url);
return Task.FromResult(false);
}
}
diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainOptions.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainOptions.cs
index 358974e3d6..54c56178f9 100644
--- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainOptions.cs
+++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/WildcardDomains/AbpOpenIddictWildcardDomainOptions.cs
@@ -6,6 +6,9 @@ public class AbpOpenIddictWildcardDomainOptions
{
public bool EnableWildcardDomainSupport { get; set; }
+ ///
+ /// Wildcard domains format. For example: https://*.abp.io
+ ///
public HashSet WildcardDomainsFormat { get; }
public AbpOpenIddictWildcardDomainOptions()
diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/OpenIddictAuthorization.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/OpenIddictAuthorization.cs
index 76ea7d5d51..25870174ff 100644
--- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/OpenIddictAuthorization.cs
+++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Authorizations/OpenIddictAuthorization.cs
@@ -1,10 +1,10 @@
using System;
-using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.Domain.Entities;
using Volo.Abp.Timing;
namespace Volo.Abp.OpenIddict.Authorizations;
-public class OpenIddictAuthorization : FullAuditedAggregateRoot
+public class OpenIddictAuthorization : AggregateRoot
{
public OpenIddictAuthorization()
{
diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/OpenIddictToken.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/OpenIddictToken.cs
index 76d76c4bfc..e08bd4dc23 100644
--- a/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/OpenIddictToken.cs
+++ b/modules/openiddict/src/Volo.Abp.OpenIddict.Domain/Volo/Abp/OpenIddict/Tokens/OpenIddictToken.cs
@@ -1,10 +1,10 @@
using System;
-using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.Domain.Entities;
using Volo.Abp.Timing;
namespace Volo.Abp.OpenIddict.Tokens;
-public class OpenIddictToken : FullAuditedAggregateRoot
+public class OpenIddictToken : AggregateRoot
{
public OpenIddictToken()
{
diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.Domain.Tests/Volo/Abp/OpenIddict/Tokens/AbpOpenIddictTokenStore_Tests.cs b/modules/openiddict/test/Volo.Abp.OpenIddict.Domain.Tests/Volo/Abp/OpenIddict/Tokens/AbpOpenIddictTokenStore_Tests.cs
index 94e5d5710d..feaa7b38a4 100644
--- a/modules/openiddict/test/Volo.Abp.OpenIddict.Domain.Tests/Volo/Abp/OpenIddict/Tokens/AbpOpenIddictTokenStore_Tests.cs
+++ b/modules/openiddict/test/Volo.Abp.OpenIddict.Domain.Tests/Volo/Abp/OpenIddict/Tokens/AbpOpenIddictTokenStore_Tests.cs
@@ -19,7 +19,7 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
_tokenStore = ServiceProvider.GetRequiredService>();
_testData = ServiceProvider.GetRequiredService();
}
-
+
[Fact]
public async Task CountAsync()
{
@@ -30,18 +30,18 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
[Fact]
public async Task CreateAsync()
{
- await _tokenStore.CreateAsync(new OpenIddictTokenModel
+ await _tokenStore.CreateAsync(new OpenIddictTokenModel
{
ApplicationId = _testData.App1Id,
Payload = "TestPayload3",
Subject = "TestSubject3",
Type = "TestType3",
Status = OpenIddictConstants.Statuses.Inactive,
-
+
}, CancellationToken.None);
var tokens = await _tokenStore.FindBySubjectAsync("TestSubject3", CancellationToken.None).ToListAsync();
-
+
tokens.Count.ShouldBe(1);
var token = tokens.First();
token.ApplicationId.ShouldBe(_testData.App1Id);
@@ -50,11 +50,12 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
token.Type.ShouldBe("TestType3");
token.Status.ShouldBe(OpenIddictConstants.Statuses.Inactive);
}
-
+
[Fact]
public async Task DeleteAsync()
{
var token = await _tokenStore.FindByIdAsync(_testData.Token1Id.ToString(), CancellationToken.None);
+ token.ShouldNotBeNull();
await _tokenStore.DeleteAsync(token, CancellationToken.None);
token = await _tokenStore.FindByIdAsync(_testData.Token1Id.ToString(), CancellationToken.None);
@@ -65,15 +66,15 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
public async Task FindAsync_Should_Return_Empty_If_Not_Found()
{
var tokens = await _tokenStore.FindAsync("non_existing_subject", _testData.App1Id.ToString(), "non_existing_status", "non_existing_type", CancellationToken.None).ToListAsync();
-
+
tokens.Count.ShouldBe(0);
}
-
+
[Fact]
public async Task FindAsync_Should_Return_Tokens_If_Found()
{
var tokens = await _tokenStore.FindAsync("TestSubject1", _testData.App1Id.ToString(),OpenIddictConstants.Statuses.Redeemed, "TestType1", CancellationToken.None).ToListAsync();
-
+
tokens.Count.ShouldBe(1);
}
@@ -81,15 +82,15 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
public async Task FindByApplicationIdAsync_Should_Return_Empty_If_Not_Found()
{
var tokens = await _tokenStore.FindByApplicationIdAsync(Guid.NewGuid().ToString(), CancellationToken.None).ToListAsync();
-
+
tokens.Count.ShouldBe(0);
}
-
+
[Fact]
public async Task FindByApplicationIdAsync_Should_Return_Tokens_If_Found()
{
var tokens = await _tokenStore.FindByApplicationIdAsync(_testData.App1Id.ToString(), CancellationToken.None).ToListAsync();
-
+
tokens.Count.ShouldBe(1);
}
@@ -121,7 +122,7 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
var token = await _tokenStore.FindByReferenceIdAsync(Guid.NewGuid().ToString(), CancellationToken.None);
token.ShouldBeNull();
}
-
+
[Fact]
public async Task FindByReferenceIdAsync_Should_Return_Token_If_Found()
{
@@ -145,7 +146,7 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
await _tokenStore.UpdateAsync(token, CancellationToken.None);
token = await _tokenStore.FindByIdAsync(_testData.Token1Id.ToString(), CancellationToken.None);
-
+
token.ApplicationId.ShouldBe(_testData.App2Id);
token.Payload.ShouldBe("New payload");
token.Subject.ShouldBe("New subject");
@@ -153,4 +154,4 @@ public class AbpOpenIddictTokenStore_Tests : OpenIddictDomainTestBase
token.Status.ShouldBe(OpenIddictConstants.Statuses.Revoked);
token.ExpirationDate.ShouldBe(now);
}
-}
\ No newline at end of file
+}
diff --git a/npm/packs/datatables.net-bs5/package.json b/npm/packs/datatables.net-bs5/package.json
index baa947afb7..f4e8ae27f2 100644
--- a/npm/packs/datatables.net-bs5/package.json
+++ b/npm/packs/datatables.net-bs5/package.json
@@ -6,7 +6,7 @@
},
"dependencies": {
"@abp/datatables.net": "~8.3.1",
- "datatables.net-bs5": "^2.0.8"
+ "datatables.net-bs5": "^2.1.4"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",
diff --git a/npm/packs/datatables.net/package.json b/npm/packs/datatables.net/package.json
index 902c43e585..e4e842135f 100644
--- a/npm/packs/datatables.net/package.json
+++ b/npm/packs/datatables.net/package.json
@@ -11,7 +11,7 @@
},
"dependencies": {
"@abp/jquery": "~8.3.1",
- "datatables.net": "^2.0.8"
+ "datatables.net": "^2.1.4"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",
diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.Designer.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.Designer.cs
similarity index 96%
rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.Designer.cs
rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.Designer.cs
index e4b566dd1d..2832a92de9 100644
--- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.Designer.cs
+++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.Designer.cs
@@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
{
[DbContext(typeof(MyProjectNameDbContext))]
- [Migration("20240923044653_Initial")]
+ [Migration("20241002093338_Initial")]
partial class Initial
{
///
@@ -657,6 +657,13 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .IsRequired()
+ .HasMaxLength(40)
+ .HasColumnType("nvarchar(40)")
+ .HasColumnName("ConcurrencyStamp");
+
b.Property("Device")
.IsRequired()
.HasMaxLength(64)
@@ -666,9 +673,14 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
+ b.Property("ExtraProperties")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasColumnName("ExtraProperties");
+
b.Property("IpAddresses")
- .HasMaxLength(256)
- .HasColumnType("nvarchar(256)");
+ .HasMaxLength(2048)
+ .HasColumnType("nvarchar(2048)");
b.Property("LastAccessed")
.HasColumnType("datetime2");
@@ -1253,41 +1265,11 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExtraProperties")
.IsRequired()
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Properties")
.HasColumnType("nvarchar(max)");
@@ -1412,22 +1394,6 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
b.Property("CreationDate")
.HasColumnType("datetime2");
- b.Property("CreationTime")
- .HasColumnType("datetime2")
- .HasColumnName("CreationTime");
-
- b.Property("CreatorId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("CreatorId");
-
- b.Property("DeleterId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("DeleterId");
-
- b.Property("DeletionTime")
- .HasColumnType("datetime2")
- .HasColumnName("DeletionTime");
-
b.Property("ExpirationDate")
.HasColumnType("datetime2");
@@ -1436,20 +1402,6 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
- b.Property("IsDeleted")
- .ValueGeneratedOnAdd()
- .HasColumnType("bit")
- .HasDefaultValue(false)
- .HasColumnName("IsDeleted");
-
- b.Property("LastModificationTime")
- .HasColumnType("datetime2")
- .HasColumnName("LastModificationTime");
-
- b.Property("LastModifierId")
- .HasColumnType("uniqueidentifier")
- .HasColumnName("LastModifierId");
-
b.Property("Payload")
.HasColumnType("nvarchar(max)");
diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.cs
similarity index 97%
rename from templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.cs
rename to templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.cs
index 39c3b550b9..768e49055a 100644
--- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20240923044653_Initial.cs
+++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Migrations/20241002093338_Initial.cs
@@ -266,9 +266,11 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
TenantId = table.Column(type: "uniqueidentifier", nullable: true),
UserId = table.Column(type: "uniqueidentifier", nullable: false),
ClientId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true),
- IpAddresses = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true),
+ IpAddresses = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true),
SignedIn = table.Column(type: "datetime2", nullable: false),
- LastAccessed = table.Column(type: "datetime2", nullable: true)
+ LastAccessed = table.Column(type: "datetime2", nullable: true),
+ ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false),
+ ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false)
},
constraints: table =>
{
@@ -698,14 +700,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true),
Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false),
- ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false),
- CreationTime = table.Column(type: "datetime2", nullable: false),
- CreatorId = table.Column(type: "uniqueidentifier", nullable: true),
- LastModificationTime = table.Column(type: "datetime2", nullable: true),
- LastModifierId = table.Column(type: "uniqueidentifier", nullable: true),
- IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false),
- DeleterId = table.Column(type: "uniqueidentifier", nullable: true),
- DeletionTime = table.Column(type: "datetime2", nullable: true)
+ ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false)
},
constraints: table =>
{
@@ -757,14 +752,7 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Migrations
Subject = table.Column(type: "nvarchar(400)", maxLength: 400, nullable: true),
Type = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true),
ExtraProperties = table.Column(type: "nvarchar(max)", nullable: false),
- ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false),
- CreationTime = table.Column(type: "datetime2", nullable: false),
- CreatorId = table.Column(type: "uniqueidentifier", nullable: true),
- LastModificationTime = table.Column(type: "datetime2", nullable: true),
- LastModifierId = table.Column(type: "uniqueidentifier", nullable: true),
- IsDeleted = table.Column