Browse Source

Merge branch 'dev' into salihozkara/abpUnited

pull/19876/head
Salih 2 years ago
parent
commit
aeaaef75ac
  1. 20
      .github/workflows/auto-pr.yml
  2. 15
      Directory.Packages.props
  3. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  4. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/tr.json
  5. 4
      common.props
  6. BIN
      docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg
  7. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/abp-studio-solution-runner.png
  8. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/cover.png
  9. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/docs-new-navigation.png
  10. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/docs-project-selection.png
  11. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/new-get-started.png
  12. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/new-mega-menu.png
  13. BIN
      docs/en/Blog-Posts/2024-05 ABP Unification/new-pricing.png
  14. 111
      docs/en/Blog-Posts/2024-05 ABP Unification/post.md
  15. 54
      docs/en/Community-Articles/2022-01-10-How-to-test-Blazor-components-in-ABP/POST.md
  16. BIN
      docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg
  17. BIN
      docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png
  18. BIN
      docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png
  19. 23
      docs/en/UI/Angular/Modifying-the-Menu.md
  20. 34
      docs/en/UI/Angular/Router-Events.md
  21. 2
      docs/zh-Hans/Timing.md
  22. 3
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
  23. 1
      framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj
  24. 1
      framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorGlobalScriptContributor.cs
  25. 11
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/AbpAspNetCoreComponentsWebThemingModule.cs
  26. 8
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/AbpAuthenticationOptions.cs
  27. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/ILocalStorageService.cs
  28. 31
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/LocalStorageService.cs
  29. 59
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Security/AbpAuthenticationState.cs
  30. 17
      framework/src/Volo.Abp.AspNetCore.Components.Web/wwwroot/libs/abp/js/authentication-state-listener.js
  31. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/ComponentsComponentsBundleContributor.cs
  32. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  33. 45
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ClientProxyExceptionEventHandler.cs
  34. 34
      framework/src/Volo.Abp.AspNetCore.SignalR/Volo/Abp/AspNetCore/SignalR/Authentication/AbpAuthenticationHubFilter.cs
  35. 13
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  36. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/BlazorAppsettingsFilePortChangeForSeparatedAuthServersStep.cs
  37. BIN
      framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.webp
  38. BIN
      modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  39. BIN
      modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  40. BIN
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  41. BIN
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/owl.carousel/assets/owl.video.play.png
  42. BIN
      modules/client-simulation/demo/Volo.ClientSimulation.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  43. BIN
      modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  44. BIN
      modules/cms-kit/host/Volo.CmsKit.Web.Host/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  45. BIN
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  46. BIN
      modules/docs/app/VoloDocs.Web/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  47. 3
      modules/openiddict/app/OpenIddict.Demo.API/OpenIddict.Demo.API.csproj
  48. 14
      modules/openiddict/app/OpenIddict.Demo.API/OpenIddictApiModule.cs
  49. 18
      modules/openiddict/app/OpenIddict.Demo.API/Program.cs
  50. 3
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddict.Demo.Client.Mvc.csproj
  51. 14
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddictMvcModule.cs
  52. 3
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml
  53. 3
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Shared/_Layout.cshtml
  54. 11
      modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs
  55. 61
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.Designer.cs
  56. 44
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.cs
  57. 59
      modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs
  58. 8
      modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj
  59. 7
      modules/openiddict/app/OpenIddict.Demo.Server/Program.cs
  60. BIN
      modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  61. BIN
      modules/virtual-file-explorer/app/Volo.Abp.VirtualFileExplorer.DemoApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png
  62. 116
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html
  63. 9
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts
  64. 72
      npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html
  65. 2
      npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts
  66. 136
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  67. 133
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  68. 16
      npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts
  69. 24
      npm/ng-packs/packages/theme-shared/src/lib/services/authentication-error-handler.service.ts
  70. 1
      npm/ng-packs/packages/theme-shared/src/lib/services/index.ts
  71. 3
      npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts
  72. 7
      npm/preview-publish.ps1
  73. 2
      templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/wwwroot/global.js
  74. 6
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs
  75. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/global.js
  76. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/wwwroot/global.js
  77. 24
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/wwwroot/global.js
  78. 4
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor
  79. 2
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.css
  80. 2
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.js
  81. 4
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor
  82. 5
      templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

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

15
Directory.Packages.props

@ -106,6 +106,7 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.5.1" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="7.5.1" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
<PackageVersion Include="Minio" Version="6.0.2" />
<PackageVersion Include="MongoDB.Driver" Version="2.22.0" />
<PackageVersion Include="NEST" Version="7.17.5" />
@ -117,11 +118,11 @@
<PackageVersion Include="NUglify" Version="1.21.0" />
<PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Octokit" Version="9.0.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="5.2.0" />
<PackageVersion Include="OpenIddict.Core" Version="5.2.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="5.2.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="5.2.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="5.2.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="5.5.0" />
<PackageVersion Include="OpenIddict.Core" Version="5.5.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="5.5.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="5.5.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="5.5.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.21.121" />
<PackageVersion Include="Polly" Version="8.2.0" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
@ -143,7 +144,7 @@
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.0.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.6" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.6" />
@ -173,4 +174,4 @@
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.0" />
</ItemGroup>
</Project>
</Project>

2
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 <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> to update the link or <a href='https://commercial.abp.io/contact'>click here</a> to navigate to the contact page."
}
}

4
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 <a href='mailto:sales@volosoft.com'>sales@volosoft.com</a> adresinden bizimle iletişime geçin veya iletişim sayfasına gitmek için <a href='https://commercial.abp.io/contact'>buraya</a> tıklayın."
}
}
}

4
common.props

