Browse Source

Merge branch 'dev' into timezone

pull/22236/head
maliming 11 months ago
parent
commit
4e786cf6e4
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      Directory.Packages.props
  2. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  3. 5
      build/common.ps1
  4. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_122158.webp
  5. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153010.webp
  6. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4732-2000px.webp
  7. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4770-2000px.webp
  8. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4915-2000px.webp
  9. BIN
      docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/Screenshot_2025-03-04_at_13.47.28.webp
  10. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/abp-js-proxy.png
  11. 324
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md
  12. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-component-result.gif
  13. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission-vue.png
  14. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission.png
  15. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-counter-result.gif
  16. BIN
      docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-message.png
  17. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/1.png
  18. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/2.png
  19. BIN
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/3.png
  20. 248
      docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/post.md
  21. BIN
      docs/en/images/account-pro-external-login-settings.png
  22. BIN
      docs/en/images/account-pro-select-account-parameter.png
  23. BIN
      docs/en/images/account-pro-select-account.png
  24. 58
      docs/en/modules/account-pro.md
  25. 16
      docs/en/modules/gdpr.md
  26. 2
      docs/en/release-info/migration-guides/identityserver4-step-by-step.md
  27. 29
      docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md
  28. 14
      framework/Volo.Abp.sln
  29. 19
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo.Abp.AspNetCore.Bundling.csproj
  30. 15
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/AbpAspNetCoreBundlingModule.cs
  31. 3
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleCache.cs
  32. 3
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleCacheItem.cs
  33. 248
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleManagerBase.cs
  34. 2
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleResult.cs
  35. 14
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundlerBase.cs
  36. 2
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundlerContext.cs
  37. 2
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundleCache.cs
  38. 3
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundleManager.cs
  39. 4
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundler.cs
  40. 2
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundlerContext.cs
  41. 6
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/Scripts/IScriptBundler.cs
  42. 6
      framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/Styles/IStyleBundler.cs
  43. 106
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorBundlingModule.cs
  44. 13
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpBlazorWebView.cs
  45. 125
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/BundleManager.cs
  46. 8
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/IMauiBlazorContentFileProvide.cs
  47. 26
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorBundlerBase.cs
  48. 65
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorContentFileProvider.cs
  49. 28
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Scripts/ScriptBundler.cs
  50. 40
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Styles/StyleBundler.cs
  51. 41
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj
  52. 2
      framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorServerComponentBundleManager.cs
  53. 34
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor
  54. 46
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor
  55. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundleContributorOptions.cs
  56. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj
  57. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs
  58. 233
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs
  59. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/MvcUiBundlerBase.cs
  60. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Scripts/IScriptBundler.cs
  61. 8
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Scripts/ScriptBundler.cs
  62. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/IStyleBundler.cs
  63. 11
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs
  64. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
  65. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs
  66. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs
  67. 11
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs
  68. 11
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs
  69. 8
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs
  70. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs
  71. 16
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  72. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/CliVersionService.cs
  73. 1
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
  74. 13
      framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingOptions.cs
  75. 9
      modules/account/src/Volo.Abp.Account.Installer/InstallationNotes.md
  76. 9
      modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/InstallationNotes.md
  77. 12
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.Installer/InstallationNotes.md
  78. 2
      modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor
  79. 13
      modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Installer/InstallationNotes.md
  80. 7
      modules/basic-theme/src/Volo.Abp.BasicTheme.Installer/InstallationNotes.md
  81. 16
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Installer/InstallationNotes.md
  82. 28
      modules/blogging/src/Volo.Blogging.Installer/InstallationNotes.md
  83. BIN
      modules/blogging/src/Volo.Blogging.Installer/blogging-permissions.png
  84. 48
      modules/cms-kit/src/Volo.CmsKit.Installer/InstallationNotes.md
  85. BIN
      modules/cms-kit/src/Volo.CmsKit.Installer/cmskit-permissions.png
  86. 5
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs
  87. 12
      modules/docs/Volo.Docs.abpmdl
  88. 31
      modules/docs/src/Volo.Docs.Installer/InstallationNotes.md
  89. BIN
      modules/docs/src/Volo.Docs.Installer/docs-permissions.png
  90. 24
      modules/feature-management/src/Volo.Abp.FeatureManagement.Installer/InstallationNotes.md
  91. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs
  92. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json
  93. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json
  94. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json
  95. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json
  96. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json
  97. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json
  98. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json
  99. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json
  100. 2
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json

2
Directory.Packages.props

@ -66,6 +66,8 @@
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.30" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="9.0.30" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.2" />

3
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -264,6 +264,7 @@
"EditProfile": "Edit Profile",
"ConfirmEmailForPost": "To be able to post, you need to confirm your email. Go to account.abp.io/Account/Manage and verify your email in the Personal Info tab.",
"DailyPostCreateLimitation": "You have reached the daily post creation limit. You can create a new post in {0}.",
"YourAccountDisabled": "Your user account is disabled!"
"YourAccountDisabled": "Your user account is disabled!",
"PostCreationFailed": "An error occurred while creating the post. Please try again later."
}
}

5
build/common.ps1

