diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml
index e38c381c88..d1eba17bf4 100644
--- a/.github/workflows/auto-pr.yml
+++ b/.github/workflows/auto-pr.yml
@@ -1,13 +1,13 @@
-name: Merge branch dev with rel-8.1
+name: Merge branch dev with prerel-8.2
on:
push:
branches:
- - rel-8.1
+ - prerel-8.2
permissions:
contents: read
jobs:
- merge-dev-with-rel-8-1:
+ merge-dev-with-prerel-8-2:
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
@@ -18,19 +18,19 @@ jobs:
ref: dev
- name: Reset promotion branch
run: |
- git fetch origin rel-8.1:rel-8.1
- git reset --hard rel-8.1
+ git fetch origin prerel-8.2:prerel-8.2
+ git reset --hard prerel-8.2
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
- branch: auto-merge/rel-8-1/${{github.run_number}}
- title: Merge branch dev with rel-8.1
- body: This PR generated automatically to merge dev with rel-8.1. Please review the changed files before merging to prevent any errors that may occur.
+ branch: auto-merge/prerel-8-2/${{github.run_number}}
+ title: Merge branch dev with prerel-8.2
+ body: This PR generated automatically to merge dev with prerel-8.2. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming
token: ${{ github.token }}
- name: Merge Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
- gh pr review auto-merge/rel-8-1/${{github.run_number}} --approve
- gh pr merge auto-merge/rel-8-1/${{github.run_number}} --merge --auto --delete-branch
+ gh pr review auto-merge/prerel-8-2/${{github.run_number}} --approve
+ gh pr merge auto-merge/prerel-8-2/${{github.run_number}} --merge --auto --delete-branch
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1cb2ae0b88..016445c47d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -106,6 +106,7 @@
+
@@ -117,11 +118,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -143,7 +144,7 @@
-
+
@@ -173,4 +174,4 @@
-
+
\ No newline at end of file
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
index 1be237a210..3fc2d697ee 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
@@ -1210,7 +1210,7 @@
"RegisterDemo": "Register",
"TrainingDescription": "We are offering the following training packages for who want to get expertise on the ABP Framework and the ABP Commercial.",
"PurchaseDevelopers": "developers",
- "LinkExpiredMessage": "The payment link has expired. Please contact us and receive an updated payment link."
+ "LinkExpiredMessage": "The payment link has expired! Contact us at sales@volosoft.com to update the link or click here to navigate to the contact page."
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
index a2385bc03b..9282239bee 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
@@ -1140,6 +1140,6 @@
"RemoveBasket": "Sepetten kaldır",
"TrainingPack": "Eğitim paketi",
"TrainingPackDiscount": "Eğitim paketi indirimi",
- "LinkExpiredMessage": "Ödeme bağlantısı süresi doldu. Lütfen bizimle iletişime geçin ve güncel bir ödeme bağlantısı alın."
+ "LinkExpiredMessage": "Ödeme bağlantısının süresi doldu! Bağlantıyı güncellemek için sales@volosoft.com adresinden bizimle iletişime geçin veya iletişim sayfasına gitmek için buraya tıklayın."
}
-}
\ No newline at end of file
+}
diff --git a/common.props b/common.props
index fdee3ad9ac..2b87badaf8 100644
--- a/common.props
+++ b/common.props
@@ -1,8 +1,8 @@
latest
- 8.2.0-preview
- 3.2.0-preview
+ 8.3.0-preview
+ 3.3.0-preview$(NoWarn);CS1591;CS0436https://abp.io/assets/abp_nupkg.pnghttps://abp.io/
diff --git a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg
index 97231e1d87..b2d0a78ee4 100644
Binary files a/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg and b/docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/abp-studio-solution-runner.png b/docs/en/Blog-Posts/2024-05 ABP Unification/abp-studio-solution-runner.png
new file mode 100644
index 0000000000..2b5bc20678
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/abp-studio-solution-runner.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/cover.png b/docs/en/Blog-Posts/2024-05 ABP Unification/cover.png
new file mode 100644
index 0000000000..95e8363e67
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/cover.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/docs-new-navigation.png b/docs/en/Blog-Posts/2024-05 ABP Unification/docs-new-navigation.png
new file mode 100644
index 0000000000..ba1f421339
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/docs-new-navigation.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/docs-project-selection.png b/docs/en/Blog-Posts/2024-05 ABP Unification/docs-project-selection.png
new file mode 100644
index 0000000000..2e4dadafc6
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/docs-project-selection.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/new-get-started.png b/docs/en/Blog-Posts/2024-05 ABP Unification/new-get-started.png
new file mode 100644
index 0000000000..ed1483f343
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/new-get-started.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/new-mega-menu.png b/docs/en/Blog-Posts/2024-05 ABP Unification/new-mega-menu.png
new file mode 100644
index 0000000000..75c21a9693
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/new-mega-menu.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/new-pricing.png b/docs/en/Blog-Posts/2024-05 ABP Unification/new-pricing.png
new file mode 100644
index 0000000000..4d299da6ee
Binary files /dev/null and b/docs/en/Blog-Posts/2024-05 ABP Unification/new-pricing.png differ
diff --git a/docs/en/Blog-Posts/2024-05 ABP Unification/post.md b/docs/en/Blog-Posts/2024-05 ABP Unification/post.md
new file mode 100644
index 0000000000..9da50ac06b
--- /dev/null
+++ b/docs/en/Blog-Posts/2024-05 ABP Unification/post.md
@@ -0,0 +1,111 @@
+# Unifying the ABP.IO Platform
+
+I am very excited to announce that some big changes and improvements are coming to the ABP.IO Platform soon. In this post, I will explain the changes we are currently working on. Here, a brief list of these changes:
+
+* We are merging the subdomains of the ABP.IO Platform websites: Community.abp.io, commercial.abp.io, blog.abp.io, docs.abp.io websites and their contents are being merged into the main domain, abp.io.
+* ABP (open source) and ABP Commercial documents are being merged into a single documentation.
+* Introducing ABP Studio Community Edition.
+
+These changes won't effect the license conditions. The open source part will remain the same and the commercial license contents will also be the same. The aim of the changes is to make the platform more consistent, holistic, understandable and easy to start.
+
+Let's dive deep...
+
+## Merging the ABP.IO Websites
+
+ABP.IO website has many subdomains currently:
+
+* **abp.io**: Home page of the open source ABP Framework project.
+* **community.abp.io**: A website that community can share contents and we organize events.
+* **commercial.abp.io**: A website to promote and sell commercial ABP licenses which have pre-built modules, themes, tooling and support on top of the ABP Framework.
+* **docs.abp.io**: The technical documentation of the ABP Framework and ABP Commercial.
+* **blog.abp.io**: A blog website to announce the news on the platform.
+* **support.abp.io**: Premium support for the ABP Commercial customers.
+
+All these subdomains (except the support website for now) are being merged to the abp.io domain. All their contents and UI designs are being revised and enriched.
+
+Some fundamental purposes of that change are;
+
+* Making content more coherent and holistic,
+* Making the design more harmonious,
+* Making the contents of the old subdomains more visible and reachable,
+* Allow you to navigate through the web pages much easier,
+* Reducing duplications between different websites,
+
+I will highlight a few important changes in the next sections.
+
+### The New Mega Menu
+
+As I said above, the abp.io UI design is also being revised. One of the big revisions is the main menu. We are replacing the current main navigation by a mega menu as shown in the following figure:
+
+
+
+We believe that new mega menu will allow you to navigate through the web pages much easier.
+
+### The New Get Started Page
+
+We are constantly working to improve ABP's onboarding experience. With the new platform changes, we now offer ABP Studio as the starting point for the ABP Platform. You can still use the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) to created new ABP solutions, but the new ABP Studio makes it much easier and understandable. It also provides features to easily run and monitor your applications, even in the Community edition.
+
+
+
+You can easily download and install ABP Studio, login with your abp.io account and create your first ABP solution.
+
+### The New Pricing Page
+
+Since the [ABP Commercial website](https://commercial.abp.io/) has merged with the main website, you will see the *Pricing* page located on the main menu of the abp.io website. We have completely revised the design and content of this page to better reflect which features are open source and free, and what is included in the paid licenses.
+
+
+
+As mentioned above, all the free & open source features are still free & open source. In addition, we included the ABP Studio Community edition (will be explained below) to the free license.
+
+## Merging the ABP Platform Documentation
+
+Currently, ABP Framework (open source) and ABP Commercial [documents](https://docs.abp.io/) are completely separated. You can switch between them on the left side:
+
+
+
+Based on our and customers' experiences, there are some problems with that approach:
+
+* Getting started, development tutorials, release notes, road map and some other documents are duplicated (or very similar) among ABP Framework and ABP Commercial documents.
+* For ABP Commercial users, it is not clear if they also need to read the ABP Framework (open source) documentation or not. Also, when they read the framework document, some parts are different for ABP Commercial users, and it is also not clear in some cases.
+
+We are currently working to completely merge the ABP Framework (open source) and ABP Commercial documentation, remove duplications and revisit the contents. We will clearly indicate if a part of a document requires a paid license.
+
+The left navigation panel tree is also completely revisited and simplified:
+
+
+
+## The ABP Studio Community Edition
+
+[ABP Studio](https://docs.abp.io/en/commercial/latest/studio/index) is a cross-platform desktop application designed for ABP and .NET developers. It aims to provide a comfortable development environment by automating tasks, providing insights about your solution, and simplifying the processes of creation, development, execution, browsing, monitoring, tracing, and deploying your solutions.
+
+Here, a screenshot from the *Solution Runner* screen of ABP Studio:
+
+
+
+ABP Studio has been started as a commercial product, as a part of [ABP Commercial](https://commercial.abp.io/). We are very excited to announce that the *Community Edition* will be available soon for free. It will have some missing features and limitations compared to the full edition, but will be enough to create, explore and run ABP solutions easily.
+
+We will be offering ABP Studio as a starting point to the ABP platform. The [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) and other documents will use ABP Studio to create new solutions and perform ABP-related operations.
+
+## Other News
+
+We are also working on some other topics related to these changes. Some of them are;
+
+* Completely renewing the [startup templates](https://docs.abp.io/en/abp/latest/Startup-Templates/Index) (with ABP Studio), so they will be more flexible and will provide more options.
+* Providing a tool to automatically convert ABP solutions created with open source startup templates into ABP commercial.
+
+## Questions
+
+I tried to explain all the important changes in this post. However, you may have some questions in your mind.
+
+### What should open source users expect?
+
+Since the [ABP Commercial](https://commercial.abp.io/) website content is merged with the main [abp.io](https://abp.io/) website, you will see paid features being introduced on the main website. The pricing page will also be available on the same website. This may lead you to wonder whether the ABP Platform is a fully paid product. The simple answer to this question is "No". Actually, nothing has changed on the open source side. Everything will be the same. Additionally, open source users will now have ABP Studio Community Edition for free. So open source has more for its users than before.
+
+### What should ABP Commercial customers expect?
+
+ABP Commercial license holders may wonder if any license change happens. The answer is "No". All the license types, rules, restrictions and features are the same. With the changes explained in this post, you will follow the documentation easier (since you won't need to go to another website for the framework documentation) and you will better understand what special features are available to you.
+
+## Last Words
+
+With this post, we wanted to announce the changes to be made on the ABP platform to the ABP community, so don't be surprised or curious about what happened. If you have any questions or suggestions, feel free to write a comment for this blog post or send an email to info@abp.io.
+
diff --git a/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md b/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md
index df4265a4a0..d87ee8cc48 100644
--- a/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md
+++ b/docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md
@@ -20,21 +20,22 @@ The contents of `BookStore.Blazor.Tests.csproj`
- net6.0
+ net8.0enable
+
false
-
-
-
-
-
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitiveall
-
+ runtime; build; native; contentfiles; analyzers; buildtransitiveall
@@ -46,6 +47,7 @@ The contents of `BookStore.Blazor.Tests.csproj`
+
```
Create `BookStoreBlazorTestModule` that depends on `AbpAspNetCoreComponentsModule` and `BookStoreEntityFrameworkCoreTestModule`.
@@ -63,7 +65,7 @@ public class BookStoreBlazorTestModule : AbpModule
Create a `BookStoreBlazorTestBase` class and add the `CreateTestContext` method. The `CreateTestContext` have key code.
-It uses ABP's `ServiceProvider` as a fallback `ServiceProvider` and add all ABP's services to the `TestContext`.
+It creates a `AutofacServiceProvider` and add all ABP's services to the `TestContext`.
```cs
public abstract class BookStoreBlazorTestBase : BookStoreTestBase
@@ -71,12 +73,23 @@ public abstract class BookStoreBlazorTestBase : BookStoreTestBase().Services)
+ var blazorise = testContext.JSInterop.SetupModule("./_content/Blazorise/utilities.js?v=1.5.1.0");
+ blazorise.SetupVoid("log", _ => true);
+
+ testContext.Services.UseServiceProviderFactory(serviceCollection =>
{
- testContext.Services.Add(service);
- }
+ foreach (var service in ServiceProvider.GetRequiredService().Services)
+ {
+ serviceCollection.Add(service);
+ }
+ var containerBuilder = new ContainerBuilder();
+ containerBuilder.Populate(serviceCollection);
+ return new AutofacServiceProvider(containerBuilder.Build());
+ });
+
testContext.Services.AddBlazorise().AddBootstrap5Providers().AddFontAwesomeIcons();
+ testContext.Services.Replace(ServiceDescriptor.Transient());
+
return testContext;
}
}
@@ -87,19 +100,18 @@ Finally, we add an `Index_Tests` class to test the `Index` component.
```cs
public class Index_Tests : BookStoreBlazorTestBase
{
- [Fact]
+[Fact]
public void Index_Test()
{
- using (var ctx = CreateTestContext())
- {
- // Act
- var cut = ctx.RenderComponent();
+ // Arrange
+ var ctx = CreateTestContext();
- // Assert
- cut.Find(".lead").InnerHtml.Contains("Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.").ShouldBeTrue();
+ // Act
+ var cut = ctx.RenderComponent();
- cut.Find("#username").InnerHtml.Contains("Welcome admin").ShouldBeTrue();
- }
+ // Assert
+ cut.Find(".lead").InnerHtml.Contains("Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.").ShouldBeTrue();
+ cut.Find("#username").InnerHtml.Contains("Welcome admin").ShouldBeTrue();
}
}
```
diff --git a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg
index 97231e1d87..b2d0a78ee4 100644
Binary files a/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg and b/docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg differ
diff --git a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png
index 37b93c5e3e..a8c8cbe8b6 100644
Binary files a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png and b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png differ
diff --git a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png
index 6223a8630b..7cb02af550 100644
Binary files a/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png and b/docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png differ
diff --git a/docs/en/UI/Angular/Modifying-the-Menu.md b/docs/en/UI/Angular/Modifying-the-Menu.md
index c59ce0d650..7a9c9a1004 100644
--- a/docs/en/UI/Angular/Modifying-the-Menu.md
+++ b/docs/en/UI/Angular/Modifying-the-Menu.md
@@ -152,6 +152,24 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
export class AppModule {}
```
+### Singularize Route Item
+- `name` property is must be a unique key. If there are multiple items with the same name, the last one will be displayed in the menu.
+- If you want to display multiple items in different parent with the same name, you can call the **setSingularizeStatus(false)** method of the `RoutesService` to disable the singularization.
+ - **This method should be called before adding the routes.**
+- To enable the singularization of the names, you can call the **setSingularizeStatus(true) `(default value: true)`** method of the `RoutesService`.
+
+```typescript
+import { RoutesService } from '@abp/ng.core';
+import { Component } from '@angular/core';
+
+@Component(/* component metadata */)
+export class AppComponent {
+ constructor(private routes: RoutesService) {
+ routes.setSingularizeStatus(false);
+ }
+}
+```
+
Here is what every property works as:
- `path` is the absolute path of the navigation element.
@@ -226,7 +244,7 @@ After adding the `routes` property as described above, the navigation menu looks
## How to Patch or Remove a Navigation Element
-The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children.
+The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. Also you can use `removeByParam` method to delete the routes with given properties.
```js
// this.routes is instance of RoutesService
@@ -249,6 +267,9 @@ const newHomeRouteConfig: Partial = {
this.routes.add([dashboardRouteConfig]);
this.routes.patch('::Menu:Home', newHomeRouteConfig);
this.routes.remove(['Your navigation']);
+
+// or
+this.routes.removeByParam({ name: 'Your navigation' });
```
- Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`.
diff --git a/docs/en/UI/Angular/Router-Events.md b/docs/en/UI/Angular/Router-Events.md
index 1d36c3a211..817667cfb9 100644
--- a/docs/en/UI/Angular/Router-Events.md
+++ b/docs/en/UI/Angular/Router-Events.md
@@ -2,9 +2,6 @@
`RouterEvents` is a utility service for filtering specific router events and reacting to them. Please see [this page in Angular docs](https://angular.io/api/router/Event) for available router events.
-
-
-
## Benefit
You can use router events directly and filter them as seen below:
@@ -50,14 +47,10 @@ class SomeService {
`RouterEvents` also delivers improved type-safety. In the example above, `navigationFinish$` has inferred type of `Observable` whereas it would have `Observable` when router events are filtered directly.
-
-
-
## Usage
You do not have to provide `RouterEvents` at the module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components.
-
### How to Get Specific Navigation Events
You can use `getNavigationEvents` to get a stream of navigation events matching given event keys.
@@ -85,7 +78,6 @@ class SomeService {
}
```
-
### How to Get All Navigation Events
You can use `getAllNavigationEvents` to get a stream of all navigation events without passing any keys.
@@ -108,6 +100,30 @@ class SomeService {
}
```
+### How to get Current and Previous Navigation
+
+You can use `previousNavigation` and `currentNavigation` properties to retrieve navigations in a reactive way.
+
+```ts
+previousNavigation: Signal;
+currentNavigation: Signal;
+```
+
+```ts
+import { RouterEvents } from "@abp/ng.core";
+
+@Injectable()
+class SomeService {
+ readonly routerEvents = inject(RouterEvents);
+
+ someAction() {
+ const previousNavUrl = this.routerEvents.previousNavigation();
+ if (previousNavUrl) {
+ // perform some action
+ }
+ }
+}
+```
### How to Get Specific Router Events
@@ -126,7 +142,6 @@ class SomeService {
}
```
-
### How to Get All Router Events
You can use `getEvents` to get a stream of all router events without passing any event constructors. This is nothing different from accessing `events` property of `Router` and is added to the service just for convenience.
@@ -143,4 +158,3 @@ class SomeService {
constructor(private routerEvents: RouterEvents) {}
}
```
-
diff --git a/docs/zh-Hans/Timing.md b/docs/zh-Hans/Timing.md
index 1d220a7aad..4cede317cf 100644
--- a/docs/zh-Hans/Timing.md
+++ b/docs/zh-Hans/Timing.md
@@ -76,7 +76,7 @@ var normalizedDateTime = Clock.Normalize(dateTime)
`Normalize` 方法的工作原理如下:
* 如果当前时钟为UTC,并且给定的 `DateTime` 为本地时间,将给定的 `DateTime` 转换为UTC(通过使用 `DateTime.ToUniversalTime()` 方法).
-* 如果当前时钟是本地的,并且给定的 `DateTime` 是UTC,将给定的 `DateTime` 转换为本地时间(通过使用 `DateTime.ToUniversalTime()` 方法).
+* 如果当前时钟是本地的,并且给定的 `DateTime` 是UTC,将给定的 `DateTime` 转换为本地时间(通过使用 `DateTime.ToLocalTime()` 方法).
* 如果未指定给定的 `DateTime` 的 `Kind`,将给定的 `DateTime` 的 `Kind`(使用 `DateTime.SpecifyKind(...)` 方法)设置为当前时钟的 `Kind`.
当获取的 `DateTime` 不是由 `IClock` 创建且可能与当前Clock类型不兼容的时候,ABP框架会使用 `Normalize` 方法. 例如;
diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
index d43da06dde..4d7b852b9c 100644
--- a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
+++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
@@ -23,6 +23,9 @@
+
+
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj
index 702b1e2ed5..4542fc4cb5 100644
--- a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj
+++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj
@@ -16,6 +16,7 @@
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
index d99dc6284d..e518b54a95 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
@@ -15,5 +15,6 @@ public class BlazorGlobalScriptContributor : BundleContributor
context.Files.AddIfNotContains("/_framework/blazor.server.js");
}
context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js");
+ context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js");
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs
index e2a0617b90..89bb84c9da 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs
@@ -1,4 +1,5 @@
-using Volo.Abp.BlazoriseUI;
+using Volo.Abp.AspNetCore.Components.Web.Security;
+using Volo.Abp.BlazoriseUI;
using Volo.Abp.Modularity;
using Volo.Abp.UI.Navigation;
@@ -10,5 +11,11 @@ namespace Volo.Abp.AspNetCore.Components.Web.Theming;
)]
public class AbpAspNetCoreComponentsWebThemingModule : AbpModule
{
-
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.Components.Add(typeof(AbpAuthenticationState), null);
+ });
+ }
}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs
new file mode 100644
index 0000000000..e1a06c72b1
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs
@@ -0,0 +1,8 @@
+namespace Volo.Abp.AspNetCore.Components.Web;
+
+public class AbpAuthenticationOptions
+{
+ public string LoginUrl { get; set; } = "Account/Login";
+
+ public string LogoutUrl { get; set; } = "Account/Logout";
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs
new file mode 100644
index 0000000000..f22e392732
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.AspNetCore.Components.Web;
+
+public interface ILocalStorageService
+{
+ public ValueTask SetItemAsync(string key, string value);
+ public ValueTask GetItemAsync(string key);
+ public ValueTask RemoveItemAsync(string key);
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs
new file mode 100644
index 0000000000..d49e85ec5f
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs
@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+using Microsoft.JSInterop;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.AspNetCore.Components.Web;
+
+[Dependency(ReplaceServices = true)]
+public class LocalStorageService : ILocalStorageService, ITransientDependency
+{
+ public IJSRuntime JsRuntime { get; }
+
+ public LocalStorageService(IJSRuntime jsRuntime)
+ {
+ JsRuntime = jsRuntime;
+ }
+
+ public async ValueTask SetItemAsync(string key, string value)
+ {
+ await JsRuntime.InvokeVoidAsync("localStorage.setItem", key, value);
+ }
+
+ public async ValueTask GetItemAsync(string key)
+ {
+ return await JsRuntime.InvokeAsync("localStorage.getItem", key);
+ }
+
+ public async ValueTask RemoveItemAsync(string key)
+ {
+ await JsRuntime.InvokeVoidAsync("localStorage.removeItem", key);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs
new file mode 100644
index 0000000000..cc435ed54d
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.Extensions.Options;
+using Volo.Abp.Users;
+
+namespace Volo.Abp.AspNetCore.Components.Web.Security;
+
+public class AbpAuthenticationState : ComponentBase
+{
+ private const string StateKey = "authentication-state-id";
+
+ [Inject]
+ protected ILocalStorageService LocalStorage { get; set; } = default!;
+
+ [Inject]
+ protected ICurrentUser CurrentUser { get; set; } = default!;
+
+ [Inject]
+ protected NavigationManager NavigationManager { get; set; } = default!;
+
+ [Inject]
+ protected IOptions AuthenticationOptions { get; set; } = default!;
+
+ protected async override Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender)
+ {
+ NavigationManager.RegisterLocationChangingHandler(OnLocationChangingAsync);
+ if (CurrentUser.IsAuthenticated)
+ {
+ await SetAuthenticationStateAsync();
+ }
+ else
+ {
+ await ClearAuthenticationStateAsync();
+ }
+ }
+ }
+
+ protected virtual async ValueTask OnLocationChangingAsync(LocationChangingContext context)
+ {
+ if (context.TargetLocation.Contains(AuthenticationOptions.Value.LogoutUrl))
+ {
+ await ClearAuthenticationStateAsync();
+ }
+ }
+
+ protected virtual async Task SetAuthenticationStateAsync()
+ {
+ await LocalStorage.SetItemAsync(StateKey, CurrentUser.GetId().ToString());
+ }
+
+ protected virtual async Task ClearAuthenticationStateAsync()
+ {
+ await LocalStorage.RemoveItemAsync(StateKey);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js b/framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js
new file mode 100644
index 0000000000..47f8c1fc81
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js
@@ -0,0 +1,17 @@
+(function () {
+
+ const stateKey = 'authentication-state-id';
+
+ window.addEventListener('storage', function (event) {
+ if (event.key !== stateKey || event.oldValue === event.newValue) {
+ return;
+ }
+
+ if (event.oldValue || !event.newValue) {
+ abp.utils.removeOidcUser();
+ window.location.reload();
+ } else {
+ location.assign('/')
+ }
+ });
+}());
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
index 942edd473c..d9b5a90e6a 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
@@ -9,6 +9,8 @@ public class ComponentsComponentsBundleContributor : IBundleContributor
context.Add("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js");
context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
+ context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js");
+ context.Add("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js");
}
public void AddStyles(BundleContext context)
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
index 268d6a2dc7..25a57616e4 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
+using Volo.Abp.AspNetCore.Components.Server;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
using Volo.Abp.AspNetCore.Components.Web.Security;
@@ -49,6 +50,15 @@ public class AbpAspNetCoreComponentsWebAssemblyModule : AbpModule
context.Services
.GetHostBuilder().Logging
.AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services));
+
+ if (!context.Services.ExecutePreConfiguredActions().IsBlazorWebApp)
+ {
+ Configure(options =>
+ {
+ options.LoginUrl = "authentication/login";
+ options.LogoutUrl = "authentication/logout";
+ });
+ }
}
public override void PostConfigureServices(ServiceConfigurationContext context)
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
index 7a5646ebae..81f3357bfb 100644
--- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
@@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using Volo.Abp.AspNetCore.Components.Server;
+using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.Http;
@@ -23,32 +24,46 @@ public class ClientProxyExceptionEventHandler : ILocalEventHandler>();
- if (!options.Value.IsBlazorWebApp)
+ case 401:
{
- var navigationManager = scope.ServiceProvider.GetRequiredService();
- var accessTokenProvider = scope.ServiceProvider.GetRequiredService();
- var result = await accessTokenProvider.RequestAccessToken();
- if (result.Status != AccessTokenResultStatus.Success)
+ var options = scope.ServiceProvider.GetRequiredService>();
+
+ if (!options.Value.IsBlazorWebApp)
{
- navigationManager.NavigateToLogout("authentication/logout");
- return;
- }
+ var navigationManager = scope.ServiceProvider.GetRequiredService();
+ var accessTokenProvider = scope.ServiceProvider.GetRequiredService();
+ var authenticationOptions = scope.ServiceProvider.GetRequiredService>();
+ var result = await accessTokenProvider.RequestAccessToken();
+ if (result.Status != AccessTokenResultStatus.Success)
+ {
+ navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl);
+ return;
+ }
- result.TryGetToken(out var token);
- if (token != null && DateTimeOffset.Now >= token.Expires.AddMinutes(-5))
+ result.TryGetToken(out var token);
+ if (token != null && DateTimeOffset.Now >= token.Expires.AddMinutes(-5))
+ {
+ navigationManager.NavigateToLogout(authenticationOptions.Value.LogoutUrl);
+ }
+ }
+ else
{
- navigationManager.NavigateToLogout("authentication/logout");
+ var jsRuntime = scope.ServiceProvider.GetRequiredService();
+ await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
}
+
+ break;
}
- else
+ case 403:
{
var jsRuntime = scope.ServiceProvider.GetRequiredService();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
+
+ break;
}
}
}
diff --git a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs
index baac1b538f..c7b8c029cf 100644
--- a/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs
+++ b/framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs
@@ -13,7 +13,9 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async ValueTask