@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>8.2.0-preview</Version>
<LeptonXVersion>3.2.0-preview</LeptonXVersion>
<Version>8.3.0-preview</Version>
<LeptonXVersion>3.3.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl>

BIN
docs/en/Blog-Posts/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/abp-studio-solution-runner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/docs-new-navigation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/docs-project-selection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/new-get-started.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/new-mega-menu.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
docs/en/Blog-Posts/2024-05 ABP Unification/new-pricing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

111
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:
![new-mega-menu](new-mega-menu.png)
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.
![new-get-started](new-get-started.png)
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.
![new-pricing](new-pricing.png)
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:
![docs-project-selection](docs-project-selection.png)
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:
![docs-new-navigation](docs-new-navigation.png)
## 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-solution-runner](abp-studio-solution-runner.png)
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.

54
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`
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="bunit" Version="1.2.49" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Volo.Abp.Authorization.Abstractions" Version="5.0.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="bunit" Version="1.28.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Volo.Abp.Authorization.Abstractions" Version="8.1.1" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@ -46,6 +47,7 @@ The contents of `BookStore.Blazor.Tests.csproj`
</ItemGroup>
</Project>
```
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<BookStoreBlazorTestModule>
@ -71,12 +73,23 @@ public abstract class BookStoreBlazorTestBase : BookStoreTestBase<BookStoreBlazo
protected virtual TestContext CreateTestContext()
{
var testContext = new TestContext();
testContext.Services.AddFallbackServiceProvider(ServiceProvider);
foreach (var service in ServiceProvider.GetRequiredService<IAbpApplicationWithExternalServiceProvider>().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<IAbpApplicationWithExternalServiceProvider>().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<IComponentActivator, ServiceProviderComponentActivator>());
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<BookStore.Blazor.Pages.Index>();
// 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<BookStore.Blazor.Pages.Index>();
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();
}
}
```

BIN
docs/en/Community-Articles/2023-03-15-Migration-Mssql-Postgresql/images/db-converter.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/efcore.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 KiB

After

Width:  |  Height:  |  Size: 429 KiB

BIN
docs/en/Community-Articles/2024-03-02-Using-Testcontainers-In-ABP-Unit-Test/mongodb.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 KiB

After

Width:  |  Height:  |  Size: 432 KiB

23
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<ABP.Route> = {
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`.

34
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<NavigationCancel | NavigationEnd | NavigationError>` whereas it would have `Observable<Event>` 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<string>;
currentNavigation: Signal<string>;
```
```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) {}
}
```