@ -23,6 +23,11 @@ $solutionPaths = @(
"../modules/blob-storing-database"
)
# Remove MAUI related projects if not on Windows
if ($env:OS -ne "Windows_NT") {
dotnet sln ../framework/Volo.Abp.sln remove ../framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj
}
if ($full -eq "-f")
{
# List of additional solutions required for full build

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_122158.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/20250304_153010.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4732-2000px.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4770-2000px.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/IMG_4915-2000px.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/en/Community-Articles/2025-03-10-WE-HAD-A-BLAST-AT-BASTA-FRANKFURT-2025/Screenshot_2025-03-04_at_13.47.28.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/abp-js-proxy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

324
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/post.md

@ -0,0 +1,324 @@
# Using Vue components in a Razor Pages ABP Application
In modern web development, integrating dynamic front-end frameworks with server-side technologies has become increasingly essential for creating responsive and interactive applications. This article explores how to effectively use Vue components within Razor Pages in an ABP Framework application. We will delve into the process of consuming endpoints through ABP Client Proxies, leveraging ABP's powerful localization features to enhance user experience, and implementing ABP permissions to ensure secure access control. By the end of this guide, you will have a comprehensive understanding of how to seamlessly blend Vue.js with Razor Pages, empowering you to build robust and user-friendly applications.
This article won't use any SPA approach. The goal of this article is to use Razor Pages with simple Vue components to eliminate jQuery while developing MVC application.
> **🎉 Also video version is available!**
>
> [Watch on YouTube Now!](https://youtu.be/sZ8iSMovHZs?si=GynuJjsLEI1p2g6w)
## Creating the Solution
Let's create a simple TODO list application to demonstrate how to use Vue components in Razor Pages. I'll build a really simple backend without a connection to a database for demonstration purposes. We will focus on the frontend part.
- Creating a solution with ABP CLI:
```bash
abp new MyTodoApp -t app-nolayers -csf
```
## Configure Vue
We need to add the `@abp/vue` package to the project to use Vue components.
```bash
npm install @abp/vue
```
- Install client libraries by using ABP CLI:
```bash
abp install-libs
```
As a last step, we need to configure our bundle in the `ConfigureBundles` method in the `MyTodoAppModule.cs` file:
```csharp
private void ConfigureBundles()
{
Configure<AbpBundlingOptions>(options =>
{
// ...
options.ScriptBundles.Configure(
// Or BasicThemeBundles.Scripts.Global
// Or LeptonXLiteThemeBundles.Scripts.Global
// 👇 Depends on the theme you are using
LeptonXThemeBundles.Scripts.Global,
bundle =>
{
bundle.AddFiles("/global-scripts.js");
// 👇 Make sure to add this line
bundle.AddContributors(typeof(VueScriptContributor));
}
);
});
}
```
> If your IDE doesn't recognize the namespace of the `VueScriptContributor`, you can add it manually:
>
> ```csharp
> using Volo.Abp.AspNetCore.Mvc.UI.Packages.Vue;
> ```
Now we're ready to use Vue components in our Razor Pages.
## Creating a Vue Component
Let's create a simple Vue component to display the TODO list.
### Passing a simple message to the component
- Remove existing HTML codes in `Index.cshtml` and replace with the following code:
```html
<div id="vue-app">
<message-component :message="'Welcome, @CurrentUser.UserName !'"></todo-component>
</div>
```
- Navigate to the `Index.cshtml.js` file and add the following code:
```js
Vue.component('message-component', {
template: '<div>Hello, {{ message }}</div>',
props: ['message']
});
new Vue({
el: '#vue-app'
});
```
Run the application and you should see the following output:
![Vue Component](./vue-message.png)
> _Hard refresh might be required to see the component since we added a new vue js file to the bundle._
>
> If still you can't see the component, please check the browser console for any errors.
### Interacting with the component
Let's add a button to the component to interact with the component.
- Add another component in the `Index.cshtml` file:
```html
<div id="vue-app">
<message-component :message="'Welcome, @CurrentUser.UserName !'"></message-component>
<counter-component></counter-component>
</div>
```
```js
Vue.component('counter-component', {
template:`
<div class="card">
<div class="card-body">
<p>Count: {{ count }}</p>
<button class="btn btn-primary" @click="increment">Increment</button>
</div>
</div>
`,
data: function () {
return {
count: 0
};
},
methods: {
increment: function () {
this.count++;
}
}
});
```
> _Do not replicate `new Vue({})` code block in the file. It's already in the `Index.cshtml.js` file. Keep it at the bottom of the file as it is._
Run the application and you should see the following output:
![Vue Component](./vue-counter-result.gif)
## Using ABP Client Proxy, Authorization and Localization
### Building the backend
Before we go, let's build our backend to use in the component.
- Creating a simple Application Service:
```csharp
public class TodoAppService : MyTodoAppAppService, ITodoAppService
{
public static List<TodoItem> Items { get; } = new List<TodoItem>();
[Authorize("Todo.Create")]
public async Task<TodoItem> AddTodoItemAsync(TodoItem input)
{
Items.Add(input);
return input;
}
[Authorize("Todo")]
public async Task<List<TodoItem>> GetAllAsync()
{
await Task.Delay(1500);
return Items;
}
}
```
- `TodoItem.cs`
```csharp
public class TodoItem
{
public string Description { get; set; }
public bool IsDone { get; set; }
}
```
- `ITodoAppService.cs`
```csharp
public interface ITodoAppService
{
Task<List<TodoItem>> GetAllAsync();
Task<TodoItem> AddTodoItemAsync(TodoItem input);
}
```
- Run the application and if you can see the following client proxy in the browser console, you're ready to go:
![Client Proxy](./abp-js-proxy.png)
> [!NOTE]
> If you can't see the client proxy in the browser console, please check the [Dynamic JavaScript Proxies](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/dynamic-javascript-proxies) to learn how to enable it.
- Add a new permission in the `MyTodoAppPermissionDefinitionProvider.cs` file:
```csharp
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup(MyTodoAppPermissions.GroupName);
var todo = myGroup.AddPermission("Todo");
todo.AddChild("Todo.Create");
}
```
> _I go without localization or constants for simplicity._
- Add a localization key in the `en.json` file:
```json
{
"TodoItems": "Todo Items Localized"
}
```
### Building the Vue Component: Using ABP Localization, Authorization and Client Proxy
Since the component it directly loaded into the page, we can access the `abp` object on the page.
So we can use:
- `abp.localization.localize()` to localize a string.
- `abp.auth.isGranted()` to check the authorization.
- `myTodoApp.todo.getAll()` and `myTodoApp.todo.addTodoItem` to call the Application Service.
inside **Vue Component** code.
- Let's add another component named `todo-component` and usee all the **ABP Features** in it.
```html
<div id="vue-app">
<!-- ... -->
<todo-component></todo-component>
</div>
```
- Implement the `todo-component` in `Index.cshtml.js` file:
```js
Vue.component('todo-component', {
template: `
<div class="card" v-if="abp.auth.isGranted('Todo')">
<div class="card-header border-bottom">
<h3>{{ abp.localization.localize('TodoItems') }}</h3>
</div>
<div class="card-body">
<div v-if="isBusy" class="w-100 text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<ul v-else-if="todos.length > 0" class="list-group">
<li class="list-group-item" v-for="item in todos" :key="item.description">
<input class="form-check-input" type="checkbox" v-model="item.isDone">
<label class="form-check-label">{{ item.description }}</label>
</li>
</ul>
<p v-else>No todos yet</p>
</div>
<div v-if="abp.auth.isGranted('Todo.Create')" class="card-footer d-flex flex-column gap-2 border-top pt-2">
<input class="form-control" type="text" v-model="newTodo.description" placeholder="Add a new todo">
<div class="form-check">
<input class="form-check-input" type="checkbox" v-model="newTodo.isDone" id="isDone">
<label class="form-check-label" for="isDone">Is Done</label>
</div>
<button class="btn btn-primary" @click="addTodo">Add</button>
</div>
</div>
`,
data: function () {
return {
newTodo: {
description: '',
isDone: false
},
isBusy: false,
todos: []
};
},
methods: {
addTodo() {
myTodoApp.todo.addTodoItem(this.newTodo);
this.newTodo = { description: '', isDone: false };
this.todos.push(this.newTodo);
// Preferrable, you can load entire list of todos again.
// this.loadTodos();
},
async loadTodos() {
if (!abp.auth.isGranted('Todo')) {
return;
}
this.isBusy = true;
this.todos = await myTodoApp.todo.getAll();
this.isBusy = false;
}
},
mounted() {
this.loadTodos();
}
});
```
And see the result:
![Vue Component](./todo-component-result.gif)
Since we use `abp.auth.isGranted()` to check the authorization, we can see the component only if we have the permission.
Whenever you remove `Todo.Create` permission, you can see the component is not rendered.
![Todo Permission](./todo-permission.png)
You won't see the card footer:
![Todo Permission](./todo-permission-vue.png)

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-component-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission-vue.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/todo-permission.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-counter-result.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/en/Community-Articles/2025-03-18-Using-Vue-Components/vue-message.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

248
docs/en/Community-Articles/2025-03-23-Understanding-the-Embedded-Files-in-ABP-Framework/post.md

@ -0,0 +1,248 @@
# Understanding the Embedded Files in ABP Framework
Embedded Files functionality in .NET applications allows external files (like configuration files, images, etc.) to be directly embedded into assemblies (.exe or .dll). This simplifies deployment, prevents file loss or tampering, improves security and performance, and reduces path and dependency management issues. Through embedded resources, programs can access these files more conveniently without additional file operations.
## Embedding Files in Your Project
We embed `Volo\Abp\MyModule\Localization\*.json` files into the assembly in our `MyModule.csproj`.
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Volo\Abp\MyModule\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" />
</ItemGroup>
</Project>
```
If we check the `en.json` file in our IDE, we'll see it's embedded in the assembly.
![image](1.png)
When we decompile the built `MyModule.dll` file, we can also see the `en.json` file.
![image](2.png)
## Accessing Embedded Files in Code
```csharp
public class Program
{
public static async Task<int> Main(string[] args)
{
var embeddedFiles = typeof(Program).Assembly.GetManifestResourceNames();
foreach (var embeddedFile in embeddedFiles)
{
Console.WriteLine(embeddedFile);
var fileStream = typeof(Program).Assembly.GetManifestResourceStream(embeddedFile);
if (fileStream != null)
{
using var reader = new System.IO.StreamReader(fileStream);
var content = await reader.ReadToEndAsync();
Console.WriteLine(content);
}
}
}
}
```
This code will output the embedded file names and their contents.
```
MyModule.Volo.Abp.MyModule.Localization.en.json
{
"key":"value"
}
```
## Integrating with ABP Virtual File System
The ABP Virtual File System makes it possible to manage files that don't physically exist on the file system (disk). It's mainly used to embed (js, css, image..) files into assemblies and use them like physical files at runtime.
The following code shows how to add embedded files from the current application assembly to the ABP virtual file system:
```csharp
[DependsOn(typeof(AbpVirtualFileSystemModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>();
});
}
}
```
ABP creates an `AbpEmbeddedFileProvider` to access the embedded files.
The full name of `en.json` is `MyModule.Volo.Abp.MyModule.Localization.en.json`. Without directory information, ABP uses `.` to split and assume directory information. This creates the following directory structure in the virtual file system:
```
[Dir] [/MyModule]
[Dir] [/MyModule/Volo]
[Dir] [/MyModule/Volo/Abp]
[Dir] [/MyModule/Volo/Abp/MyModule]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization]
[File] [/MyModule/Volo/Abp/MyModule/Localization/en.json]
```
Now you can inject `IVirtualFileProvider` to access embedded files using the directory/file structure above.
## Manifest Embedded File Provider
You might have noticed that using `.` to split and assume directory information can cause confusion if filenames contain dots.
For example, if your filename is `zh.hans.json`, ABP will generate the following directory structure, which isn't what we want:
```
[Dir] [/MyModule]
[Dir] [/MyModule/Volo]
[Dir] [/MyModule/Volo/Abp]
[Dir] [/MyModule/Volo/Abp/MyModule]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization]
[Dir] [/MyModule/Volo/Abp/MyModule/Localization/zh]
[File] [/MyModule/Volo/Abp/MyModule/Localization/zh/hans.json]
```
Microsoft provides the `Microsoft.Extensions.FileProviders.Manifest` library to solve this problem.
We need to add this package dependency and set `<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>` in our project:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="Volo.Abp.VirtualFileSystem" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Manifest" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Remove="Volo\Abp\MyModule\Localization\*.json" />
<EmbeddedResource Include="Volo\Abp\MyModule\Localization\*.json" />
</ItemGroup>
</Project>
```
After rebuilding the project, when we decompile `MyModule.dll`, we'll see an additional `Microsoft.Extensions.FileProviders.Embedded.Manifest.xml` file.
![image](3.png)
This manifest file stores all the directory and file information of embedded resources. When ABP finds this file, it will use `ManifestEmbeddedFileProvider` instead of `AbpEmbeddedFileProvider` to access embedded files:
```xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Manifest>
<ManifestVersion>1.0</ManifestVersion>
<FileSystem>
<File Name="Microsoft.Extensions.FileProviders.Embedded.Manifest.xml">
<ResourcePath>Microsoft.Extensions.FileProviders.Embedded.Manifest.xml</ResourcePath>
</File>
<Directory Name="Volo">
<Directory Name="Abp">
<Directory Name="MyModule">
<Directory Name="Localization">
<File Name="zh.hans.json">
<ResourcePath>MyModule.Volo.Abp.MyModule.Localization.zh.hans.json</ResourcePath>
</File>
</Directory>
</Directory>
</Directory>
</Directory>
</FileSystem>
</Manifest>
```
## Parameters of AddEmbedded Method
The `AddEmbedded` method can take two parameters:
### baseNamespace
This may only be needed if you haven't used the `Manifest Embedded File Provider` and your project's `root namespace` isn't empty. In this case, set your root namespace here.
The `root namespace` is your project's name by default. You can change it or set it to empty in the `csproj` file.
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>MyModule</RootNamespace>
</PropertyGroup>
</Project>
```
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace></RootNamespace>
</PropertyGroup>
</Project>
```
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>(baseNamespace: "MyModule");
});
```
```
[Dir] [/Volo]
[Dir] [/Volo/Abp]
[Dir] [/Volo/Abp/MyModule]
[Dir] [/Volo/Abp/MyModule/Localization]
[File] [/Volo/Abp/MyModule/Localization/en.json]
```
### baseFolder
If you don't want to expose all embedded files in the project, but only want to expose a specific folder (and sub folders/files), you can set the base folder relative to your project root folder.
> baseFolder is only effective when using `Manifest Embedded File Provider`.
You can set the `baseFolder` parameter to `/Volo/Abp/MyModule`, resulting in this directory structure:
```csharp
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<MyModule>(baseFolder: "/Volo/Abp/MyModule");
});
```
```
[Dir] [Localization]
[File] [Localization/en.json]
```
## Summary
We recommend using the `Manifest Embedded File Provider` in your projects and libraries. Hope this article has been helpful.
## References
[ABP Virtual File System](https://abp.io/docs/latest/framework/infrastructure/virtual-file-system)
[Manifest Embedded File Provider](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers#manifest-embedded-file-provider)

BIN
docs/en/images/account-pro-external-login-settings.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 485 KiB

BIN
docs/en/images/account-pro-select-account-parameter.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/images/account-pro-select-account.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

58
docs/en/modules/account-pro.md

@ -104,6 +104,48 @@ If you use `Social / External Logins`, It is automatically called for authentica
![account-pro-module-local-login-setting](../images/account-pro-module-local-login-setting.png)
### Switching users during OAuth login
If you have an OAuth/Auth Server application using the Account Pro module, you can pass the `prompt=select_account` parameter to force the user to select an account.
Example to pass `prompt=select_account` parameter in OpenIdConnect:
```csharp
.AddAbpOpenIdConnect("oidc", options =>
{
// ...
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = redirectContext =>
{
redirectContext.ProtocolMessage.Prompt = "select_account";
return Task.CompletedTask;
}
};
// ...
});
```
![account-pro-secect-account](../images/account-pro-select-account-parameter.png)
You have three options:
- Continue: The login process will continue with the current account.
- Switch to another account: Will be redirected to the login page to log in with another account.
- Create a new account: Will be redirected to the register page to create a new account.
> The OAuth login process will continue after the user selects one of the options.
![account-pro-secect-account](../images/account-pro-select-account.png)
All available prompt parameters:
| **Parameter** | **Description** |
|------------------|-------------------------------------------------------------------------------------------------------------|
| `login` | Forces the user to re-authenticate, even if they are already logged in. |
| `consent` | Forces the user to re-consent to the requested permissions, even if they have consented before. |
| `select_account` | Forces the user to select an account, even if they are already logged in (especially relevant if multiple accounts are available). |
| `none` | Does not trigger any prompt. If the user is not logged in, or their consent is not granted, it will return an error or redirect accordingly. |
## Social / External Logins
@ -113,7 +155,7 @@ The application startup template comes with **Twitter**, **Google** and **Micros
![account-pro-external-login-settings](../images/account-pro-external-login-settings.png)
The social/External login system is compatible with the multi-tenancy. Each tenant can configure their own provider settings if your application is multi-tenant.
The social/External login system is compatible with the multi-tenancy. Each tenant can enable or disable the external login provider and configure their own provider settings if your application is multi-tenant.
### Install a new External Login
@ -137,7 +179,7 @@ context.Services.AddAuthentication()
facebook.Scope.Add("public_profile");
})
.WithDynamicOptions<FacebookOptions>(
FacebookDefaults.AuthenticationScheme,
FacebookDefaults.AuthenticationScheme, // Facebook
options =>
{
options.WithProperty(x => x.AppId);
@ -149,6 +191,18 @@ context.Services.AddAuthentication()
* `AddFacebook()` is the standard method that you can set hard-coded configuration.
* `WithDynamicOptions<FacebookOptions>` is provided by the Account Module which makes possible to configure the provided properties on the UI.
#### Localize Provider Properties
You can add following translation to localize the properties of the external login providers:
`en.json`:
````json
"ExternalProvider:Facebook": "Facebook",
"ExternalProvider:Facebook:AppId": "App ID",
"ExternalProvider:Facebook:AppSecret": "App Secret",
````
### IPostConfigureAccountExternalProviderOptions
Some external logins may be initialized based on dynamic properties. You can implement an `IPostConfigureAccountExternalProviderOptions` to initialize again after dynamic properties are initialized.

16
docs/en/modules/gdpr.md

@ -48,7 +48,14 @@ The "Personal Data" page is used to manage personal data requests. You can view
![gdpr](../images/gdpr-personal-data-page.png)
To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr).
The GDPR module is designed for distributed architectures. When a user requests their personal data, the module publishes two events:
- `GdprUserDataRequestedEto`: Triggers personal data collectors to prepare user data
- `GdprUserDataDeletionRequestedEto`: Triggers personal data collectors to delete user data
You can subscribe to these events to implement custom data collection and deletion logic in your modules. See the [Distributed Events](#distributed-events) section for more details.
> To see the other features of the GDPR module, visit [the module description page](https://abp.io/modules/Volo.Gdpr).
## Options
@ -227,8 +234,13 @@ This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#e
### GdprUserDataPreparedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file by module.
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is used to save the collected personal data into a single JSON file per module. Typically, you don't need to implement this event handler since the module already has an implementation that returns the collected data within a zip file containing multiple JSON files, with each file containing data collected from a specific module.
### GdprUserDataDeletionRequestedEto
This [Event Transfer Object](../framework/infrastructure/event-bus/distributed#event-transfer-object) is published when a user requests to permanently delete their personal data and account. By default, only the `IdentityGdprEventHandler` in the [Identity Pro Module](../modules/identity-pro) subscribes to this event to anonymize the user's data and delete their account (using soft-delete unless configured otherwise).
If you want to delete additional sensitive user data stored in other modules, you can subscribe to this event and implement custom deletion (or anonymization) logic in those modules.
## Cookie Consent

2
docs/en/release-info/migration-guides/identityserver4-step-by-step.md

@ -76,7 +76,7 @@ typeof(AbpPermissionManagementDomainIdentityServerModule),
DataSeeder is the most important part for starting the application since it seeds the initial data for both OpenID providers.
- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/master/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name.
- Create a folder named *IdentityServer* under the Domain project and copy the [IdentityServerDataSeedContributor.cs](https://github.com/abpframework/abp-samples/blob/1dc297255ca22af02ef6d71092dbc1b394f9260a/Ids2OpenId/src/Ids2OpenId.Domain/IdentityServer/IdentityServerDataSeedContributor.cs) under this folder. **Rename** all the `OpenId2Ids` with your project name.
- Delete *OpenIddict* folder that contains `OpenIddictDataSeedContributor.cs` which is no longer needed.
### EntityFrameworkCore Layer

29
docs/en/solution-templates/layered-web-application/deployment/deployment-iis.md

@ -254,6 +254,35 @@ We can visit the websites from a browser.
![Tiered IIS deployment](../../../images/iis-sample-tiered-deployment.gif)
## Fix 405 Method Not Allowed Error
Remove `WebDAV` modules and handlers from the `Web.config` file.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<modules>
<remove name="WebDAVModule" />
</modules>
<handlers>
<remove name="WebDAV" />
</handlers>
</system.webServer>
</configuration>
```
Also remove the `WebDAV Publishing` feature from your computer if it's not being used. To do so, follow these steps:
1. Select Start, type Turn Windows features on or off in the Start Search box, and then select Turn Windows features on or off.
2. In the Windows Features window, expand Internet Information Services -> World Wide Web Services -> Common HTTP Features.
3. Uncheck the WebDAV Publishing feature.
See:
- https://learn.microsoft.com/en-us/aspnet/web-api/overview/testing-and-debugging/troubleshooting-http-405-errors-after-publishing-web-api-applications#resolve-http-405-errors
- https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/site-behavior-performance/http-error-405-website#resolution-for-cause-3
## How to get stdout-log
If your application is running on IIS and getting errors like `502.5, 500.3x`, you can enable stdout logs to see the error details.

