diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml
index f916789293..80d0acd30f 100644
--- a/.github/workflows/auto-pr.yml
+++ b/.github/workflows/auto-pr.yml
@@ -1,4 +1,4 @@
-name: Merge branch dev with rel-10.0
+name: Merge branch rel-10.1 with rel-10.0
on:
push:
branches:
@@ -7,7 +7,7 @@ permissions:
contents: read
jobs:
- merge-dev-with-rel-10-0:
+ merge-rel-10-1-with-rel-10-0:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
@@ -15,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
- ref: dev
+ ref: rel-10.1
- name: Reset promotion branch
run: |
git fetch origin rel-10.0:rel-10.0
@@ -24,8 +24,8 @@ jobs:
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-10-0/${{github.run_number}}
- title: Merge branch dev with rel-10.0
- body: This PR generated automatically to merge dev with rel-10.0. Please review the changed files before merging to prevent any errors that may occur.
+ title: Merge branch rel-10.1 with rel-10.0
+ body: This PR generated automatically to merge rel-10.1 with rel-10.0. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming
draft: true
token: ${{ github.token }}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index a568b976e1..453ddc618f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -7,7 +7,6 @@
-
@@ -141,7 +140,7 @@
-
+
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index ffcaf9b805..b59620e72b 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -1857,6 +1857,10 @@
"text": "Overriding the User Interface",
"path": "framework/architecture/modularity/extending/overriding-user-interface.md"
},
+ {
+ "text": "How to Override LeptonX CSS Variables",
+ "path": "framework/ui/common/leptonx-css-variables.md"
+ },
{
"text": "Utilities",
"items": [
diff --git a/docs/en/framework/ui/common/leptonx-css-variables.md b/docs/en/framework/ui/common/leptonx-css-variables.md
new file mode 100644
index 0000000000..193246a420
--- /dev/null
+++ b/docs/en/framework/ui/common/leptonx-css-variables.md
@@ -0,0 +1,119 @@
+# LeptonX CSS Variables Documentation
+
+LeptonX uses CSS custom properties (variables) prefixed with `--lpx-*` to provide a flexible theming system. These variables control colors, spacing, shadows, and component-specific styles throughout the application.
+
+## Brand & Semantic Colors
+
+| Variable | Description |
+|----------|-------------|
+| `--lpx-brand` | Brand-specific accent color |
+| `--lpx-brand-text` | Text color used on brand-colored backgrounds |
+
+## Base Colors
+
+| Variable | Description |
+|----------|-------------|
+| `--lpx-light` | Light shade for subtle backgrounds or text |
+| `--lpx-dark` | Dark shade for contrasting elements |
+
+## Layout & Surface Colors
+
+| Variable | Description |
+|----------|-------------|
+| `--lpx-content-bg` | Main content area background color |
+| `--lpx-content-text` | Default text color for content areas |
+| `--lpx-card-bg` | Card component background color |
+| `--lpx-card-title-text-color` | Card title text color |
+| `--lpx-border-color` | Default border color for dividers and outlines |
+| `--lpx-shadow` | Box shadow definition for elevated elements |
+
+## Navigation
+
+| Variable | Description |
+|----------|-------------|
+| `--lpx-navbar-color` | Navbar background color |
+| `--lpx-navbar-text-color` | Navbar default text/icon color |
+| `--lpx-navbar-active-text-color` | Navbar active/hover text color |
+| `--lpx-navbar-active-bg-color` | Navbar active item background color |
+
+## Utility
+
+| Variable | Description |
+|----------|-------------|
+| `--lpx-radius` | Global border-radius value for rounded corners |
+
+## Global Override
+
+Applies to all themes and pages:
+
+```css
+:root {
+ /* Brand & Semantic */
+ --lpx-brand: #f72585;
+
+ /* Base Colors */
+ --lpx-light: #f5f7fb;
+ --lpx-dark: #0b0f19;
+
+ /* Layout & Surface */
+ --lpx-content-bg: #101018;
+ --lpx-content-text: #cfd6e4;
+ --lpx-card-bg: #151a2b;
+ --lpx-card-title-text-color: #ffffff;
+ --lpx-border-color: #242836;
+ --lpx-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+
+ /* Navigation */
+ --lpx-navbar-color: #0d1020;
+ --lpx-navbar-text-color: #aab2c8;
+ --lpx-navbar-active-text-color: #ffffff;
+ --lpx-navbar-active-bg-color: rgba(247, 37, 133, 0.15);
+
+ /* Utility */
+ --lpx-radius: 10px;
+}
+
+```
+
+## Theme-Scoped Override
+
+Applies only when a specific theme class is active (e.g., `.lpx-theme-dark` on `` or `
`):
+
+```css
+:root .lpx-theme-dark {
+ /* Brand & Semantic */
+ --lpx-brand: #4dd0e1;
+
+ /* Base Colors */
+ --lpx-light: #e0f7fa;
+ --lpx-dark: #020617;
+
+ /* Layout & Surface */
+ --lpx-content-bg: #0b1118;
+ --lpx-content-text: #c7d0e0;
+ --lpx-card-bg: #111a24;
+ --lpx-card-title-text-color: #e6f1ff;
+ --lpx-border-color: #1e2a3a;
+ --lpx-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
+
+ /* Navigation */
+ --lpx-navbar-color: #0f1a22;
+ --lpx-navbar-text-color: #9fb3c8;
+ --lpx-navbar-active-text-color: #ffffff;
+ --lpx-navbar-active-bg-color: rgba(77, 208, 225, 0.18);
+
+ /* Utility */
+ --lpx-radius: 12px;
+}
+```
+
+## Component/Page-Specific Override
+
+For targeted customizations that should only affect a specific section:
+
+```css
+.my-custom-page {
+ --lpx-brand: #e91e63;
+ --lpx-card-bg: #1a1a2e;
+}
+```
\ No newline at end of file
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png
index adebe653b5..94dfb63a56 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-opening.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png
index a0ecbd469d..e3032dfbde 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png and b/docs/en/tutorials/book-store-with-abp-suite/images/abp-suite-solution-test-projects-mongo.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png
index 687dca3593..5c4a377b4a 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-angular.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png
index 5535659b45..e690e9b0e9 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-blazor.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png
index 1bf3253fe7..961e7565a7 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mauiblazor.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png
index 54860a5e9f..7a2850afc2 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png and b/docs/en/tutorials/book-store-with-abp-suite/images/book-store-studio-run-app-mvc.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png b/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png
index 47a37bbd9b..bf1394a0a2 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png and b/docs/en/tutorials/book-store-with-abp-suite/images/studio-browser-suite.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png
index dbb1890c14..b1e4a7d06e 100644
Binary files a/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png and b/docs/en/tutorials/book-store-with-abp-suite/images/suite-book-entity-1.png differ
diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-02.md b/docs/en/tutorials/book-store-with-abp-suite/part-02.md
index 11725201cb..f9462b9130 100644
--- a/docs/en/tutorials/book-store-with-abp-suite/part-02.md
+++ b/docs/en/tutorials/book-store-with-abp-suite/part-02.md
@@ -96,7 +96,7 @@ Here is the all details for the `Book` entity:
* `Name` is **required**, it's a **string** property and maximum length is **128**.
* `Type` is an **enum** and the enum file path is *\Acme.BookStore.Domain.Shared\Books\BookType.cs*.
* `PublishDate` is a **DateTime** property and **not nullable**.
-* `Price` is a **float** property and **required**.
+* `Price` is a **float** property.
You can leave the other configurations as default.
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs
index ad2f6ba983..26e51dbd38 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.DependencyInjection;
@@ -16,7 +17,7 @@ public class MauiCurrentApplicationConfigurationCacheResetService :
_mauiBlazorCachedApplicationConfigurationClient = mauiBlazorCachedApplicationConfigurationClient;
}
- public async Task ResetAsync()
+ public async Task ResetAsync(Guid? userId = null)
{
await _mauiBlazorCachedApplicationConfigurationClient.InitializeAsync();
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs
index 02de9d8bf7..144d975cb5 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs
@@ -1,4 +1,5 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.DependencyInjection;
@@ -19,10 +20,8 @@ public class BlazorServerCurrentApplicationConfigurationCacheResetService :
_localEventBus = localEventBus;
}
- public async Task ResetAsync()
+ public async Task ResetAsync(Guid? userId = null)
{
- await _localEventBus.PublishAsync(
- new CurrentApplicationConfigurationCacheResetEventData()
- );
+ await _localEventBus.PublishAsync(new CurrentApplicationConfigurationCacheResetEventData(userId));
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs
index c3e33a9e41..2d44399f9d 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs
@@ -1,8 +1,9 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Configuration;
public interface ICurrentApplicationConfigurationCacheResetService
{
- Task ResetAsync();
+ Task ResetAsync(Guid? userId = null);
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs
index bb91d70775..1cfaee3315 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
@@ -5,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Components.Web.Configuration;
public class NullCurrentApplicationConfigurationCacheResetService : ICurrentApplicationConfigurationCacheResetService, ISingletonDependency
{
- public Task ResetAsync()
+ public Task ResetAsync(Guid? userId = null)
{
return Task.CompletedTask;
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs
index 40ac508030..359678daf4 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.DependencyInjection;
@@ -16,7 +17,7 @@ public class BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService :
_webAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient;
}
- public async Task ResetAsync()
+ public async Task ResetAsync(Guid? userId = null)
{
await _webAssemblyCachedApplicationConfigurationClient.InitializeAsync();
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs
index cc1180fd20..ea0d778fad 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs
@@ -1,13 +1,26 @@
-using System.Globalization;
-using Volo.Abp.Users;
+using System;
+using System.Globalization;
+using System.Threading.Tasks;
+using Volo.Abp.Caching;
+using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Client;
-public static class MvcCachedApplicationConfigurationClientHelper
+public class MvcCachedApplicationConfigurationClientHelper : ITransientDependency
{
- public static string CreateCacheKey(ICurrentUser currentUser)
+ protected IDistributedCache ApplicationVersionCache { get; }
+
+ public MvcCachedApplicationConfigurationClientHelper(IDistributedCache applicationVersionCache)
+ {
+ ApplicationVersionCache = applicationVersionCache;
+ }
+
+ public virtual async Task CreateCacheKeyAsync(Guid? userId)
{
- var userKey = currentUser.Id?.ToString("N") ?? "Anonymous";
- return $"ApplicationConfiguration_{userKey}_{CultureInfo.CurrentUICulture.Name}";
+ var appVersion = await ApplicationVersionCache.GetOrAddAsync(MvcCachedApplicationVersionCacheItem.CacheKey,
+ () => Task.FromResult(new MvcCachedApplicationVersionCacheItem(Guid.NewGuid().ToString("N")))) ??
+ new MvcCachedApplicationVersionCacheItem(Guid.NewGuid().ToString("N"));
+ var userKey = userId?.ToString("N") ?? "Anonymous";
+ return $"ApplicationConfiguration_{appVersion.Version}_{userKey}_{CultureInfo.CurrentUICulture.Name}";
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationVersionCacheItem.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationVersionCacheItem.cs
new file mode 100644
index 0000000000..1cd9990a44
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationVersionCacheItem.cs
@@ -0,0 +1,13 @@
+namespace Volo.Abp.AspNetCore.Mvc.Client;
+
+public class MvcCachedApplicationVersionCacheItem
+{
+ public const string CacheKey = "Mvc_Application_Version";
+
+ public string Version { get; set; }
+
+ public MvcCachedApplicationVersionCacheItem(string version)
+ {
+ Version = version;
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs
index 8d787bec65..ba42b55d18 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs
@@ -20,6 +20,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
protected IHttpClientFactory HttpClientFactory { get; }
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; }
protected IDistributedCache ApplicationConfigurationDtoCache { get; }
+ protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
protected ICurrentUser CurrentUser { get; }
public RemoteDynamicClaimsPrincipalContributorCache(
@@ -28,7 +29,8 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
IOptions abpClaimsPrincipalFactoryOptions,
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator,
IDistributedCache applicationConfigurationDtoCache,
- ICurrentUser currentUser)
+ ICurrentUser currentUser,
+ MvcCachedApplicationConfigurationClientHelper cacheHelper)
: base(abpClaimsPrincipalFactoryOptions)
{
Cache = cache;
@@ -36,6 +38,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
HttpClientAuthenticator = httpClientAuthenticator;
ApplicationConfigurationDtoCache = applicationConfigurationDtoCache;
CurrentUser = currentUser;
+ CacheHelper = cacheHelper;
}
protected async override Task GetCacheAsync(Guid userId, Guid? tenantId = null)
@@ -56,7 +59,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
catch (Exception e)
{
Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}");
- await ApplicationConfigurationDtoCache.RemoveAsync(MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser));
+ await ApplicationConfigurationDtoCache.RemoveAsync(await CacheHelper.CreateCacheKeyAsync(CurrentUser.Id));
throw;
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
index 0bff9d09b1..e3d3c12370 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
@@ -1,3 +1,4 @@
+using System;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
@@ -13,14 +14,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Client;
public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency
{
+ private const string ApplicationConfigurationDtoCacheKey = "ApplicationConfigurationDto_CacheKey";
+
protected IHttpContextAccessor HttpContextAccessor { get; }
protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; }
protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; }
protected ICurrentUser CurrentUser { get; }
+ protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
protected IDistributedCache Cache { get; }
protected AbpAspNetCoreMvcClientCacheOptions Options { get; }
public MvcCachedApplicationConfigurationClient(
+ MvcCachedApplicationConfigurationClientHelper cacheHelper,
IDistributedCache cache,
AbpApplicationConfigurationClientProxy applicationConfigurationAppService,
ICurrentUser currentUser,
@@ -33,13 +38,27 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
HttpContextAccessor = httpContextAccessor;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
Options = options.Value;
+ CacheHelper = cacheHelper;
Cache = cache;
}
- public async Task GetAsync()
+ public virtual async Task GetAsync()
{
- var cacheKey = CreateCacheKey();
+ string? cacheKey = null;
var httpContext = HttpContextAccessor?.HttpContext;
+ if (httpContext != null && httpContext.Items[ApplicationConfigurationDtoCacheKey] is string key)
+ {
+ cacheKey = key;
+ }
+
+ if (cacheKey.IsNullOrWhiteSpace())
+ {
+ cacheKey = await CreateCacheKeyAsync();
+ if (httpContext != null)
+ {
+ httpContext.Items[ApplicationConfigurationDtoCacheKey] = cacheKey;
+ }
+ }
if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration)
{
@@ -86,8 +105,21 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
public ApplicationConfigurationDto Get()
{
- var cacheKey = CreateCacheKey();
+ string? cacheKey = null;
var httpContext = HttpContextAccessor?.HttpContext;
+ if (httpContext != null && httpContext.Items[ApplicationConfigurationDtoCacheKey] is string key)
+ {
+ cacheKey = key;
+ }
+
+ if (cacheKey.IsNullOrWhiteSpace())
+ {
+ cacheKey = AsyncHelper.RunSync(CreateCacheKeyAsync);
+ if (httpContext != null)
+ {
+ httpContext.Items[ApplicationConfigurationDtoCacheKey] = cacheKey;
+ }
+ }
if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration)
{
@@ -97,8 +129,8 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
return AsyncHelper.RunSync(GetAsync);
}
- protected virtual string CreateCacheKey()
+ protected virtual async Task CreateCacheKeyAsync()
{
- return MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser);
+ return await CacheHelper.CreateCacheKeyAsync(CurrentUser.Id);
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs
index 8bd3971779..c32b63249c 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs
@@ -3,7 +3,6 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
-using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.Client;
@@ -11,23 +10,29 @@ public class MvcCurrentApplicationConfigurationCacheResetEventHandler :
ILocalEventHandler,
ITransientDependency
{
- protected ICurrentUser CurrentUser { get; }
protected IDistributedCache Cache { get; }
+ protected IDistributedCache ApplicationVersionCache { get; }
+ protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
- public MvcCurrentApplicationConfigurationCacheResetEventHandler(ICurrentUser currentUser,
- IDistributedCache cache)
+ public MvcCurrentApplicationConfigurationCacheResetEventHandler(
+ IDistributedCache cache,
+ IDistributedCache applicationVersionCache,
+ MvcCachedApplicationConfigurationClientHelper cacheHelper)
{
- CurrentUser = currentUser;
Cache = cache;
+ ApplicationVersionCache = applicationVersionCache;
+ CacheHelper = cacheHelper;
}
public virtual async Task HandleEventAsync(CurrentApplicationConfigurationCacheResetEventData eventData)
{
- await Cache.RemoveAsync(CreateCacheKey());
- }
-
- protected virtual string CreateCacheKey()
- {
- return MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser);
+ if (eventData.UserId.HasValue)
+ {
+ await Cache.RemoveAsync(await CacheHelper.CreateCacheKeyAsync(eventData.UserId));
+ }
+ else
+ {
+ await ApplicationVersionCache.RemoveAsync(MvcCachedApplicationVersionCacheItem.CacheKey);
+ }
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs
index a50cb7b136..fccc295429 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs
@@ -1,9 +1,21 @@
-namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
+using System;
+
+namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
///
/// This event is used to invalidate current user's cached configuration.
///
public class CurrentApplicationConfigurationCacheResetEventData
{
+ public Guid? UserId { get; set; }
+
+ public CurrentApplicationConfigurationCacheResetEventData()
+ {
+
+ }
+ public CurrentApplicationConfigurationCacheResetEventData(Guid? userId)
+ {
+ UserId = userId;
+ }
}
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs
new file mode 100644
index 0000000000..6bc7b5f165
--- /dev/null
+++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Volo.Abp.Authorization.Permissions;
+
+[Serializable]
+public class StaticPermissionDefinitionChangedEvent
+{
+
+}
diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
index 4e6ff0d11c..8329fde12e 100644
--- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
+++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs
@@ -6,44 +6,58 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
+using Volo.Abp.StaticDefinitions;
namespace Volo.Abp.Authorization.Permissions;
public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency
{
- protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value;
- private readonly Lazy> _lazyPermissionGroupDefinitions;
-
- protected IDictionary PermissionDefinitions => _lazyPermissionDefinitions.Value;
- private readonly Lazy> _lazyPermissionDefinitions;
-
+ protected IServiceProvider ServiceProvider { get; }
protected AbpPermissionOptions Options { get; }
-
- private readonly IServiceProvider _serviceProvider;
+ protected IStaticDefinitionCache> GroupCache { get; }
+ protected IStaticDefinitionCache> DefinitionCache { get; }
public StaticPermissionDefinitionStore(
IServiceProvider serviceProvider,
- IOptions options)
+ IOptions options,
+ IStaticDefinitionCache> groupCache,
+ IStaticDefinitionCache> definitionCache)
{
- _serviceProvider = serviceProvider;
+ ServiceProvider = serviceProvider;
Options = options.Value;
+ GroupCache = groupCache;
+ DefinitionCache = definitionCache;
+ }
- _lazyPermissionDefinitions = new Lazy>(
- CreatePermissionDefinitions,
- isThreadSafe: true
- );
+ public async Task GetOrNullAsync(string name)
+ {
+ var defs = await GetPermissionDefinitionsAsync();
+ return defs.GetOrDefault(name);
+ }
- _lazyPermissionGroupDefinitions = new Lazy>(
- CreatePermissionGroupDefinitions,
- isThreadSafe: true
- );
+ public virtual async Task> GetPermissionsAsync()
+ {
+ var defs = await GetPermissionDefinitionsAsync();
+ return defs.Values.ToImmutableList();
+ }
+
+ public async Task> GetGroupsAsync()
+ {
+ var groups = await GetPermissionGroupDefinitionsAsync();
+ return groups.Values.ToImmutableList();
+ }
+
+ protected virtual async Task> GetPermissionDefinitionsAsync()
+ {
+ return await DefinitionCache.GetOrCreateAsync(CreatePermissionDefinitionsAsync);
}
-
- protected virtual Dictionary CreatePermissionDefinitions()
+
+ protected virtual async Task> CreatePermissionDefinitionsAsync()
{
var permissions = new Dictionary();
- foreach (var groupDefinition in PermissionGroupDefinitions.Values)
+ var groups = await GetPermissionGroupDefinitionsAsync();
+ foreach (var groupDefinition in groups.Values)
{
foreach (var permission in groupDefinition.Permissions)
{
@@ -71,9 +85,14 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore,
}
}
- protected virtual Dictionary CreatePermissionGroupDefinitions()
+ protected virtual async Task> GetPermissionGroupDefinitionsAsync()
+ {
+ return await GroupCache.GetOrCreateAsync(CreatePermissionGroupDefinitionsAsync);
+ }
+
+ protected virtual Task> CreatePermissionGroupDefinitionsAsync()
{
- using (var scope = _serviceProvider.CreateScope())
+ using (var scope = ServiceProvider.CreateScope())
{
var context = new PermissionDefinitionContext(scope.ServiceProvider);
@@ -99,29 +118,10 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore,
context.CurrentProvider = provider;
provider.PostDefine(context);
}
-
+
context.CurrentProvider = null;
- return context.Groups;
+ return Task.FromResult(context.Groups);
}
}
-
- public Task GetOrNullAsync(string name)
- {
- return Task.FromResult(PermissionDefinitions.GetOrDefault(name));
- }
-
- public virtual Task> GetPermissionsAsync()
- {
- return Task.FromResult>(
- PermissionDefinitions.Values.ToImmutableList()
- );
- }
-
- public Task> GetGroupsAsync()
- {
- return Task.FromResult>(
- PermissionGroupDefinitions.Values.ToImmutableList()
- );
- }
-}
\ No newline at end of file
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
index be28f569ea..5ed5561e0e 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs
@@ -5,6 +5,7 @@ using Volo.Abp.Logging;
using Volo.Abp.Modularity;
using Volo.Abp.Reflection;
using Volo.Abp.SimpleStateChecking;
+using Volo.Abp.StaticDefinitions;
namespace Volo.Abp.Internal;
@@ -42,7 +43,7 @@ internal static class InternalServiceCollectionExtensions
services.AddAssemblyOf();
services.AddTransient(typeof(ISimpleStateCheckerManager<>), typeof(SimpleStateCheckerManager<>));
-
+ services.AddSingleton(typeof(IStaticDefinitionCache<,>), typeof(StaticDefinitionCache<,>));
services.Configure(options =>
{
options.Contributors.Add();
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs
new file mode 100644
index 0000000000..0f4bb5b5a8
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.StaticDefinitions;
+
+public interface IStaticDefinitionCache
+{
+ Task GetOrCreateAsync(Func> factory);
+
+ Task ClearAsync();
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs
new file mode 100644
index 0000000000..4f6a9bebe4
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.StaticDefinitions;
+
+public class StaticDefinitionCache : IStaticDefinitionCache
+{
+ private Lazy>? _lazy;
+
+ public virtual async Task GetOrCreateAsync(Func> factory)
+ {
+ var lazy = _lazy;
+ if (lazy != null)
+ {
+ return await lazy.Value;
+ }
+
+ var newLazy = new Lazy>(factory, LazyThreadSafetyMode.ExecutionAndPublication);
+ lazy = Interlocked.CompareExchange(ref _lazy, newLazy, null) ?? newLazy;
+
+ return await lazy.Value;
+ }
+
+ public virtual Task ClearAsync()
+ {
+ Interlocked.Exchange(ref _lazy, null);
+ return Task.CompletedTask;
+ }
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/KeyedLock.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/KeyedLock.cs
new file mode 100644
index 0000000000..7cab60c8d7
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/KeyedLock.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.Threading;
+
+///
+/// Per-key asynchronous lock for coordinating concurrent flows.
+///
+///
+/// Based on the pattern described in https://stackoverflow.com/a/31194647.
+/// Use within a using scope to ensure the lock is released via IDisposable.Dispose().
+///
+public static class KeyedLock
+{
+ private static readonly Dictionary