2
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` 方法. 例如;

3
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj

@ -23,6 +23,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="IdentityModel" />
</ItemGroup>

1
framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj

@ -16,6 +16,7 @@
<ProjectReference Include="..\Volo.Abp.RemoteServices\Volo.Abp.RemoteServices.csproj" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>
</Project>

1
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");
}
}

11
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<AbpDynamicLayoutComponentOptions>(options =>
{
options.Components.Add(typeof(AbpAuthenticationState), null);
});
}
}

8
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";
}

10
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<string> GetItemAsync(string key);
public ValueTask RemoveItemAsync(string key);
}

31
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<string> GetItemAsync(string key)
{
return await JsRuntime.InvokeAsync<string>("localStorage.getItem", key);
}
public async ValueTask RemoveItemAsync(string key)
{
await JsRuntime.InvokeVoidAsync("localStorage.removeItem", key);
}
}

59
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<AbpAuthenticationOptions> 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);
}
}

17
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('/')
}
});
}());

2
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)

10
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<AbpAspNetCoreComponentsWebOptions>().IsBlazorWebApp)
{
Configure<AbpAuthenticationOptions>(options =>
{
options.LoginUrl = "authentication/login";
options.LogoutUrl = "authentication/logout";
});
}
}
public override void PostConfigureServices(ServiceConfigurationContext context)

45
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<ClientProxyEx
public virtual async Task HandleEventAsync(ClientProxyExceptionEventData eventData)
{
if (eventData.StatusCode == 401)
using (var scope = ServiceProvider.CreateScope())
{
using (var scope = ServiceProvider.CreateScope())
switch (eventData.StatusCode)
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
case 401:
{
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
var accessTokenProvider = scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var result = await accessTokenProvider.RequestAccessToken();
if (result.Status != AccessTokenResultStatus.Success)
var options = scope.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreComponentsWebOptions>>();
if (!options.Value.IsBlazorWebApp)
{
navigationManager.NavigateToLogout("authentication/logout");
return;
}
var navigationManager = scope.ServiceProvider.GetRequiredService<NavigationManager>();
var accessTokenProvider = scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var authenticationOptions = scope.ServiceProvider.GetRequiredService<IOptions<AbpAuthenticationOptions>>();
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<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
}
break;
}
else
case 403:
{
var jsRuntime = scope.ServiceProvider.GetRequiredService<IJSRuntime>();
await jsRuntime.InvokeVoidAsync("eval", "setTimeout(function(){location.assign('/')}, 2000)");
break;
}
}
}

34
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<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
{
var currentPrincipalAccessor = invocationContext.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(invocationContext.Context.User, invocationContext.ServiceProvider))!))
var claimsPrincipal = invocationContext.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, invocationContext.ServiceProvider, invocationContext.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
return await next(invocationContext);
}
@ -22,7 +24,9 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async Task OnConnectedAsync(HubLifetimeContext context, Func<HubLifetimeContext, Task> next)
{
var currentPrincipalAccessor = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(context.Context.User, context.ServiceProvider))!))
var claimsPrincipal = context.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, context.ServiceProvider, context.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
await next(context);
}
@ -31,27 +35,29 @@ public class AbpAuthenticationHubFilter : IHubFilter
public virtual async Task OnDisconnectedAsync(HubLifetimeContext context, Exception? exception, Func<HubLifetimeContext, Exception?, Task> next)
{
var currentPrincipalAccessor = context.ServiceProvider.GetRequiredService<ICurrentPrincipalAccessor>();
using (currentPrincipalAccessor.Change((await GetDynamicClaimsPrincipalAsync(context.Context.User, context.ServiceProvider))!))
var claimsPrincipal = context.Context.User;
await HandleDynamicClaimsPrincipalAsync(claimsPrincipal, context.ServiceProvider, context.Context);
using (currentPrincipalAccessor.Change(claimsPrincipal!))
{
await next(context, exception);
}
}
protected virtual async Task<ClaimsPrincipal?> GetDynamicClaimsPrincipalAsync(ClaimsPrincipal? claimsPrincipal, IServiceProvider serviceProvider)
protected virtual async Task HandleDynamicClaimsPrincipalAsync(ClaimsPrincipal? claimsPrincipal, IServiceProvider serviceProvider, HubCallerContext hubCallerContext)
{
if (claimsPrincipal == null)
{
return claimsPrincipal;
}
if (claimsPrincipal.Identity != null &&
if (claimsPrincipal?.Identity != null &&
claimsPrincipal.Identity.IsAuthenticated &&
serviceProvider.GetRequiredService<IOptions<AbpClaimsPrincipalFactoryOptions>>().Value.IsDynamicClaimsEnabled)
{
var abpClaimsPrincipalFactory = serviceProvider.GetRequiredService<IAbpClaimsPrincipalFactory>();
claimsPrincipal = await abpClaimsPrincipalFactory.CreateDynamicAsync(claimsPrincipal);
}
claimsPrincipal = claimsPrincipal.Identity is ClaimsIdentity identity
? new ClaimsPrincipal(new ClaimsIdentity(claimsPrincipal.Claims, claimsPrincipal.Identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType))
: new ClaimsPrincipal(new ClaimsIdentity(claimsPrincipal.Claims, claimsPrincipal.Identity.AuthenticationType));
return claimsPrincipal;
claimsPrincipal = await serviceProvider.GetRequiredService<IAbpClaimsPrincipalFactory>().CreateDynamicAsync(claimsPrincipal);
if (claimsPrincipal.Identity?.IsAuthenticated == false)
{
hubCallerContext.Abort();
}
}
}
}

13
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -160,11 +160,10 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
var nugetVersion = version;
var localCacheFile = Path.Combine(CliPaths.TemplateCache, name.Replace("/", ".") + ".zip");
#if DEBUG
if (File.Exists(localCacheFile))
if (File.Exists(localCacheFile) && templateSource.IsNullOrWhiteSpace())
{
return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion, nugetVersion);
}
@ -176,6 +175,16 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
return new TemplateFile(File.ReadAllBytes(localCacheFile), version, latestVersion, nugetVersion);
}
if (!skipCache && !templateSource.IsNullOrWhiteSpace() && type == SourceCodeTypes.Template)
{
var templateFilePath = templateSource.EndsWith(".zip")
? templateSource
: Path.Combine(templateSource, name.Replace("/", ".").EnsureEndsWith('-') + version + ".zip");
Logger.LogInformation("Using cached template: " + name + ", version: " + version + " from template source: " + templateFilePath);
return new TemplateFile(File.ReadAllBytes(templateFilePath), version, latestVersion, nugetVersion);
}
Logger.LogInformation("Downloading " + type + ": " + name + ", version: " + version);
var fileContent = await DownloadSourceCodeContentAsync(

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/BlazorAppsettingsFilePortChangeForSeparatedAuthServersStep.cs

@ -10,15 +10,17 @@ public class BlazorAppsettingsFilePortChangeForSeparatedAuthServersStep : Projec
{
var appsettingsFile = context.Files.FirstOrDefault(x =>
!x.IsDirectory &&
x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json",
StringComparison.InvariantCultureIgnoreCase)
(x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json",
StringComparison.InvariantCultureIgnoreCase) ||
x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/appsettings.json",
StringComparison.InvariantCultureIgnoreCase))
);
if (appsettingsFile == null)
{
return;
}
appsettingsFile.NormalizeLineEndings();
var lines = appsettingsFile.GetLines();

BIN
framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 29 KiB

BIN
modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/owl.carousel/assets/owl.video.play.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
modules/client-simulation/demo/Volo.ClientSimulation.Demo/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/cms-kit/host/Volo.CmsKit.IdentityServer/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/cms-kit/host/Volo.CmsKit.Web.Host/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/cms-kit/host/Volo.CmsKit.Web.Unified/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/docs/app/VoloDocs.Web/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

3
modules/openiddict/app/OpenIddict.Demo.API/OpenIddict.Demo.API.csproj

@ -8,7 +8,8 @@
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj" />
</ItemGroup>
</Project>

14
modules/openiddict/app/OpenIddict.Demo.API/OpenIddictApiModule.cs

@ -0,0 +1,14 @@
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace OpenIddict.Demo.API;
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAspNetCoreAuthenticationJwtBearerModule)
)]
public class OpenIddictApiModule : AbpModule
{
}

18
modules/openiddict/app/OpenIddict.Demo.API/Program.cs

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using OpenIddict.Demo.API;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
@ -22,13 +23,15 @@ builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
.AddAbpJwtBearer(options =>
{
options.Authority = "https://localhost:44301";
options.Audience = "AbpAPIResource";
});
await builder.AddApplicationAsync<OpenIddictApiModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
@ -38,11 +41,16 @@ if (app.Environment.IsDevelopment())
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseConfiguredEndpoints(options =>
{
options.MapFallback("{**slug}", context =>
{
context.Response.Redirect("/swagger");
return Task.CompletedTask;
});
});
app.Run();

3
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddict.Demo.Client.Mvc.csproj

@ -8,7 +8,8 @@
<ItemGroup>
<PackageReference Include="IdentityModel" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj" />
</ItemGroup>
</Project>

14
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/OpenIddictMvcModule.cs

@ -0,0 +1,14 @@
using Volo.Abp.AspNetCore.Authentication.OpenIdConnect;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace OpenIddict.Demo.Client.Mvc;
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule)
)]
public class OpenIddictMvcModule : AbpModule
{
}

3
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Index.cshtml

@ -1,6 +1,7 @@
@page
@using Microsoft.AspNetCore.Authentication
@using System.Net.Http
@using System.Net.Http.Headers
@using Microsoft.AspNetCore.Authentication
@using System.Text.Json
@model IndexModel
@{

3
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Pages/Shared/_Layout.cshtml

@ -6,7 +6,6 @@
<title>@ViewData["Title"] - OpenIddict.Demo.Client</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/OpenIddict.Demo.Client.styles.css" asp-append-version="true"/>
</head>
<body>
<header>
@ -48,4 +47,4 @@
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
</html>

11
modules/openiddict/app/OpenIddict.Demo.Client.Mvc/Program.cs

@ -1,4 +1,5 @@
using IdentityModel;
using OpenIddict.Demo.Client.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
@ -16,7 +17,7 @@ builder.Services.AddAuthentication(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
})
.AddOpenIdConnect("oidc", options =>
.AddAbpOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:44301/";
options.RequireHttpsMetadata = true;
@ -35,7 +36,9 @@ builder.Services.AddAuthentication(options =>
options.Scope.Add("AbpAPI");
});
await builder.AddApplicationAsync<OpenIddictMvcModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
@ -47,12 +50,8 @@ if (!app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.UseConfiguredEndpoints();
app.Run();

61
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240108043816_Initial.Designer.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.Designer.cs

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace OpenIddict.Demo.Server.Migrations
{
[DbContext(typeof(ServerDbContext))]
[Migration("20240108043816_Initial")]
[Migration("20240427010513_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -384,6 +384,58 @@ namespace OpenIddict.Demo.Server.Migrations
b.ToTable("AbpSecurityLogs", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentitySession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ClientId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Device")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("DeviceInfo")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("IpAddresses")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("LastAccessed")
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<DateTime>("SignedIn")
.HasColumnType("datetime2");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("Device");
b.HasIndex("SessionId");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpSessions", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
{
b.Property<Guid>("Id")
@ -1424,10 +1476,17 @@ namespace OpenIddict.Demo.Server.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("Id");
b.HasIndex("Name");
b.HasIndex("NormalizedName");
b.ToTable("AbpTenants", (string)null);
});

44
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240108043816_Initial.cs → modules/openiddict/app/OpenIddict.Demo.Server/Migrations/20240427010513_Initial.cs

@ -221,6 +221,26 @@ namespace OpenIddict.Demo.Server.Migrations
table.PrimaryKey("PK_AbpSecurityLogs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AbpSessions",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
SessionId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Device = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
DeviceInfo = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ClientId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
IpAddresses = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
SignedIn = table.Column<DateTime>(type: "datetime2", nullable: false),
LastAccessed = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpSessions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AbpSettingDefinitions",
columns: table => new
@ -262,6 +282,7 @@ namespace OpenIddict.Demo.Server.Migrations
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
NormalizedName = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
EntityVersion = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: false),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: false),
@ -753,6 +774,21 @@ namespace OpenIddict.Demo.Server.Migrations
table: "AbpSecurityLogs",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AbpSessions_Device",
table: "AbpSessions",
column: "Device");
migrationBuilder.CreateIndex(
name: "IX_AbpSessions_SessionId",
table: "AbpSessions",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_AbpSessions_TenantId_UserId",
table: "AbpSessions",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AbpSettingDefinitions_Name",
table: "AbpSettingDefinitions",
@ -771,6 +807,11 @@ namespace OpenIddict.Demo.Server.Migrations
table: "AbpTenants",
column: "Name");
migrationBuilder.CreateIndex(
name: "IX_AbpTenants_NormalizedName",
table: "AbpTenants",
column: "NormalizedName");
migrationBuilder.CreateIndex(
name: "IX_AbpUserClaims_UserId",
table: "AbpUserClaims",
@ -878,6 +919,9 @@ namespace OpenIddict.Demo.Server.Migrations
migrationBuilder.DropTable(
name: "AbpSecurityLogs");
migrationBuilder.DropTable(
name: "AbpSessions");
migrationBuilder.DropTable(
name: "AbpSettingDefinitions");

59
modules/openiddict/app/OpenIddict.Demo.Server/Migrations/ServerDbContextModelSnapshot.cs

@ -381,6 +381,58 @@ namespace OpenIddict.Demo.Server.Migrations
b.ToTable("AbpSecurityLogs", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentitySession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<string>("ClientId")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("Device")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("DeviceInfo")
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("IpAddresses")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<DateTime?>("LastAccessed")
.HasColumnType("datetime2");
b.Property<string>("SessionId")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<DateTime>("SignedIn")
.HasColumnType("datetime2");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("Device");
b.HasIndex("SessionId");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpSessions", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
{
b.Property<Guid>("Id")
@ -1421,10 +1473,17 @@ namespace OpenIddict.Demo.Server.Migrations
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("nvarchar(64)");
b.HasKey("Id");
b.HasIndex("Name");
b.HasIndex("NormalizedName");
b.ToTable("AbpTenants", (string)null);
});

8
modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj

@ -4,7 +4,6 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>OpenIddict.Demo.Server1</AssemblyName>
</PropertyGroup>
<ItemGroup>
@ -65,7 +64,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>
@ -81,10 +79,4 @@
</None>
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="Pages\Shared\_Layout.cshtml" />
<_ContentIncludedByDefault Remove="Pages\Shared\_ValidationScriptsPartial.cshtml" />
<_ContentIncludedByDefault Remove="wwwroot\js\site.js" />
</ItemGroup>
</Project>

7
modules/openiddict/app/OpenIddict.Demo.Server/Program.cs

@ -28,7 +28,6 @@ builder.Services.Configure<AbpLocalizationOptions>(options =>
});
await builder.AddApplicationAsync<OpenIddictServerModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
@ -51,15 +50,9 @@ app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
// Use Microsoft.AspNetCore.Authentication.JwtBearer instead of OpenIddict.Validation.AspNetCore
//app.UseJwtTokenMiddleware();
app.UseAuthentication();
app.UseAbpOpenIddictValidation();
app.UseMultiTenancy();
app.UseAuthorization();
app.UseConfiguredEndpoints();
app.Run();

BIN
modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
modules/virtual-file-explorer/app/Volo.Abp.VirtualFileExplorer.DemoApp/wwwroot/libs/malihu-custom-scrollbar-plugin/mCSB_buttons.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

116
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html

@ -6,62 +6,68 @@
(activate)="tableActivate.emit($event)"
>
@if (actionsTemplate || (actionList.length && hasAtLeastOnePermittedAction)) {
<ngx-datatable-column
[name]="actionsText | abpLocalization"
[maxWidth]="columnWidths[0]"
[width]="columnWidths[0]"
[sortable]="false"
>
<ng-template let-row="row" let-i="rowIndex" ngx-datatable-cell-template>
<ng-container
*ngTemplateOutlet="actionsTemplate || gridActions; context: { $implicit: row, index: i }"
></ng-container>
<ng-template #gridActions>
<abp-grid-actions [index]="i" [record]="row" text="AbpUi::Actions"></abp-grid-actions>
</ng-template>
</ng-template>
</ngx-datatable-column>
} @for (prop of propList; track prop.name; let i = $index) {
<ngx-datatable-column
*abpVisible="prop.columnVisible(getInjected)"
[width]="columnWidths[i + 1] || 200"
[name]="prop.displayName | abpLocalization"
[prop]="prop.name"
[sortable]="prop.sortable"
>
<ng-template ngx-datatable-header-template let-column="column">
@if (prop.tooltip) {
<span [ngbTooltip]="prop.tooltip.text | abpLocalization" [placement]="prop.tooltip.placement || 'auto'"
container="body">
{{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i>
</span>
}@else{
{{ column.name }}
}
</ng-template>
<ng-template let-row="row" let-i="index" ngx-datatable-cell-template>
<ng-container *abpPermission="prop.permission; runChangeDetection: false">
<ng-container *abpVisible="row['_' + prop.name]?.visible">
@if (!row['_' + prop.name].component) {
<div
[innerHTML]="row['_' + prop.name]?.value | async"
(click)="
prop.action && prop.action({ getInjected: getInjected, record: row, index: i })
"
[ngClass]="entityPropTypeClasses[prop.type]"
[class.pointer]="prop.action"
></div>
}@else{
<ng-container
*ngComponentOutlet="
row['_' + prop.name].component;
injector: row['_' + prop.name].injector
"
></ng-container>
<ngx-datatable-column
[name]="actionsText | abpLocalization"
[maxWidth]="columnWidths[0]"
[width]="columnWidths[0]"
[sortable]="false"
>
<ng-template let-row="row" let-i="rowIndex" ngx-datatable-cell-template>
<ng-container
*ngTemplateOutlet="actionsTemplate || gridActions; context: { $implicit: row, index: i }"
></ng-container>
<ng-template #gridActions>
@if (hasAvailableActions(i, row)) {
<abp-grid-actions [index]="i" [record]="row" text="AbpUi::Actions"></abp-grid-actions>
}
</ng-template>
</ng-template>
</ngx-datatable-column>
}
@for (prop of propList; track prop.name; let i = $index) {
<ngx-datatable-column
*abpVisible="prop.columnVisible(getInjected)"
[width]="columnWidths[i + 1] || 200"
[name]="prop.displayName | abpLocalization"
[prop]="prop.name"
[sortable]="prop.sortable"
>
<ng-template ngx-datatable-header-template let-column="column">
@if (prop.tooltip) {
<span
[ngbTooltip]="prop.tooltip.text | abpLocalization"
[placement]="prop.tooltip.placement || 'auto'"
container="body"
>
{{ column.name }} <i class="fa fa-info-circle" aria-hidden="true"></i>
</span>
} @else {
{{ column.name }}
}
</ng-template>
<ng-template let-row="row" let-i="index" ngx-datatable-cell-template>
<ng-container *abpPermission="prop.permission; runChangeDetection: false">
<ng-container *abpVisible="row['_' + prop.name]?.visible">
@if (!row['_' + prop.name].component) {
<div
[innerHTML]="row['_' + prop.name]?.value | async"
(click)="
prop.action && prop.action({ getInjected: getInjected, record: row, index: i })
"
[ngClass]="entityPropTypeClasses[prop.type]"
[class.pointer]="prop.action"
></div>
} @else {
<ng-container
*ngComponentOutlet="
row['_' + prop.name].component;
injector: row['_' + prop.name].injector
"
></ng-container>
}
</ng-container>
</ng-container>
</ng-container>
</ng-template>
</ngx-datatable-column>
</ng-template>
</ngx-datatable-column>
}
</ngx-datatable>

9
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

@ -109,6 +109,7 @@ export class ExtensibleTableComponent<R = any> implements OnChanges {
entityPropTypeClasses = inject(ENTITY_PROP_TYPE_CLASSES);
#injector = inject(Injector);
getInjected = this.#injector.get.bind(this.#injector);
permissionService = this.#injector.get(PermissionService);
constructor() {
const extensions = this.#injector.get(ExtensionsService);
@ -117,9 +118,8 @@ export class ExtensibleTableComponent<R = any> implements OnChanges {
this.actionList = extensions['entityActions'].get(name)
.actions as unknown as EntityActionList<R>;
const permissionService = this.#injector.get(PermissionService);
this.hasAtLeastOnePermittedAction =
permissionService.filterItemsByPolicy(
this.permissionService.filterItemsByPolicy(
this.actionList.toArray().map(action => ({ requiredPolicy: action.permission })),
).length > 0;
this.setColumnWidths(DEFAULT_ACTIONS_COLUMN_WIDTH);
@ -205,4 +205,9 @@ export class ExtensibleTableComponent<R = any> implements OnChanges {
return record;
});
}
hasAvailableActions(index, row): boolean {
const { permission, visible } = this.actionList.get(index).value;
return this.permissionService.getGrantedPolicy(permission) && visible(row);
}
}

72
npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.html

@ -1,42 +1,44 @@
@if (actionList.length > 1) {
<div ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i [ngClass]="icon" [class.me-1]="icon"></i>{{ text | abpLocalization }}
</button>
<div ngbDropdownMenu>
@for (action of actionList; track action.text) {
<ng-container
[ngTemplateOutlet]="dropDownBtnItemTmp"
[ngTemplateOutletContext]="{ $implicit: action }"
>
</ng-container>
}
<div ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i [ngClass]="icon" [class.me-1]="icon"></i>{{ text | abpLocalization }}
</button>
<div ngbDropdownMenu>
@for (action of actionList; track action.text) {
<ng-container
[ngTemplateOutlet]="dropDownBtnItemTmp"
[ngTemplateOutletContext]="{ $implicit: action }"
>
</ng-container>
}
</div>
</div>
</div>
} @if (actionList.length === 1) {
<ng-container
[ngTemplateOutlet]="btnTmp"
[ngTemplateOutletContext]="{ $implicit: actionList.get(0).value }"
></ng-container>
}
@if (actionList.length === 1) {
<ng-container
[ngTemplateOutlet]="btnTmp"
[ngTemplateOutletContext]="{ $implicit: actionList.get(0).value }"
></ng-container>
}
<ng-template #dropDownBtnItemTmp let-action>
@if (action.visible(data)) {
<button
ngbDropdownItem
*abpPermission="action.permission; runChangeDetection: false"
(click)="action.action(data)"
type="button"
>
<ng-container
*ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"
></ng-container>
</button>
<button
ngbDropdownItem
*abpPermission="action.permission; runChangeDetection: false"
(click)="action.action(data)"
type="button"
>
<ng-container
*ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"
></ng-container>
</button>
}
</ng-template>
@ -60,7 +62,9 @@
type="button"
[class]="action.btnClass"
[style]="action.btnStyle"
[ngbTooltip]="action.tooltip.text | abpLocalization" [placement]="action.tooltip.placement || 'auto'" container="body"
[ngbTooltip]="action.tooltip.text | abpLocalization"
[placement]="action.tooltip.placement || 'auto'"
container="body"
>
<ng-container
*ngTemplateOutlet="buttonContentTmp; context: { $implicit: action }"

2
npm/ng-packs/packages/components/extensible/src/lib/components/grid-actions/grid-actions.component.ts

@ -23,7 +23,7 @@ import { NgClass, NgTemplateOutlet } from '@angular/common';
NgClass,
LocalizationModule,
NgTemplateOutlet,
NgbTooltipModule
NgbTooltipModule,
],
selector: 'abp-grid-actions',
templateUrl: './grid-actions.component.html',

136
npm/ng-packs/packages/core/src/lib/services/routes.service.ts

@ -26,6 +26,7 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
private _visible$ = new BehaviorSubject<TreeNode<T>[]>([]);
protected othersGroup: string;
protected shouldSingularizeRoutes = true;
get flat(): T[] {
return this._flat$.value;
@ -51,6 +52,29 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return this._visible$.asObservable();
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) {
return acc;
}
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(flatItems.filter(item => !this.hide(item))));
return flatItems;
}
protected createTree(items: T[]): TreeNode<T>[] {
return createTreeFromList<T, TreeNode<T>>(
items,
@ -69,57 +93,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return Array.from(map, ([key, items]) => ({ group: key, items }));
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) return acc;
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[], visibleItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(visibleItems));
return flatItems;
}
add(items: T[]): T[] {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
let flatItems: T[] = [];
if (!this.shouldSingularizeRoutes) {
flatItems = [...this.flat, ...items];
}
const flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
if (this.shouldSingularizeRoutes) {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
}
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
find(predicate: (item: TreeNode<T>) => boolean, tree = this.tree): TreeNode<T> | null {
return tree.reduce<TreeNode<T> | null>(
(acc, node) => (acc ? acc : predicate(node) ? node : this.find(predicate, node.children)),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (predicate(node)) {
return node;
}
return this.find(predicate, node.children);
}, null);
}
patch(identifier: string, props: Partial<T>): T[] | false {
const flatItems = this._flat$.value;
const index = flatItems.findIndex(item => item[this.id] === identifier);
if (index < 0) return false;
if (index < 0) {
return false;
}
flatItems[index] = { ...flatItems[index], ...props };
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
refresh(): T[] {
@ -132,23 +149,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
const setToRemove = this.findItemsToRemove(set);
const flatItems = this.filterWith(setToRemove);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems);
}
removeByParam(params: Partial<T>): T[] | null {
if (!params) {
return null;
}
const keys = Object.keys(params) as Array<keyof Partial<T>>;
if (keys.length === 0) {
return null;
}
const excludedList = this.flat.filter(item => keys.every(key => item[key] === params[key]));
if (!excludedList?.length) {
return null;
}
return this.publish(flatItems, visibleItems);
for (const item of excludedList) {
this.removeByParam({ [this.parentId]: item[this.id] } as Partial<T>);
}
const flatItems = this.flat.filter(item => !excludedList.includes(item));
return this.publish(flatItems);
}
search(params: Partial<T>, tree = this.tree): TreeNode<T> | null {
const searchKeys = Object.keys(params) as Array<keyof Partial<T>>;
return tree.reduce<TreeNode<T> | null>(
(acc, node) =>
acc
? acc
: searchKeys.every(key => node[key] === params[key])
? node
: this.search(params, node.children),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (searchKeys.every(key => node[key] === params[key])) {
return node;
}
return this.search(params, node.children);
}, null);
}
setSingularizeStatus(singularize = true): void {
this.shouldSingularizeRoutes = singularize;
}
}
@ -164,7 +208,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
readonly parentId = 'parentName';
readonly hide = (item: T) => item.invisible || !this.isGranted(item);
readonly sort = (a: T, b: T) => {
return this.compareFunc(a,b)
return this.compareFunc(a, b);
};
constructor(protected injector: Injector) {

133
npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

@ -172,6 +172,64 @@ describe('Routes Service', () => {
});
});
describe('#setSingularizeStatus', () => {
it('should allow to duplicate routes when called with false', () => {
service.setSingularizeStatus(false);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(routes.length);
});
it('should allow to duplicate routes with the same name when called with false', () => {
service.setSingularizeStatus(false);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'foo', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(routes.length + 1);
});
it('should allow to routes with the same name but different parentName when called with false', () => {
service.setSingularizeStatus(false);
service.add([
{ path: '/foo/bar', name: 'bar', parentName: 'foo', order: 2 },
{ path: '/foo/bar', name: 'bar', parentName: 'baz', order: 1 },
]);
const flat = service.flat;
expect(flat.length).toBe(2);
});
it('should not allow to duplicate routes when called with true', () => {
service.setSingularizeStatus(false);
service.add(routes);
service.setSingularizeStatus(true);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(5);
});
it('should not allow to duplicate routes with the same name when called with true', () => {
service.setSingularizeStatus(true);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'any', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(5);
});
});
describe('#find', () => {
it('should return node found based on query', () => {
service.add(routes);
@ -242,6 +300,81 @@ describe('Routes Service', () => {
});
});
describe('#removeByParam', () => {
it('should remove route based on given route', () => {
service.add(routes);
service.removeByParam({
name: 'bar',
parentName: 'foo',
});
const flat = service.flat;
expect(flat.length).toBe(2);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).toBe(null);
});
it('should remove if more than one route has the same properties', () => {
service.setSingularizeStatus(false);
service.add([
...routes,
{
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
},
]);
service.removeByParam({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
const flat = service.flat;
expect(flat.length).toBe(5);
const notFound = service.search({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
expect(notFound).toBe(null);
});
it("shouldn't remove if there is no route with the given properties", () => {
service.add(routes);
const flatLengthBeforeRemove = service.flat.length;
service.removeByParam({
name: 'bar',
parentName: 'baz',
});
const flat = service.flat;
expect(flatLengthBeforeRemove - flat.length).toBe(0);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).not.toBe(null);
});
});
describe('#patch', () => {
it('should patch propeties of routes based on given routeNames', () => {
service['isGranted'] = jest.fn(route => route.requiredPolicy !== 'X');

16
npm/ng-packs/packages/theme-shared/src/lib/providers/error-handlers.provider.ts

@ -1,9 +1,12 @@
import { Provider } from '@angular/core';
import { CUSTOM_ERROR_HANDLERS } from '../tokens';
import { TenantResolveErrorHandlerService } from '../services/tenant-resolve-error-handler.service';
import { AbpFormatErrorHandlerService } from '../services/abp-format-error-handler.service';
import { StatusCodeErrorHandlerService } from '../services/status-code-error-handler.service';
import { UnknownStatusCodeErrorHandlerService } from '../services/unknown-status-code-error-handler.service';
import {
TenantResolveErrorHandlerService,
AbpFormatErrorHandlerService,
StatusCodeErrorHandlerService,
UnknownStatusCodeErrorHandlerService,
AbpAuthenticationErrorHandler,
} from '../services';
export const DEFAULT_HANDLERS_PROVIDERS: Provider[] = [
{
@ -26,4 +29,9 @@ export const DEFAULT_HANDLERS_PROVIDERS: Provider[] = [
multi: true,
useExisting: UnknownStatusCodeErrorHandlerService,
},
{
provide: CUSTOM_ERROR_HANDLERS,
multi: true,
useExisting: AbpAuthenticationErrorHandler,
},
];

24
npm/ng-packs/packages/theme-shared/src/lib/services/authentication-error-handler.service.ts

@ -0,0 +1,24 @@
import { inject, Injectable } from '@angular/core';
import { AuthService, ConfigStateService } from '@abp/ng.core';
import { HttpErrorResponse } from '@angular/common/http';
import { CustomHttpErrorHandlerService } from '../models/common';
import { CUSTOM_HTTP_ERROR_HANDLER_PRIORITY } from '../constants/default-errors';
@Injectable({ providedIn: 'root' })
export class AbpAuthenticationErrorHandler implements CustomHttpErrorHandlerService {
readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.veryHigh;
protected readonly authService = inject(AuthService);
protected readonly configStateService = inject(ConfigStateService);
canHandle(error: unknown): boolean {
return error instanceof HttpErrorResponse && error.status === 401;
}
execute() {
this.configStateService.refreshAppState().subscribe(({ currentUser }) => {
if (!currentUser.isAuthenticated) {
this.authService.logout();
}
});
}
}

1
npm/ng-packs/packages/theme-shared/src/lib/services/index.ts

@ -9,3 +9,4 @@ export * from './tenant-resolve-error-handler.service';
export * from './status-code-error-handler.service';
export * from './unknown-status-code-error-handler.service';
export * from './router-error-handler.service';
export * from './authentication-error-handler.service'

3
npm/ng-packs/packages/theme-shared/src/lib/services/status-code-error-handler.service.ts

@ -17,7 +17,7 @@ export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerServ
protected readonly authService = inject(AuthService);
protected readonly handledStatusCodes = [401, 403, 404, 500] as const;
protected status: typeof this.handledStatusCodes[number];
protected status: (typeof this.handledStatusCodes)[number];
readonly priority = CUSTOM_HTTP_ERROR_HANDLER_PRIORITY.normal;
@ -90,7 +90,6 @@ export class StatusCodeErrorHandlerService implements CustomHttpErrorHandlerServ
this.showConfirmation(title, message).subscribe();
break;
case 403:
case 500:
this.showPage();

7
npm/preview-publish.ps1

@ -1,11 +1,12 @@
param(
[string]$Version,
[string]$Registry
[string]$Registry,
[string]$LeptonXVersion
)
$commands = (
".\publish-mvc.ps1 $Version $Registry",
".\publish-ng.ps1 $Version $Registry"
".\publish-ng.ps1 $Version $Registry $LeptonXVersion"
);
$NextVersion = $(node publish-utils.js --nextVersion)

2
templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/wwwroot/global.js

@ -5,6 +5,8 @@ var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.domReady=function(n){doc
var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.utils.updateHTMLDirAndLangFromLocalStorage=function(){var i=JSON.parse(localStorage.getItem("Abp.IsRtl")),n=document.getElementsByTagName("html")[0],t;n&&(t=localStorage.getItem("Abp.SelectedLanguage"),t&&n.setAttribute("lang",t),i&&n.setAttribute("dir","rtl"))};abp.utils.updateHTMLDirAndLangFromLocalStorage()})();
(function(){const n="authentication-state-id";window.addEventListener("storage",function(t){t.key===n&&t.oldValue!==t.newValue&&(t.oldValue||!t.newValue?(abp.utils.removeOidcUser(),window.location.reload()):location.assign("/"))})})();
/*!
* Bootstrap v5.1.3 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)

6
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs

@ -68,13 +68,15 @@ public class MyProjectNameMenuContributor : IMenuContributor
{
var accountStringLocalizer = context.GetLocalizer<AccountResource>();
var authServerUrl = _configuration["AuthServer:Authority"] ?? "";
context.Menu.AddItem(new ApplicationMenuItem(
"Account.Manage",
accountStringLocalizer["MyAccount"],
$"Account/Manage",
$"{authServerUrl.EnsureEndsWith('/')}Account/Manage",
icon: "fa fa-cog",
order: 1000,
null).RequireAuthenticated());
target: "_blank").RequireAuthenticated());
return Task.CompletedTask;
}

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

24
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/wwwroot/global.js

File diff suppressed because one or more lines are too long

4
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor

@ -8,7 +8,7 @@
<base href="/" />
<!--ABP:Styles-->
<link href="global.css?_v=638473199568040519" rel="stylesheet"/>
<link href="global.css?_v=638490521600060525" rel="stylesheet"/>
<link href="main.css" rel="stylesheet"/>
<!--/ABP:Styles-->
<link href="MyCompanyName.MyProjectName.Blazor.Client.styles.css" rel="stylesheet"/>
@ -36,7 +36,7 @@
</div>
<!--ABP:Scripts-->
<script src="global.js?_v=638473199570937070"></script>
<script src="global.js?_v=638490521602018274"></script>
<!--/ABP:Scripts-->
<!-- <TEMPLATE-REMOVE IF-NOT='PWA'> -->

2
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.css

File diff suppressed because one or more lines are too long

2
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/wwwroot/global.js

@ -5,3 +5,5 @@ var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.domReady=function(n){doc
var abp=abp||{};(function(){abp.utils=abp.utils||{};abp.utils.updateHTMLDirAndLangFromLocalStorage=function(){var i=JSON.parse(localStorage.getItem("Abp.IsRtl")),n=document.getElementsByTagName("html")[0],t;n&&(t=localStorage.getItem("Abp.SelectedLanguage"),t&&n.setAttribute("lang",t),i&&n.setAttribute("dir","rtl"))};abp.utils.updateHTMLDirAndLangFromLocalStorage()})();
(function(){const n="authentication-state-id";window.addEventListener("storage",function(t){t.key===n&&t.oldValue!==t.newValue&&(t.oldValue||!t.newValue?(abp.utils.removeOidcUser(),window.location.reload()):location.assign("/"))})})();

4
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor

@ -8,7 +8,7 @@
<base href="/" />
<!--ABP:Styles-->
<link href="global.css?_v=638473199135168410" rel="stylesheet"/>
<link href="global.css?_v=638490522235034880" rel="stylesheet"/>
<link href="main.css" rel="stylesheet"/>
<!--/ABP:Styles-->
<link href="MyCompanyName.MyProjectName.Blazor.Host.Client.styles.css" rel="stylesheet"/>
@ -30,7 +30,7 @@
</div>
<!--ABP:Scripts-->
<script src="global.js?_v=638473199135794271"></script>
<script src="global.js?_v=638490522235601638"></script>
<!--/ABP:Scripts-->
</body>

5
templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs

@ -134,6 +134,11 @@ public class MyProjectNameBlazorHostModule : AbpModule
options.UseSqlServer();
});
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = MultiTenancyConsts.IsEnabled;
});
Configure<AbpBundlingOptions>(options =>
{
// MVC UI

Loading…
Cancel
Save