14
framework/Volo.Abp.sln

@ -481,6 +481,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Sms.TencentCloud.Tests", "test\Volo.Abp.Sms.TencenCloud.Tests\Volo.Abp.Sms.TencentCloud.Tests.csproj", "{C753DDD6-5699-45F8-8669-08CE0BB816DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Bundling", "src\Volo.Abp.AspNetCore.Bundling\Volo.Abp.AspNetCore.Bundling.csproj", "{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling", "src\Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling\Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj", "{70720321-DED4-464F-B913-BDA5BBDD7982}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny", "src\Volo.Abp.BlobStoring.Bunny\Volo.Abp.BlobStoring.Bunny.csproj", "{1BBCBA72-CDB6-4882-96EE-D4CD149433A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny.Tests", "test\Volo.Abp.BlobStoring.Bunny.Tests\Volo.Abp.BlobStoring.Bunny.Tests.csproj", "{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915}"
@ -1441,6 +1445,14 @@ Global
{C753DDD6-5699-45F8-8669-08CE0BB816DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C753DDD6-5699-45F8-8669-08CE0BB816DE}.Release|Any CPU.Build.0 = Release|Any CPU
{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C}.Release|Any CPU.Build.0 = Release|Any CPU
{70720321-DED4-464F-B913-BDA5BBDD7982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70720321-DED4-464F-B913-BDA5BBDD7982}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70720321-DED4-464F-B913-BDA5BBDD7982}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70720321-DED4-464F-B913-BDA5BBDD7982}.Release|Any CPU.Build.0 = Release|Any CPU
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -1695,6 +1707,8 @@ Global
{E50739A7-5E2F-4EB5-AEA9-554115CB9613} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{BE7109C5-7368-4688-8557-4A15D3F4776A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{C753DDD6-5699-45F8-8669-08CE0BB816DE} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{75AA8A90-B3F6-43DF-ADA7-0990DEF44E2C} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{70720321-DED4-464F-B913-BDA5BBDD7982} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64} = {447C8A77-E5F0-4538-8687-7383196D04EA}

19
framework/src/Volo.Abp.AspNetCore.Bundling/Volo.Abp.AspNetCore.Bundling.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Minify\Volo.Abp.Minify.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions.csproj" />
</ItemGroup>
</Project>

15
framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/AbpAspNetCoreBundlingModule.cs

@ -0,0 +1,15 @@
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Minify;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Bundling;
[DependsOn(
typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule),
typeof(AbpMinifyModule),
typeof(AbpVirtualFileSystemModule)
)]
public class AbpAspNetCoreBundlingModule : AbpModule
{
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCache.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleCache.cs

@ -1,9 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public class BundleCache : IBundleCache, ISingletonDependency
{

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleCacheItem.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleCacheItem.cs

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public class BundleCacheItem
{

248
framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleManagerBase.cs

@ -0,0 +1,248 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.AspNetCore.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Bundling;
public abstract class BundleManagerBase : IBundleManager
{
public ILogger<BundleManagerBase> Logger { get; set; }
protected readonly AbpBundlingOptions Options;
protected readonly AbpBundleContributorOptions ContributorOptions;
protected readonly IScriptBundler ScriptBundler;
protected readonly IStyleBundler StyleBundler;
protected readonly IServiceProvider ServiceProvider;
protected readonly IDynamicFileProvider DynamicFileProvider;
protected readonly IBundleCache BundleCache;
public BundleManagerBase(
IOptions<AbpBundlingOptions> options,
IOptions<AbpBundleContributorOptions> contributorOptions,
IScriptBundler scriptBundler,
IStyleBundler styleBundler,
IServiceProvider serviceProvider,
IDynamicFileProvider dynamicFileProvider,
IBundleCache bundleCache)
{
Options = options.Value;
ContributorOptions = contributorOptions.Value;
ScriptBundler = scriptBundler;
ServiceProvider = serviceProvider;
DynamicFileProvider = dynamicFileProvider;
BundleCache = bundleCache;
StyleBundler = styleBundler;
Logger = NullLogger<BundleManagerBase>.Instance;
}
public virtual async Task<IReadOnlyList<BundleFile>> GetStyleBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.StyleBundles, bundleName, StyleBundler);
}
public virtual async Task<IReadOnlyList<BundleFile>> GetScriptBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.ScriptBundles, bundleName, ScriptBundler);
}
protected virtual async Task<IReadOnlyList<BundleFile>> GetBundleFilesAsync(BundleConfigurationCollection bundles,
string bundleName, IBundler bundler)
{
var files = new List<BundleFile>();
var contributors = GetContributors(bundles, bundleName);
var bundleFiles = await GetBundleFilesAsync(contributors);
var dynamicResources = await GetDynamicResourcesAsync(contributors);
if (!IsBundlingEnabled())
{
return bundleFiles.Union(dynamicResources).ToImmutableList();
}
var localBundleFiles = new List<string>();
foreach (var bundleFile in bundleFiles)
{
if (!bundleFile.IsExternalFile)
{
localBundleFiles.Add(bundleFile.FileName);
}
else
{
if (localBundleFiles.Count != 0)
{
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
localBundleFiles.Clear();
}
files.Add(bundleFile);
}
}
if (localBundleFiles.Count != 0)
{
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
}
return files.Union(dynamicResources).ToImmutableList();
}
private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List<string> bundleFiles)
{
var bundleRelativePath =
Options.BundleFolderName.EnsureEndsWith('/') +
bundleName + "." + bundleFiles.JoinAsString("|").ToMd5() + "." + bundler.FileExtension;
return BundleCache.GetOrAdd(bundleRelativePath, () =>
{
var cacheValue = new BundleCacheItem(
new List<BundleFile> { new BundleFile("/" + bundleRelativePath) }
);
WatchChanges(cacheValue, bundleFiles, bundleRelativePath);
var bundleResult = bundler.Bundle(
new BundlerContext(
bundleRelativePath,
bundleFiles,
IsMinficationEnabled()
)
);
SaveBundleResult(bundleRelativePath, bundleResult);
return cacheValue;
});
}
private void WatchChanges(BundleCacheItem cacheValue, List<string> files, string bundleRelativePath)
{
lock (cacheValue.WatchDisposeHandles)
{
foreach (var file in files)
{
var watchDisposeHandle = GetFileProvider().Watch(file).RegisterChangeCallback(_ =>
{
lock (cacheValue.WatchDisposeHandles)
{
cacheValue.WatchDisposeHandles.ForEach(h => h.Dispose());
cacheValue.WatchDisposeHandles.Clear();
}
BundleCache.Remove(bundleRelativePath);
DynamicFileProvider.Delete("/wwwroot/" + bundleRelativePath); //TODO: get rid of wwwroot!
}, null);
cacheValue.WatchDisposeHandles.Add(watchDisposeHandle);
}
}
}
protected virtual void SaveBundleResult(string bundleRelativePath, BundleResult bundleResult)
{
var fileName = bundleRelativePath.Substring(bundleRelativePath.IndexOf('/') + 1);
DynamicFileProvider.AddOrUpdate(
new InMemoryFileInfo(
"/wwwroot/" + bundleRelativePath, //TODO: get rid of wwwroot!
Encoding.UTF8.GetBytes(bundleResult.Content),
fileName
)
);
}
public abstract bool IsBundlingEnabled();
protected abstract bool IsMinficationEnabled();
protected virtual async Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors)
{
var context = CreateBundleConfigurationContext();
foreach (var contributor in contributors)
{
await contributor.PreConfigureBundleAsync(context);
}
foreach (var contributor in contributors)
{
await contributor.ConfigureBundleAsync(context);
}
foreach (var contributor in contributors)
{
await contributor.PostConfigureBundleAsync(context);
}
return context.Files;
}
protected virtual async Task<List<BundleFile>> GetDynamicResourcesAsync(List<IBundleContributor> contributors)
{
var context = CreateBundleConfigurationContext();
foreach (var contributor in contributors)
{
await contributor.ConfigureDynamicResourcesAsync(context);
}
return context.Files;
}
protected virtual BundleConfigurationContext CreateBundleConfigurationContext()
{
return new BundleConfigurationContext(ServiceProvider, GetFileProvider(),
Options.Parameters);
}
protected abstract IFileProvider GetFileProvider();
protected virtual List<IBundleContributor> GetContributors(BundleConfigurationCollection bundles, string bundleName)
{
var contributors = new List<IBundleContributor>();
AddContributorsWithBaseBundles(contributors, bundles, bundleName);
for (var i = 0; i < contributors.Count; ++i)
{
var extensions = ContributorOptions.Extensions(contributors[i].GetType()).GetAll();
if (extensions.Count > 0)
{
contributors.InsertRange(i + 1, extensions);
i += extensions.Count;
}
}
return contributors;
}
protected virtual void AddContributorsWithBaseBundles(List<IBundleContributor> contributors,
BundleConfigurationCollection bundles, string bundleName)
{
var bundleConfiguration = bundles.Get(bundleName);
foreach (var baseBundleName in bundleConfiguration.BaseBundles)
{
AddContributorsWithBaseBundles(contributors, bundles, baseBundleName); //Recursive call
}
var selfContributors = bundleConfiguration.Contributors.GetAll();
if (selfContributors.Any())
{
contributors.AddRange(selfContributors);
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleResult.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundleResult.cs

@ -1,4 +1,4 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public class BundleResult
{

14
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerBase.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundlerBase.cs

@ -1,31 +1,27 @@
using System;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Minify;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public abstract class BundlerBase : IBundler, ITransientDependency
{
private static string[] _minFileSuffixes = { "min", "prod" };
public ILogger<BundlerBase> Logger { get; set; }
protected IWebHostEnvironment HostEnvironment { get; }
protected IMinifier Minifier { get; }
protected AbpBundlingOptions BundlingOptions { get; }
protected BundlerBase(
IWebHostEnvironment hostEnvironment,
IMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions)
{
HostEnvironment = hostEnvironment;
Minifier = minifier;
BundlingOptions = bundlingOptions.Value;
@ -123,7 +119,7 @@ public abstract class BundlerBase : IBundler, ITransientDependency
protected virtual IFileInfo GetFileInfo(IBundlerContext context, string file)
{
var fileInfo = HostEnvironment.WebRootFileProvider.GetFileInfo(file);
var fileInfo = FindFileInfo(file);
if (!fileInfo.Exists)
{
@ -150,7 +146,7 @@ public abstract class BundlerBase : IBundler, ITransientDependency
{
foreach (var suffix in _minFileSuffixes)
{
var fileInfo = HostEnvironment.WebRootFileProvider.GetFileInfo(
var fileInfo = FindFileInfo(
$"{file.RemovePostFix($".{FileExtension}")}.{suffix}.{FileExtension}"
);
@ -163,6 +159,8 @@ public abstract class BundlerBase : IBundler, ITransientDependency
return null;
}
protected abstract IFileInfo FindFileInfo(string file);
protected virtual string ProcessBeforeAddingToTheBundle(IBundlerContext context, string filePath, string fileContent)
{
return fileContent;

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundlerContext.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/BundlerContext.cs

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public class BundlerContext : IBundlerContext
{

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleCache.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundleCache.cs

@ -1,6 +1,6 @@
using System;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public interface IBundleCache
{

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundleManager.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundleManager.cs

@ -1,7 +1,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public interface IBundleManager
{

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundler.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundler.cs

@ -1,4 +1,6 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public interface IBundler
{

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/IBundlerContext.cs → framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/IBundlerContext.cs

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Bundling;
public interface IBundlerContext
{

6
framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/Scripts/IScriptBundler.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.AspNetCore.Bundling.Scripts;
public interface IScriptBundler : IBundler
{
}

6
framework/src/Volo.Abp.AspNetCore.Bundling/Volo/Abp/AspNetCore/Bundling/Styles/IStyleBundler.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.AspNetCore.Bundling.Styles;
public interface IStyleBundler : IBundler
{
}

106
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorBundlingModule.cs

@ -0,0 +1,106 @@
using System.Text;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Bundling.Styles;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
[DependsOn(
typeof(AbpAspNetCoreComponentsMauiBlazorModule),
typeof(AbpAspNetCoreBundlingModule)
)]
public class AbpAspNetCoreComponentsMauiBlazorBundlingModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context));
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
await InitialGlobalAssetsAsync(context);
}
protected virtual async Task InitialGlobalAssetsAsync(ApplicationInitializationContext context)
{
var bundlingOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBundlingOptions>>().Value;
var logger = context.ServiceProvider.GetRequiredService<ILogger<AbpAspNetCoreComponentsMauiBlazorBundlingModule>>();
if (!bundlingOptions.GlobalAssets.Enabled)
{
return;
}
var bundleManager = context.ServiceProvider.GetRequiredService<BundleManager>();
var mauiBlazorContentFileProvider = context.ServiceProvider.GetRequiredService<IMauiBlazorContentFileProvider>();
var dynamicFileProvider = context.ServiceProvider.GetRequiredService<IDynamicFileProvider>();
if (!bundlingOptions.GlobalAssets.GlobalStyleBundleName.IsNullOrWhiteSpace())
{
var styleFiles = await bundleManager.GetStyleBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalStyleBundleName);
var styles = string.Empty;
foreach (var file in styleFiles)
{
var fileInfo = mauiBlazorContentFileProvider.GetFileInfo(file.FileName);
if (!fileInfo.Exists)
{
logger.LogError($"Could not find the file: {file.FileName}");
continue;
}
var fileContent = await fileInfo.ReadAsStringAsync();
if (!bundleManager.IsBundlingEnabled())
{
fileContent = CssRelativePath.Adjust(fileContent,
file.FileName,
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"));
styles += $"/*{file.FileName}*/{Environment.NewLine}{fileContent}{Environment.NewLine}{Environment.NewLine}";
}
else
{
styles += $"{fileContent}{Environment.NewLine}{Environment.NewLine}";
}
}
dynamicFileProvider.AddOrUpdate(
new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.CssFileName,
Encoding.UTF8.GetBytes(styles),
bundlingOptions.GlobalAssets.CssFileName));
}
if (!bundlingOptions.GlobalAssets.GlobalScriptBundleName.IsNullOrWhiteSpace())
{
var scriptFiles = await bundleManager.GetScriptBundleFilesAsync(bundlingOptions.GlobalAssets.GlobalScriptBundleName);
var scripts = string.Empty;
foreach (var file in scriptFiles)
{
var fileInfo = mauiBlazorContentFileProvider.GetFileInfo(file.FileName);
if (!fileInfo.Exists)
{
logger.LogError($"Could not find the file: {file.FileName}");
continue;
}
var fileContent = await fileInfo.ReadAsStringAsync();
if (!bundleManager.IsBundlingEnabled())
{
scripts += $"{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}";
}
else
{
scripts += $"//{file.FileName}{Environment.NewLine}{fileContent.EnsureEndsWith(';')}{Environment.NewLine}{Environment.NewLine}";
}
}
dynamicFileProvider.AddOrUpdate(
new InMemoryFileInfo("/wwwroot/" + bundlingOptions.GlobalAssets.JavaScriptFileName,
Encoding.UTF8.GetBytes(scripts),
bundlingOptions.GlobalAssets.JavaScriptFileName));
}
}
}

13
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/AbpBlazorWebView.cs

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Components.WebView.Maui;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
public class AbpBlazorWebView : BlazorWebView
{
public override IFileProvider CreateFileProvider(string contentRootDir)
{
return new CompositeFileProvider(Handler!.GetRequiredService<IMauiBlazorContentFileProvider>(), base.CreateFileProvider(contentRootDir));
}
}

125
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/BundleManager.cs

@ -0,0 +1,125 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.AspNetCore.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
public class BundleManager : BundleManagerBase, ITransientDependency
{
protected IMauiBlazorContentFileProvider MauiBlazorContentFileProvider { get; }
public BundleManager(
IOptions<AbpBundlingOptions> options,
IOptions<AbpBundleContributorOptions> contributorOptions,
IScriptBundler scriptBundler,
IStyleBundler styleBundler,
IServiceProvider serviceProvider,
IDynamicFileProvider dynamicFileProvider,
IBundleCache bundleCache,
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider) : base(
options,
contributorOptions,
scriptBundler,
styleBundler,
serviceProvider,
dynamicFileProvider,
bundleCache)
{
MauiBlazorContentFileProvider = mauiBlazorContentFileProvider;
}
public override bool IsBundlingEnabled()
{
switch (Options.Mode)
{
case BundlingMode.None:
return false;
case BundlingMode.Bundle:
case BundlingMode.BundleAndMinify:
return true;
case BundlingMode.Auto:
return !IsDebug();
default:
throw new AbpException($"Unhandled {nameof(BundlingMode)}: {Options.Mode}");
}
}
protected async override Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors)
{
var files = await base.GetBundleFilesAsync(contributors);
foreach (var file in files)
{
await CopyFileToAppDataDirectoryAsync(file);
}
return files;
}
protected virtual async Task CopyFileToAppDataDirectoryAsync(BundleFile file)
{
if (file.IsExternalFile)
{
return;
}
var fileName = Path.Combine("wwwroot", file.FileName);
if(MauiBlazorContentFileProvider.GetFileInfo(fileName).Exists)
{
return;
}
try
{
await using var inputStream = await FileSystem.Current.OpenAppPackageFileAsync(fileName);
var targetFile = Path.Combine(FileSystem.Current.AppDataDirectory, fileName);
var fileDirectory = Path.GetDirectoryName(targetFile)!;
if (!Path.Exists(fileDirectory))
{
Directory.CreateDirectory(fileDirectory);
}
await using var outputStream = File.Create(targetFile);
await inputStream.CopyToAsync(outputStream);
}
catch (Exception e)
{
Logger.LogError($"Could not copy the file to the app data directory: {fileName}", e);
}
}
protected override bool IsMinficationEnabled()
{
switch (Options.Mode)
{
case BundlingMode.None:
case BundlingMode.Bundle:
return false;
case BundlingMode.BundleAndMinify:
return true;
case BundlingMode.Auto:
return !IsDebug();
default:
throw new AbpException($"Unhandled {nameof(BundlingMode)}: {Options.Mode}");
}
}
protected virtual bool IsDebug()
{
#if DEBUG
return true;
#else
retur false;
#endif
}
protected override IFileProvider GetFileProvider()
{
return MauiBlazorContentFileProvider;
}
}

8
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/IMauiBlazorContentFileProvide.cs

@ -0,0 +1,8 @@
using Microsoft.Extensions.FileProviders;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
public interface IMauiBlazorContentFileProvider : IFileProvider
{
string ContentRootPath { get; }
}

26
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorBundlerBase.cs

@ -0,0 +1,26 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Minify;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
public abstract class MauiBlazorBundlerBase : BundlerBase
{
protected IMauiBlazorContentFileProvider MauiBlazorContentFileProvider { get; }
public MauiBlazorBundlerBase(
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider,
IMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions) : base(minifier,
bundlingOptions)
{
MauiBlazorContentFileProvider = mauiBlazorContentFileProvider;
}
protected override IFileInfo FindFileInfo(string file)
{
return MauiBlazorContentFileProvider.GetFileInfo(file);
}
}

65
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/MauiBlazorContentFileProvider.cs

@ -0,0 +1,65 @@
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Microsoft.Maui.Controls.PlatformConfiguration;
using Volo.Abp.DependencyInjection;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling;
public class MauiBlazorContentFileProvider : IMauiBlazorContentFileProvider, ISingletonDependency
{
private readonly IVirtualFileProvider _virtualFileProvider;
private readonly IFileProvider _fileProvider;
private string _rootPath = "/wwwroot";
public MauiBlazorContentFileProvider(IVirtualFileProvider virtualFileProvider)
{
_virtualFileProvider = virtualFileProvider;
_fileProvider = CreateFileProvider();
}
public string ContentRootPath => FileSystem.Current.AppDataDirectory;
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return new NotFoundFileInfo(subpath);
}
var fileInfo = _fileProvider.GetFileInfo(subpath);
return fileInfo.Exists ? fileInfo : _fileProvider.GetFileInfo( _rootPath + subpath.EnsureStartsWith('/'));
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
if (string.IsNullOrEmpty(subpath))
{
return NotFoundDirectoryContents.Singleton;
}
var directory = _fileProvider.GetDirectoryContents(subpath);
return directory.Exists ? directory : _fileProvider.GetDirectoryContents( _rootPath + subpath.EnsureStartsWith('/'));
}
public IChangeToken Watch(string filter)
{
return new CompositeChangeToken(
[
_fileProvider.Watch(_rootPath + filter),
_fileProvider.Watch(filter)
]
);
}
protected virtual IFileProvider CreateFileProvider()
{
var assetsDirectory = Path.Combine(ContentRootPath, _rootPath.TrimStart('/'));
if (!Path.Exists(assetsDirectory))
{
Directory.CreateDirectory(assetsDirectory);
}
return new CompositeFileProvider(new PhysicalFileProvider(assetsDirectory), _virtualFileProvider);
}
}

28
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Scripts/ScriptBundler.cs

@ -0,0 +1,28 @@
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.Scripts;
public class ScriptBundler : MauiBlazorBundlerBase, IScriptBundler
{
public override string FileExtension => "js";
public ScriptBundler(
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider,
IJavascriptMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions)
: base(
mauiBlazorContentFileProvider,
minifier,
bundlingOptions)
{
}
protected override string ProcessBeforeAddingToTheBundle(IBundlerContext context, string filePath, string fileContent)
{
return fileContent.EnsureEndsWith(';') + Environment.NewLine;
}
}

40
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Styles/StyleBundler.cs

@ -0,0 +1,40 @@
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.Bundling.Styles;
using Volo.Abp.Minify.Styles;
namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.Styles;
public class StyleBundler : MauiBlazorBundlerBase, IStyleBundler
{
private readonly IMauiBlazorContentFileProvider _mauiBlazorContentFileProvider;
public override string FileExtension => "css";
public StyleBundler(
IMauiBlazorContentFileProvider mauiBlazorContentFileProvider,
ICssMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions)
: base(
mauiBlazorContentFileProvider,
minifier,
bundlingOptions)
{
_mauiBlazorContentFileProvider = mauiBlazorContentFileProvider;
}
public string GetAbsolutePath(string relativePath)
{
return Path.Combine(_mauiBlazorContentFileProvider.ContentRootPath, "wwwroot", relativePath.RemovePreFix("/")).Replace("file://", "");
}
protected override string ProcessBeforeAddingToTheBundle(IBundlerContext context, string filePath, string fileContent)
{
return CssRelativePath.Adjust(
fileContent,
GetAbsolutePath(filePath),
GetAbsolutePath(context.BundleRelativePath)
);
}
}

41
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling.csproj

@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" />
<PackageReference Include="Microsoft.Maui.Controls"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Bundling\Volo.Abp.AspNetCore.Bundling.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.MauiBlazor\Volo.Abp.AspNetCore.Components.MauiBlazor.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Platforms\Android\" />
<Folder Include="Platforms\iOS\" />
<Folder Include="Platforms\MacCatalyst\" />
<Folder Include="Platforms\Windows\" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.AspNetCore.Components.Server.Theming/Bundling/BlazorServerComponentBundleManager.cs

@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.Server.Theming.Bundling;

34
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor

@ -1,4 +1,6 @@
@implements IDisposable
@inject IComponentBundleManager BundleManager
@inject PersistentComponentState ApplicationState
@if (ScriptFiles != null)
{
foreach (var file in ScriptFiles)
@ -8,6 +10,9 @@
}
@code {
private const string PrerenderedKey = "abp_script_prerendered";
[Parameter]
public List<string>? WebAssemblyScriptFiles { get; set; }
@ -16,18 +21,33 @@
private List<string>? ScriptFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;
protected override async Task OnInitializedAsync()
{
ScriptFiles = new List<string>();
if (!BundleName.IsNullOrWhiteSpace())
_persistingSubscription = ApplicationState.RegisterOnPersisting(Callback);
if (!ApplicationState.TryTakeFromJson<string>(PrerenderedKey, out _))
{
ScriptFiles = (await BundleManager.GetScriptBundleFilesAsync(BundleName!)).ToList();
// We are in prerendering mode
if (!BundleName.IsNullOrWhiteSpace())
{
ScriptFiles = (await BundleManager.GetScriptBundleFilesAsync(BundleName!)).ToList();
}
}
if (OperatingSystem.IsBrowser() && WebAssemblyScriptFiles != null)
else
{
ScriptFiles.AddIfNotContains(WebAssemblyScriptFiles);
if (OperatingSystem.IsBrowser() && WebAssemblyScriptFiles != null)
{
ScriptFiles = WebAssemblyScriptFiles;
}
}
}
private Task Callback()
{
ApplicationState.PersistAsJson(PrerenderedKey, PrerenderedKey);
return Task.CompletedTask;
}
public void Dispose() => _persistingSubscription.Dispose();
}

46
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor

@ -1,4 +1,6 @@
@implements IDisposable
@inject IComponentBundleManager BundleManager
@inject PersistentComponentState ApplicationState
@if (StyleFiles != null)
{
foreach (var file in StyleFiles)
@ -8,6 +10,9 @@
}
@code {
private const string PrerenderedKey = "abp_style_prerendered";
[Parameter]
public List<string>? WebAssemblyStyleFiles { get; set; }
@ -16,18 +21,47 @@
private List<string>? StyleFiles { get; set; }
private PersistingComponentStateSubscription _persistingSubscription;
protected override async Task OnInitializedAsync()
{
StyleFiles = new List<string>();
if (!BundleName.IsNullOrWhiteSpace())
_persistingSubscription = ApplicationState.RegisterOnPersisting(Callback);
if (!ApplicationState.TryTakeFromJson<List<string>>(PrerenderedKey, out var scriptFiles))
{
StyleFiles = (await BundleManager.GetStyleBundleFilesAsync(BundleName!)).ToList();
// We are in prerendering mode
if (!BundleName.IsNullOrWhiteSpace())
{
StyleFiles = (await BundleManager.GetStyleBundleFilesAsync(BundleName!)).ToList();
}
}
else
{
StyleFiles = scriptFiles;
if (OperatingSystem.IsBrowser() && StyleFiles != null && WebAssemblyStyleFiles != null)
{
StyleFiles.AddIfNotContains(WebAssemblyStyleFiles);
}
}
}
if (OperatingSystem.IsBrowser() && WebAssemblyStyleFiles != null)
private bool _hasRemoveServerStyle = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!_hasRemoveServerStyle && OperatingSystem.IsBrowser() && WebAssemblyStyleFiles != null)
{
StyleFiles.AddIfNotContains(WebAssemblyStyleFiles);
_hasRemoveServerStyle = true;
await Task.Delay(3000);
StyleFiles = WebAssemblyStyleFiles;
StateHasChanged();
}
}
private Task Callback()
{
ApplicationState.PersistAsJson(PrerenderedKey, StyleFiles);
return Task.CompletedTask;
}
public void Dispose() => _persistingSubscription.Dispose();
}

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundleContributorOptions.cs → framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpBundleContributorOptions.cs

@ -1,6 +1,6 @@
using System;
using System;
using System.Collections.Concurrent;
using JetBrains.Annotations;
using System.Diagnostics.CodeAnalysis;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
@ -27,4 +27,4 @@ public class AbpBundleContributorOptions
_ => new BundleContributorCollection()
);
}
}
}

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj

@ -19,6 +19,7 @@
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions\Volo.Abp.AspNetCore.Mvc.UI.Bundling.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.Minify\Volo.Abp.Minify.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.Bundling\Volo.Abp.AspNetCore.Bundling.csproj" />
</ItemGroup>
</Project>

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Bundling.Styles;
@ -21,8 +22,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
[DependsOn(
typeof(AbpAspNetCoreMvcUiBootstrapModule),
typeof(AbpMinifyModule),
typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule)
typeof(AbpAspNetCoreBundlingModule)
)]
public class AbpAspNetCoreMvcUiBundlingModule : AbpModule
{

233
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/BundleManager.cs

@ -9,6 +9,10 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.AspNetCore.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles;
using Volo.Abp.AspNetCore.Mvc.UI.Resources;
@ -17,162 +21,47 @@ using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public class BundleManager : IBundleManager, ITransientDependency
public class BundleManager : BundleManagerBase, ITransientDependency
{
public ILogger<BundleManager> Logger { get; set; }
protected readonly AbpBundlingOptions Options;
protected readonly AbpBundleContributorOptions ContributorOptions;
protected readonly IWebHostEnvironment HostingEnvironment;
protected readonly IScriptBundler ScriptBundler;
protected readonly IStyleBundler StyleBundler;
protected readonly IServiceProvider ServiceProvider;
protected readonly IDynamicFileProvider DynamicFileProvider;
protected readonly IBundleCache BundleCache;
protected readonly IWebRequestResources RequestResources;
public BundleManager(
IOptions<AbpBundlingOptions> options,
IOptions<AbpBundleContributorOptions> contributorOptions,
IScriptBundler scriptBundler,
IScriptBundler scriptBundler,
IStyleBundler styleBundler,
IWebHostEnvironment hostingEnvironment,
IServiceProvider serviceProvider,
IDynamicFileProvider dynamicFileProvider,
IBundleCache bundleCache,
IWebRequestResources requestResources)
IWebHostEnvironment hostingEnvironment,
IWebRequestResources requestResources) : base(
options,
contributorOptions,
scriptBundler,
styleBundler,
serviceProvider,
dynamicFileProvider,
bundleCache)
{
Options = options.Value;
ContributorOptions = contributorOptions.Value;
HostingEnvironment = hostingEnvironment;
ScriptBundler = scriptBundler;
ServiceProvider = serviceProvider;
DynamicFileProvider = dynamicFileProvider;
BundleCache = bundleCache;
RequestResources = requestResources;
StyleBundler = styleBundler;
Logger = NullLogger<BundleManager>.Instance;
}
public virtual async Task<IReadOnlyList<BundleFile>> GetStyleBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.StyleBundles, bundleName, StyleBundler);
}
public virtual async Task<IReadOnlyList<BundleFile>> GetScriptBundleFilesAsync(string bundleName)
{
return await GetBundleFilesAsync(Options.ScriptBundles, bundleName, ScriptBundler);
}
protected virtual async Task<IReadOnlyList<BundleFile>> GetBundleFilesAsync(BundleConfigurationCollection bundles, string bundleName, IBundler bundler)
{
var files = new List<BundleFile>();
var contributors = GetContributors(bundles, bundleName);
var bundleFiles = RequestResources.TryAdd(await GetBundleFilesAsync(contributors));
var dynamicResources = RequestResources.TryAdd(await GetDynamicResourcesAsync(contributors));
if (!IsBundlingEnabled())
{
return bundleFiles.Union(dynamicResources).ToImmutableList();
}
var localBundleFiles = new List<string>();
foreach (var bundleFile in bundleFiles)
{
if (!bundleFile.IsExternalFile)
{
localBundleFiles.Add(bundleFile.FileName);
}
else
{
if (localBundleFiles.Count != 0)
{
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
localBundleFiles.Clear();
}
files.Add(bundleFile);
}
}
if (localBundleFiles.Count != 0)
{
files.AddRange(AddToBundleCache(bundleName, bundler, localBundleFiles).Files);
}
return files.Union(dynamicResources).ToImmutableList();
}
private BundleCacheItem AddToBundleCache(string bundleName, IBundler bundler, List<string> bundleFiles)
{
var bundleRelativePath =
Options.BundleFolderName.EnsureEndsWith('/') +
bundleName + "." + bundleFiles.JoinAsString("|").ToMd5() + "." + bundler.FileExtension;
return BundleCache.GetOrAdd(bundleRelativePath, () =>
{
var cacheValue = new BundleCacheItem(
new List<BundleFile>
{
new BundleFile("/" + bundleRelativePath)
}
);
WatchChanges(cacheValue, bundleFiles, bundleRelativePath);
protected IWebHostEnvironment HostingEnvironment { get; }
var bundleResult = bundler.Bundle(
new BundlerContext(
bundleRelativePath,
bundleFiles,
IsMinficationEnabled()
)
);
protected IWebRequestResources RequestResources { get; }
SaveBundleResult(bundleRelativePath, bundleResult);
return cacheValue;
});
}
private void WatchChanges(BundleCacheItem cacheValue, List<string> files, string bundleRelativePath)
protected async override Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors)
{
lock (cacheValue.WatchDisposeHandles)
{
foreach (var file in files)
{
var watchDisposeHandle = HostingEnvironment.WebRootFileProvider.Watch(file).RegisterChangeCallback(_ =>
{
lock (cacheValue.WatchDisposeHandles)
{
cacheValue.WatchDisposeHandles.ForEach(h => h.Dispose());
cacheValue.WatchDisposeHandles.Clear();
}
BundleCache.Remove(bundleRelativePath);
DynamicFileProvider.Delete("/wwwroot/" + bundleRelativePath); //TODO: get rid of wwwroot!
}, null);
cacheValue.WatchDisposeHandles.Add(watchDisposeHandle);
}
}
return RequestResources.TryAdd(await base.GetBundleFilesAsync(contributors));
}
protected virtual void SaveBundleResult(string bundleRelativePath, BundleResult bundleResult)
protected async override Task<List<BundleFile>> GetDynamicResourcesAsync(List<IBundleContributor> contributors)
{
var fileName = bundleRelativePath.Substring(bundleRelativePath.IndexOf('/') + 1);
DynamicFileProvider.AddOrUpdate(
new InMemoryFileInfo(
"/wwwroot/" + bundleRelativePath, //TODO: get rid of wwwroot!
Encoding.UTF8.GetBytes(bundleResult.Content),
fileName
)
);
return RequestResources.TryAdd(await base.GetDynamicResourcesAsync(contributors));
}
public virtual bool IsBundlingEnabled()
public override bool IsBundlingEnabled()
{
switch (Options.Mode)
{
@ -188,7 +77,7 @@ public class BundleManager : IBundleManager, ITransientDependency
}
}
protected virtual bool IsMinficationEnabled()
protected override bool IsMinficationEnabled()
{
switch (Options.Mode)
{
@ -204,78 +93,8 @@ public class BundleManager : IBundleManager, ITransientDependency
}
}
protected async Task<List<BundleFile>> GetBundleFilesAsync(List<IBundleContributor> contributors)
{
var context = CreateBundleConfigurationContext();
foreach (var contributor in contributors)
{
await contributor.PreConfigureBundleAsync(context);
}
foreach (var contributor in contributors)
{
await contributor.ConfigureBundleAsync(context);
}
foreach (var contributor in contributors)
{
await contributor.PostConfigureBundleAsync(context);
}
return context.Files;
}
protected virtual async Task<List<BundleFile>> GetDynamicResourcesAsync(List<IBundleContributor> contributors)
{
var context = CreateBundleConfigurationContext();
foreach (var contributor in contributors)
{
await contributor.ConfigureDynamicResourcesAsync(context);
}
return context.Files;
}
protected virtual BundleConfigurationContext CreateBundleConfigurationContext()
{
return new BundleConfigurationContext(ServiceProvider, HostingEnvironment.WebRootFileProvider, Options.Parameters);
}
protected virtual List<IBundleContributor> GetContributors(BundleConfigurationCollection bundles, string bundleName)
{
var contributors = new List<IBundleContributor>();
AddContributorsWithBaseBundles(contributors, bundles, bundleName);
for (var i = 0; i < contributors.Count; ++i)
{
var extensions = ContributorOptions.Extensions(contributors[i].GetType()).GetAll();
if (extensions.Count > 0)
{
contributors.InsertRange(i + 1, extensions);
i += extensions.Count;
}
}
return contributors;
}
protected virtual void AddContributorsWithBaseBundles(List<IBundleContributor> contributors, BundleConfigurationCollection bundles, string bundleName)
protected override IFileProvider GetFileProvider()
{
var bundleConfiguration = bundles.Get(bundleName);
foreach (var baseBundleName in bundleConfiguration.BaseBundles)
{
AddContributorsWithBaseBundles(contributors, bundles, baseBundleName); //Recursive call
}
var selfContributors = bundleConfiguration.Contributors.GetAll();
if (selfContributors.Any())
{
contributors.AddRange(selfContributors);
}
return HostingEnvironment.WebRootFileProvider;
}
}
}

25
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/MvcUiBundlerBase.cs

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.Minify;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling;
public abstract class MvcUiBundlerBase : BundlerBase
{
protected IWebHostEnvironment WebHostingEnvironment { get; }
protected MvcUiBundlerBase(
IWebHostEnvironment webHostingEnvironment,
IMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions) : base(minifier, bundlingOptions)
{
WebHostingEnvironment = webHostingEnvironment;
}
protected override IFileInfo FindFileInfo(string file)
{
return WebHostingEnvironment.WebRootFileProvider.GetFileInfo(file);
}
}

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Scripts/IScriptBundler.cs

@ -1,6 +0,0 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts;
public interface IScriptBundler : IBundler
{
}

8
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Scripts/ScriptBundler.cs

@ -1,20 +1,22 @@
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Scripts;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Scripts;
public class ScriptBundler : BundlerBase, IScriptBundler
public class ScriptBundler : MvcUiBundlerBase, IScriptBundler
{
public override string FileExtension => "js";
public ScriptBundler(
IWebHostEnvironment hostEnvironment,
IWebHostEnvironment hostingEnvironment,
IJavascriptMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions)
: base(
hostEnvironment,
hostingEnvironment,
minifier,
bundlingOptions)
{

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/IStyleBundler.cs

@ -1,6 +0,0 @@
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles;
public interface IStyleBundler : IBundler
{
}

11
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/Styles/StyleBundler.cs

@ -2,27 +2,28 @@ using System;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Bundling.Styles;
using Volo.Abp.Bundling.Styles;
using Volo.Abp.Minify.Styles;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.Styles;
public class StyleBundler : BundlerBase, IStyleBundler
public class StyleBundler : MvcUiBundlerBase, IStyleBundler
{
private readonly IWebHostEnvironment _hostingEnvironment;
public override string FileExtension => "css";
public StyleBundler(
IWebHostEnvironment hostEnvironment,
IWebHostEnvironment hostingEnvironment,
ICssMinifier minifier,
IOptions<AbpBundlingOptions> bundlingOptions)
: base(
hostEnvironment,
hostingEnvironment,
minifier,
bundlingOptions)
{
_hostingEnvironment = hostEnvironment;
_hostingEnvironment = hostingEnvironment;
}
public string GetAbsolutePath(string relativePath)

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperScriptService.cs

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperStyleService.cs

@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Bundling;
using Volo.Abp.AspNetCore.Security;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers;

11
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs

@ -109,9 +109,12 @@ public class AbpExceptionFilter : IAsyncExceptionFilter, IAbpFilter, ITransientD
remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService<IJsonSerializer>().Serialize(remoteServiceErrorInfo, indented: true));
var logger = context.GetService<ILogger<AbpExceptionFilter>>(NullLogger<AbpExceptionFilter>.Instance)!;
var logLevel = context.Exception.GetLogLevel();
logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
logger.LogException(context.Exception, logLevel);
if(exceptionHandlingOptions.ShouldLogException(context.Exception))
{
var logger = context.GetService<ILogger<AbpExceptionFilter>>(NullLogger<AbpExceptionFilter>.Instance)!;
var logLevel = context.Exception.GetLogLevel();
logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
logger.LogException(context.Exception, logLevel);
}
}
}

11
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionPageFilter.cs

@ -90,10 +90,12 @@ public class AbpExceptionPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDe
remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService<IJsonSerializer>().Serialize(remoteServiceErrorInfo, indented: true));
var logger = context.GetService<ILogger<AbpExceptionPageFilter>>(NullLogger<AbpExceptionPageFilter>.Instance)!;
logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
logger.LogException(context.Exception!, logLevel);
if (context.Exception != null && exceptionHandlingOptions.ShouldLogException(context.Exception))
{
var logger = context.GetService<ILogger<AbpExceptionPageFilter>>(NullLogger<AbpExceptionPageFilter>.Instance)!;
logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
logger.LogException(context.Exception!, logLevel);
}
await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(new ExceptionNotificationContext(context.Exception!));
@ -113,6 +115,7 @@ public class AbpExceptionPageFilter : IAsyncPageFilter, IAbpFilter, ITransientDe
}
else
{
var logger = context.GetService<ILogger<AbpExceptionPageFilter>>(NullLogger<AbpExceptionPageFilter>.Instance)!;
logger.LogWarning("HTTP response has already started, cannot set headers and status code!");
}

8
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs

@ -58,7 +58,12 @@ public class AbpExceptionHandlingMiddleware : AbpMiddlewareBase, ITransientDepen
private async Task HandleAndWrapException(HttpContext httpContext, Exception exception)
{
_logger.LogException(exception);
var exceptionHandlingOptions = httpContext.RequestServices.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value;
if (exceptionHandlingOptions.ShouldLogException(exception))
{
_logger.LogException(exception);
}
await httpContext
.RequestServices
@ -77,7 +82,6 @@ public class AbpExceptionHandlingMiddleware : AbpMiddlewareBase, ITransientDepen
var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>();
var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>();
var jsonSerializer = httpContext.RequestServices.GetRequiredService<IJsonSerializer>();
var exceptionHandlingOptions = httpContext.RequestServices.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value;
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCodeFinder.GetStatusCode(httpContext, exception);

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs

@ -107,6 +107,9 @@ public class SuiteCommand : IConsoleCommand, ITransientDependency
Logger.LogInformation("Removing ABP Suite...");
RemoveSuite();
break;
default:
throw new CliUsageException("Invalid Suite command! Run \"abp help suite\" command to see available Suite commands.");
}
}

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

@ -137,10 +137,18 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency
? $"The specified template version ({templateVersion}) is different than the CLI version ({currentCliVersion}). This may cause compatibility issues."
: $"The latest template version ({templateVersion}) is different than the CLI version ({currentCliVersion}). This may cause compatibility issues.");
Logger.LogWarning("Please upgrade/downgrade the CLI version to the template version.");
Logger.LogWarning($"> dotnet tool uninstall -g volo.abp.cli");
Logger.LogWarning(!templateVersion.IsPrerelease
? $"> dotnet tool install -g volo.abp.cli --version \"{templateVersion.Major}.{templateVersion.Minor}.*\""
: $"> dotnet tool install -g volo.abp.cli --version {templateVersion}");
if (currentCliVersion.ToString().EndsWith("-studio"))
{
Logger.LogWarning($"> abp install-old-cli --version {templateVersion}");
}
else
{
Logger.LogWarning($"> dotnet tool uninstall -g volo.abp.cli");
Logger.LogWarning(!templateVersion.IsPrerelease
? $"> dotnet tool install -g volo.abp.cli --version \"{templateVersion.Major}.{templateVersion.Minor}.*\""
: $"> dotnet tool install -g volo.abp.cli --version {templateVersion}");
}
if (userSpecifiedVersion)
{

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Version/CliVersionService.cs

@ -34,6 +34,11 @@ public class CliVersionService : ITransientDependency
break;
}
if (line.StartsWith("volo.abp.studio.cli", StringComparison.InvariantCultureIgnoreCase))
{
var assemblyVersion = string.Join(".", Assembly.GetExecutingAssembly().GetFileVersion().Split('.').Take(3));
return SemanticVersion.Parse(assemblyVersion + "-studio");
}
}
if (currentCliVersion == null)

1
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj

@ -22,7 +22,6 @@
<ItemGroup>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" VersionOverride="9.0.0-preview.7.24405.3" />
</ItemGroup>
</Project>

13
framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingOptions.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.AspNetCore.ExceptionHandling;
@ -11,6 +12,12 @@ public class AbpExceptionHandlingOptions
public List<Type> SendExceptionDataToClientTypes { get; set; }
/// <summary>
/// Selectors to exclude exception from logging.
/// If a selector returns true, the exception is not logged in to the logging.
/// </summary>
public List<Func<Exception, bool>> ExcludeExceptionFromLoggerSelectors { get; }
public AbpExceptionHandlingOptions()
{
SendExceptionsDetailsToClients = false;
@ -19,5 +26,11 @@ public class AbpExceptionHandlingOptions
[
typeof(IBusinessException)
];
ExcludeExceptionFromLoggerSelectors = new List<Func<Exception, bool>>();
}
public bool ShouldLogException(Exception exception)
{
return ExcludeExceptionFromLoggerSelectors.All(selector => !selector(exception));
}
}

9
modules/account/src/Volo.Abp.Account.Installer/InstallationNotes.md

@ -0,0 +1,9 @@
# Installation Notes for Account Module
Account module implements the basic authentication features like login, register, forgot password and account management.
This module is based on [Microsoft's Identity library](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-6.0&tabs=visual-studio) and the [Identity Module](https://docs.abp.io/en/abp/latest/modules/identity). It has [IdentityServer](https://docs.abp.io/en/abp/latest/modules/identity-server) integration (based on the [IdentityServer Module](https://docs.abp.io/en/abp/latest/modules/identity-server)) and [OpenIddict](https://github.com/openiddict) integration (based on the [Openiddict Module](https://docs.abp.io/en/abp/latest/modules/openiddict)) to provide single sign-on, access control and other advanced authentication features.
## Documentation
For detailed information and usage instructions, please visit the [Account Module documentation](https://abp.io/docs/latest/Modules/Account).

9
modules/audit-logging/src/Volo.Abp.AuditLogging.Installer/InstallationNotes.md

@ -0,0 +1,9 @@
# Installation Notes for Audit Logging Module
The ABP Audit Logging module provides automatic audit logging for web requests, service methods, and entity changes. It helps you track user activities and changes in your application.
This module is part of the ABP Framework and provides comprehensive audit logging capabilities including entity history tracking, exception logging, and user activity monitoring.
## Documentation
For detailed information and usage instructions, please visit the [Audit Logging documentation](https://abp.io/docs/latest/framework/infrastructure/audit-logging).

12
modules/background-jobs/src/Volo.Abp.BackgroundJobs.Installer/InstallationNotes.md

@ -0,0 +1,12 @@
# Installation Notes for Background Jobs Module
Background jobs are used to queue some tasks to be executed in the background. You may need background jobs for several reasons. Here are some examples:
- To perform **long-running tasks** without having the users wait. For example, a user presses a 'report' button to start a long-running reporting job. You add this job to the **queue** and send the report's result to your user via email when it's completed.
- To create **re-trying** and **persistent tasks** to **guarantee** that a code will be **successfully executed**. For example, you can send emails in a background job to overcome **temporary failures** and **guarantee** that it eventually will be sent. That way users do not wait while sending emails.
Background jobs are **persistent** that means they will be **re-tried** and **executed** later even if your application crashes.
## Documentation
For detailed information and usage instructions, please visit the [Background Jobs documentation](https://abp.io/docs/latest/framework/infrastructure/background-jobs).

2
modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor

@ -15,7 +15,7 @@
{
if (AbpAspNetCoreComponentsWebOptions.Value.IsBlazorWebApp)
{
Navigation.NavigateTo(AuthenticationOptions.Value.LogoutUrl, forceLoad: true);
Navigation.NavigateTo(AuthenticationOptions.Value.LoginUrl, forceLoad: true);
}
else
{

13
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Installer/InstallationNotes.md

@ -0,0 +1,13 @@
# Installation Notes for Basic Theme Module (MVC)
The Basic Theme is a theme implementation for the ASP.NET Core MVC / Razor Pages UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the base theme and build your own theme or styling on top of it. See the Customization section.
The Basic Theme has RTL (Right-to-Left language) support.
If you are looking for a professional, enterprise ready theme, you can check the [Lepton Theme](https://abp.io/themes), which is a part of the ABP.
See the [Theming document](https://github.com/abpframework/abp/blob/rel-9.1/docs/en/framework/ui/mvc-razor-pages/theming.md) to learn about themes.
## Documentation
For detailed information and usage instructions, please visit the [Basic Theme documentation](https://abp.io/docs/latest/framework/ui/mvc-razor-pages/basic-theme).

7
modules/basic-theme/src/Volo.Abp.BasicTheme.Installer/InstallationNotes.md

@ -0,0 +1,7 @@
# Installation Notes for Basic Theme Module (Blazor)
The Basic Theme is a theme implementation for the Blazor UI. It is a minimalist theme that doesn't add any styling on top of the plain [Bootstrap](https://getbootstrap.com/). You can take the Basic Theme as the base theme and build your own theme or styling on top of it. See the Customization section.
## Documentation
For detailed information and usage instructions, please visit the [Blazor UI Basic Theme documentation](https://abp.io/docs/latest/framework/ui/blazor/basic-theme?UI=BlazorServer).

16
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.Installer/InstallationNotes.md

@ -0,0 +1,16 @@
# Installation Notes for Blob Storing Database Module
It is typical to store file contents in an application and read these file contents on need. Not only files, but you may also need to save various types of large binary objects, a.k.a. [BLOBs](https://en.wikipedia.org/wiki/Binary_large_object), into a storage. For example, you may want to save user profile pictures.
A BLOB is a typically byte array. There are various places to store a BLOB item; storing in the local file system, in a shared database or on the [Azure BLOB storage](https://azure.microsoft.com/en-us/products/storage/blobs/) can be options.
The ABP provides an abstraction to work with BLOBs and provides some pre-built storage providers that you can easily integrate to. Having such an abstraction has some benefits;
You can easily integrate to your favorite BLOB storage provides with a few lines of configuration.
You can then easily change your BLOB storage without changing your application code.
If you want to create reusable application modules, you don't need to make assumption about how the BLOBs are stored.
ABP BLOB Storage system is also compatible to other ABP features like multi-tenancy.
## Documentation
For detailed information and usage instructions, please visit the [BLOB Storing documentation](https://abp.io/docs/latest/framework/infrastructure/blob-storing).

28
modules/blogging/src/Volo.Blogging.Installer/InstallationNotes.md

@ -0,0 +1,28 @@
# Installation Notes for Blogging Module
The ABP Blogging module provides a simple blogging system for ABP applications. It allows you to create and manage blogs, posts, tags, and comments. The module includes both a public interface for readers and an admin interface for content management.
Key features of the Blogging module:
- Multiple blog support
- Post management with rich text editing
- Commenting functionality
- Social media sharing
- Admin interface for content management
## Required Configurations
The Blogging module requires **permission** settings to be configured after installation. Ensure that the necessary roles have the appropriate access rights for managing blogs, posts, comments and others.
### Update Database
The Blogging module requires database migrations to be applied. Following installation, you must update the database to create the necessary tables.
### Permissions
Enable the following permissions for the roles that require access to the Blogging module:
![Blogging Permissions](blogging-permissions.png)
## Documentation
For detailed information and usage instructions, please visit the [Blogging Module documentation](https://abp.io/docs/latest/Modules/Cms-Kit/Blogging).

BIN
modules/blogging/src/Volo.Blogging.Installer/blogging-permissions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

48
modules/cms-kit/src/Volo.CmsKit.Installer/InstallationNotes.md

@ -0,0 +1,48 @@
# Installation Notes for CMS Kit Module
The ABP CMS Kit module provides a set of reusable Content Management System (CMS) features for your ABP-based applications. It offers ready-to-use UI components and APIs for common content management requirements.
This module is part of the ABP Framework and provides features like comments, ratings, tags, blogs, and more to help you build content-rich applications.
## Required Configurations
The CmsKit module requires **permission** settings to be configured after installation. Ensure that the necessary roles have the appropriate access rights for managing blogs, posts, comments and others.
### Enable CmsKit
To enable the CmsKit module, add the following line to the `GlobalFeatureConfigurator` class of your module:
```csharp
public static void Configure()
{
OneTimeRunner.Run(() =>
{
/* You can configure (enable/disable) global features of the used modules here.
* Please refer to the documentation to learn more about the Global Features System:
* https://docs.abp.io/en/abp/latest/Global-Features
*/
GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
{
cmsKit.EnableAll();
// or
// cmsKit.Tags.Enable();
// cmsKit.Comments.Enable();
});
});
}
```
### Database Migrations
The CmsKit module requires database migrations to be applied. After enable **CmsKit**, Add a new migration and update the database to create the necessary tables.
### Permissions
Enable the following permissions for the roles that require access to the CmsKit module:
![CmsKit Permissions](cmskit-permissions.png)
## Documentation
For detailed information and usage instructions, please visit the [CMS Kit documentation](https://abp.io/docs/latest/modules/cms-kit).

BIN
modules/cms-kit/src/Volo.CmsKit.Installer/cmskit-permissions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

5
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebModule.cs

@ -79,11 +79,6 @@ public class CmsKitPublicWebModule : AbpModule
options.DisableModule(CmsKitPublicRemoteServiceConsts.ModuleName);
});
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "CmsKit:";
});
if (GlobalFeatureManager.Instance.IsEnabled<PagesFeature>())
{
Configure<AbpEndpointRouterOptions>(options =>

12
modules/docs/Volo.Docs.abpmdl

@ -7,18 +7,6 @@
}
},
"packages": {
"VoloDocs.Web": {
"path": "app/VoloDocs.Web/VoloDocs.Web.abppkg",
"folder": "app"
},
"VoloDocs.EntityFrameworkCore": {
"path": "app/VoloDocs.EntityFrameworkCore/VoloDocs.EntityFrameworkCore.abppkg",
"folder": "app"
},
"VoloDocs.Migrator": {
"path": "app/VoloDocs.Migrator/VoloDocs.Migrator.abppkg",
"folder": "app"
},
"Volo.Docs.Web": {
"path": "src/Volo.Docs.Web/Volo.Docs.Web.abppkg",
"folder": "src"

31
modules/docs/src/Volo.Docs.Installer/InstallationNotes.md

@ -0,0 +1,31 @@
# Installation Notes for Docs Module
The ABP Docs module provides a complete documentation system for ABP applications. It allows you to create, manage, and publish documentation from various sources like GitHub, GitLab, or local file system. The module includes both a public interface for readers and an admin interface for documentation management.
Key features of the Docs module:
- Multiple documentation projects support
- Version control integration
- Markdown support
- Navigation generation
- Full-text search
- Multi-language support
- Admin interface for documentation management
## Required Configurations
The Docs module requires **permission** settings to be configured after installation and database update.
### Update Database
The Docs module requires database migrations to be applied. Following installation, you must update the database to create the necessary tables.
### Permissions
Enable the following permissions for the roles that require access to the Docs module:
![Docs Permissions](docs-permissions.png)
## Documentation
For detailed information and usage instructions, please visit the [Docs Module documentation](https://abp.io/docs/latest/Modules/Docs).

BIN
modules/docs/src/Volo.Docs.Installer/docs-permissions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

24
modules/feature-management/src/Volo.Abp.FeatureManagement.Installer/InstallationNotes.md

@ -0,0 +1,24 @@
# Installation Notes for Feature Management Module
The Feature Management module provides a way to define and manage features in an ABP application. Features are used to enable or disable specific functionalities of an application based on different conditions, such as tenant subscription levels or user preferences.
Key capabilities of the Feature Management module:
- Define features with different value types (boolean, numeric, etc.)
- Group features by providers (tenant, edition, etc.)
- Manage feature values through a user interface
- Check feature status in your application code
## NuGet Packages
The following NuGet packages are required for the Feature Management module:
- `Volo.Abp.FeatureManagement.Application`
- `Volo.Abp.FeatureManagement.HttpApi`
- `Volo.Abp.FeatureManagement.EntityFrameworkCore` (for EF Core)
- `Volo.Abp.FeatureManagement.MongoDB` (for MongoDB)
- `Volo.Abp.FeatureManagement.Web` (for MVC UI)
- `Volo.Abp.FeatureManagement.Blazor.Server` (for Blazor Server UI)
- `Volo.Abp.FeatureManagement.Blazor.WebAssembly` (for Blazor WebAssembly UI)
## Documentation
For detailed information and usage instructions, please visit the [Feature Management Module documentation](https://abp.io/docs/latest/Modules/Feature-Management).

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs

@ -12,4 +12,6 @@ public static class IdentityErrorCodes
public const string CanNotChangeTwoFactor = "Volo.Abp.Identity:010008";
public const string YouCannotDelegateYourself = "Volo.Abp.Identity:010009";
public const string ClaimNameExist = "Volo.Abp.Identity:010021";
public const string CanNotUpdateStaticClaimType = "Volo.Abp.Identity:010022";
public const string CanNotDeleteStaticClaimType = "Volo.Abp.Identity:010023";
}

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "لا يجوز تغيير إعداد عاملين.",
"Volo.Abp.Identity:010009": "لا يمكنك تفويض نفسك.",
"Volo.Abp.Identity:010021": "الاسم {0} موجود بالفعل.",
"Volo.Abp.Identity:010022": "لا يمكن تحديث نوع المطابقة.",
"Volo.Abp.Identity:010023": "لا يمكن حذف نوع المطابقة.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "العدد الأقصى المسموح به لعضوية الوحدة التنظيمية للمستخدم",
"ThisUserIsNotActiveMessage": "هذا المستخدم غير نشط.",
"Permission:IdentityManagement": "إدارة الهوية",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "Není povoleno měnit nastavení dvou faktorů.",
"Volo.Abp.Identity:010009": "Nemůžete delegovat své vlastní oprávnění.",
"Volo.Abp.Identity:010021": "Název '{0}' již existuje.",
"Volo.Abp.Identity:010022": "Nelze aktualizovat statický typ deklarace.",
"Volo.Abp.Identity:010023": "Nelze smazat statický typ deklarace.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Maximální povolený počet členů organizační jednotky pro uživatele",
"ThisUserIsNotActiveMessage": "Tento uživatel není aktivní.",
"Permission:IdentityManagement": "Správa identit",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "Die Zwei-Faktor-Einstellung dürfen nicht geändert werden.",
"Volo.Abp.Identity:010009": "Sie können sich nicht selbst delegieren.",
"Volo.Abp.Identity:010021": "Der Name existiert bereits {0}",
"Volo.Abp.Identity:010022": "Der Name kann nicht aktualisiert werden.",
"Volo.Abp.Identity:010023": "Der Name kann nicht gelöscht werden.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Maximal zulässige Anzahl an Mitgliedschaften in Organisationseinheiten für einen Benutzer",
"ThisUserIsNotActiveMessage": "Dieser Benutzer ist nicht aktiv.",
"Permission:IdentityManagement": "Identitätsverwaltung",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json

@ -76,6 +76,8 @@
"Volo.Abp.Identity:010008": "Δεν επιτρέπεται η αλλαγή της ρύθμισης δύο παραγόντων.",
"Volo.Abp.Identity:010009": "Δεν μπορείτε να αναθέσετε την εξουσιοδότησή σας σε εσάς ίδιο.",
"Volo.Abp.Identity:010021": "Το όνομα '{0}' υπάρχει ήδη.",
"Volo.Abp.Identity:010022": "Δεν μπορείτε να ενημερώσετε ένα στατικό τύπο δήλωσης.",
"Volo.Abp.Identity:010023": "Δεν μπορείτε να διαγράψετε ένα στατικό τύπο δήλωσης.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Μέγιστος επιτρεπόμενος αριθμός μελών μονάδας οργανισμού για έναν χρήστη",
"ThisUserIsNotActiveMessage": "Αυτός ο χρήστης δεν είναι ενεργός.",
"Permission:IdentityManagement": "Διαχείριση ταυτότητας",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json

@ -76,6 +76,8 @@
"Volo.Abp.Identity:010008": "Changing the two factor setting is not allowed.",
"Volo.Abp.Identity:010009": "You cannot delegate yourself!",
"Volo.Abp.Identity:010021": "Name exist: '{0}'",
"Volo.Abp.Identity:010022": "Can not update a static ClaimType.",
"Volo.Abp.Identity:010023": "Can not delete a static ClaimType.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organisation unit membership count for a user",
"Permission:IdentityManagement": "Identity management",
"Permission:RoleManagement": "Role management",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "It's not allowed to change two factor setting.",
"Volo.Abp.Identity:010009": "You can not delegate yourself.",
"Volo.Abp.Identity:010021": "Name exist: '{0}'.",
"Volo.Abp.Identity:010022": "Can not update a static ClaimType.",
"Volo.Abp.Identity:010023": "Can not delete a static ClaimType.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organization unit membership count for a user",
"ThisUserIsNotActiveMessage": "This user is not active.",
"Permission:IdentityManagement": "Identity management",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "No está permitido cambiar la configuración de autenticación de dos pasos.",
"Volo.Abp.Identity:010009": "No puedes delegar tu propia cuenta!",
"Volo.Abp.Identity:010021": "Ya existe un con el nombre '{0}'",
"Volo.Abp.Identity:010022": "No se puede actualizar un tipo de reclamación estática.",
"Volo.Abp.Identity:010023": "No se puede borrar un tipo de reclamación estática.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Número máximo de unidades organizativas por usuario",
"ThisUserIsNotActiveMessage": "Este usuario no está activo.",
"Permission:IdentityManagement": "Gestión de identidades",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "تغییر دادن تنظیمات دو مرحله ای مجاز نمی باشد.",
"Volo.Abp.Identity:010009": "شما نمی توانید خودتان را معرفی کنید.",
"Volo.Abp.Identity:010021": "نام {0} قبلاً استفاده گردیده است.",
"Volo.Abp.Identity:010022": "نمی توان نام نقش/وظیفه را تغییر داد.",
"Volo.Abp.Identity:010023": "نمی توان نقش/وظیفه را حذف کرد.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "حداکثر تعداد مجاز عضویت در یک واحد سازمان برای یک کاربر",
"ThisUserIsNotActiveMessage": "این کاربر غیرفعال میباشد.",
"Permission:IdentityManagement": "مدیریت هویت",

2
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json

@ -77,6 +77,8 @@
"Volo.Abp.Identity:010008": "Kahden tekijän asetusta ei saa muuttaa.",
"Volo.Abp.Identity:010009": "Et voi delegoida itseäsi.",
"Volo.Abp.Identity:010021": "{0} on jo olemassa.",
"Volo.Abp.Identity:010022": "Staattista väittämätunnusta ei voi päivittää.",
"Volo.Abp.Identity:010023": "Staattista väittämätunnusta ei voi poistaa.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Suurin sallittu organisaatioyksikön jäsenmäärä käyttäjälle",
"ThisUserIsNotActiveMessage": "Tämä käyttäjä ei ole aktiivinen.",
"Permission:IdentityManagement": "Identiteetin hallinta",

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save