Browse Source

Merge remote-tracking branch 'origin/dev' into maliming/replace-timeago

pull/26001/head
maliming 2 weeks ago
parent
commit
2d589c75e3
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 3
      .github/workflows/angular.yml
  2. 144
      .github/workflows/auto-merge-forward.yml
  3. 37
      .github/workflows/auto-pr.yml
  4. 3
      .github/workflows/image-compression.yml
  5. 98
      docs/en/framework/architecture/domain-driven-design/application-services.md
  6. 1
      docs/en/framework/ui/mvc-razor-pages/overall.md
  7. 1
      docs/en/framework/ui/mvc-razor-pages/theming.md
  8. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginScriptBundleContributor.cs
  9. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginStyleBundleContributor.cs
  10. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  11. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
  12. 93
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs
  13. 7
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs
  14. 7
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs
  15. 11
      framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs
  16. 24
      framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjector.cs
  17. 23
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/Book.cs
  18. 27
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyAppService.cs
  19. 35
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyProjectingAppService.cs
  20. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAppService.cs
  21. 46
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAsyncProjectionAppService.cs
  22. 34
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookCustomizedAppService.cs
  23. 15
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailAppService.cs
  24. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailDto.cs
  25. 18
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailProjector.cs
  26. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDto.cs
  27. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteAppService.cs
  28. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteDto.cs
  29. 48
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookObjectMapper.cs
  30. 29
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookPolicyCheckedAppService.cs
  31. 18
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookProjector.cs
  32. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookReadOnlyAppService.cs
  33. 106
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookRepository.cs
  34. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructAppService.cs
  35. 10
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructDto.cs
  36. 16
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructProjector.cs
  37. 33
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookWithoutProjectionAppService.cs
  38. 19
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/IBookNameSuffixProvider.cs
  39. 196
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/QueryProjection_Tests.cs
  40. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  41. 14
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionAppService.cs
  42. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionDto.cs
  43. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjector.cs
  44. 15
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionAppService.cs
  45. 9
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionDto.cs
  46. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjector.cs
  47. 51
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityAppService.cs
  48. 12
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityDto.cs
  49. 197
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/QueryProjection_Tests.cs
  50. 40
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/SqlCommandCapture.cs
  51. 39
      framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyQueryProjection_Tests.cs
  52. 11
      framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityQueryProjector.cs
  53. 15
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionAppService.cs
  54. 9
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionDto.cs
  55. 17
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjector.cs
  56. 46
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/QueryProjection_Tests.cs
  57. 5
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  58. 8
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js
  59. 24
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js
  60. 14
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css
  61. 2
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css.map
  62. 15
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss
  63. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css
  64. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js
  65. 9
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/permission-management-modal.js
  66. 1
      npm/packs/aspnetcore.mvc.ui.theme.shared/package.json
  67. 1
      npm/packs/docs/package.json
  68. 129
      npm/packs/malihu-custom-scrollbar-plugin/README.md
  69. 5
      npm/packs/malihu-custom-scrollbar-plugin/abp.resourcemapping.js
  70. 32
      npm/packs/malihu-custom-scrollbar-plugin/package.json
  71. 2
      npm/packs/select2/package.json

3
.github/workflows/angular.yml

@ -27,8 +27,9 @@ jobs:
build-test-lint: build-test-lint:
if: ${{ !github.event.pull_request.draft }} if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

144
.github/workflows/auto-merge-forward.yml

@ -0,0 +1,144 @@
name: Auto-merge forward
# Push to a rel-x.y branch opens a merge PR into the next newer rel-* line,
# or into dev when this line is the newest. Merge of that PR retriggers the
# next hop, so a bug-fix on rel-1.0 flows rel-1.0 -> rel-1.1 -> ... -> dev.
on:
push:
branches:
- 'rel-*'
workflow_dispatch:
concurrency:
group: auto-merge-forward-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: read
jobs:
forward:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve forward target
id: target
run: |
set -euo pipefail
SOURCE="${GITHUB_REF_NAME}"
if [[ ! "$SOURCE" =~ ^rel-[0-9]+\.[0-9]+$ ]]; then
echo "Not a rel-x.y branch ($SOURCE); skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
git fetch origin --prune
mapfile -t RELS < <(
git ls-remote --heads origin 'rel-*' \
| awk '{print $2}' \
| sed 's|refs/heads/||' \
| grep -E '^rel-[0-9]+\.[0-9]+$' \
| sort -t. -k1.5,1n -k2,2n
)
TARGET="dev"
found=0
for branch in "${RELS[@]}"; do
if [[ "$found" -eq 1 ]]; then
TARGET="$branch"
break
fi
if [[ "$branch" == "$SOURCE" ]]; then
found=1
fi
done
if [[ "$found" -eq 0 ]]; then
echo "::error::Source branch $SOURCE was not listed among origin rel-* heads."
exit 1
fi
if ! git rev-parse --verify "origin/$TARGET" >/dev/null 2>&1; then
echo "::error::Target branch origin/$TARGET does not exist."
exit 1
fi
if git merge-base --is-ancestor "origin/$SOURCE" "origin/$TARGET"; then
echo "origin/$SOURCE is already an ancestor of origin/$TARGET; nothing to forward."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "source=$SOURCE" >> "$GITHUB_OUTPUT"
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
echo "Auto-merge forward: $SOURCE -> $TARGET"
- name: Merge into forward branch
if: steps.target.outputs.skip != 'true'
id: merge
run: |
set -euo pipefail
SOURCE="${{ steps.target.outputs.source }}"
TARGET="${{ steps.target.outputs.target }}"
FORWARD_BRANCH="auto-merge-forward/${SOURCE}-to-${TARGET}-${{ github.run_number }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "$FORWARD_BRANCH" "origin/$TARGET"
if git merge --no-edit "origin/$SOURCE"; then
echo "conflict=false" >> "$GITHUB_OUTPUT"
else
git merge --abort
git checkout -B "$FORWARD_BRANCH" "origin/$SOURCE"
echo "conflict=true" >> "$GITHUB_OUTPUT"
echo "::warning::Merge conflict forwarding ${SOURCE} to ${TARGET}. PR left open for manual resolution."
fi
git push origin "$FORWARD_BRANCH"
echo "branch=$FORWARD_BRANCH" >> "$GITHUB_OUTPUT"
- name: Create pull request
if: steps.target.outputs.skip != 'true'
id: pr
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
SOURCE="${{ steps.target.outputs.source }}"
TARGET="${{ steps.target.outputs.target }}"
FORWARD_BRANCH="${{ steps.merge.outputs.branch }}"
CONFLICT="${{ steps.merge.outputs.conflict }}"
BODY="Automated forward merge of \`${SOURCE}\` into \`${TARGET}\`."
if [[ "$CONFLICT" == "true" ]]; then
BODY+=$'\n\n**Merge conflict:** this branch is \`${SOURCE}\` as-is. Resolve against \`${TARGET}\` before merging.'
fi
URL="$(gh pr create \
--base "$TARGET" \
--head "$FORWARD_BRANCH" \
--title "Auto-merge forward ${SOURCE} → ${TARGET}" \
--body "$BODY")"
echo "url=$URL" >> "$GITHUB_OUTPUT"
echo "Created $URL"
# BOT_SECRET, not github.token: a merge performed with the default token produces a push
# that triggers no workflow, which would stop the chain at the first hop.
- name: Approve and auto-merge
if: steps.target.outputs.skip != 'true' && steps.merge.outputs.conflict != 'true'
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
set -euo pipefail
FORWARD_BRANCH="${{ steps.merge.outputs.branch }}"
gh pr review "$FORWARD_BRANCH" --approve
gh pr merge "$FORWARD_BRANCH" --merge --auto --delete-branch

37
.github/workflows/auto-pr.yml

@ -1,37 +0,0 @@
name: Merge branch dev with rel-10.7
on:
push:
branches:
- rel-10.7
permissions:
contents: read
jobs:
merge-dev-with-rel-10-7:
permissions:
contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: dev
- name: Reset promotion branch
run: |
git fetch origin rel-10.7:rel-10.7
git reset --hard rel-10.7
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-10-7/${{github.run_number}}
title: Merge branch dev with rel-10.7
body: This PR generated automatically to merge dev with rel-10.7. Please review the changed files before merging to prevent any errors that may occur.
draft: true
token: ${{ github.token }}
- name: Merge Pull Request
env:
GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: |
gh pr ready
gh pr review auto-merge/rel-10-7/${{github.run_number}} --approve
gh pr merge auto-merge/rel-10-7/${{github.run_number}} --merge --auto --delete-branch

3
.github/workflows/image-compression.yml

@ -22,9 +22,10 @@ jobs:
if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft
name: calibreapp/image-actions name: calibreapp/image-actions
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Compress Images - name: Compress Images
uses: calibreapp/image-actions@main uses: calibreapp/image-actions@main

98
docs/en/framework/architecture/domain-driven-design/application-services.md

@ -444,6 +444,7 @@ These methods are low level methods that can control how to query entities from
* `ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP automatically understands it and performs the paging. * `ApplyPaging` is used to make paging on the query. If your `TGetListInput` already implements `IPagedResultRequest`, you don't need to override this since the ABP automatically understands it and performs the paging.
* `ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creation time, if your entity implements the standard `IHasCreationTime` interface. * `ApplySorting` is used to sort (order by...) the query. If your `TGetListInput` already implements the `ISortedResultRequest`, ABP automatically sorts the query. If not, it fallbacks to the `ApplyDefaultSorting` which tries to sort by creation time, if your entity implements the standard `IHasCreationTime` interface.
* `GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default. * `GetEntityByIdAsync` is used to get an entity by id, which calls `Repository.GetAsync(id)` by default.
* `CreateEntityQueryOrNullAsync` is used to create a query for a single entity by id, which is only needed for the *Query Projection* explained below. It returns `null` if the application service can not create such a query, then `GetEntityByIdAsync` is used.
* `DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default. * `DeleteByIdAsync` is used to delete an entity by id, which calls `Repository.DeleteAsync(id)` by default.
#### Object to Object Mapping #### Object to Object Mapping
@ -456,6 +457,103 @@ These methods are used to convert Entities to DTOs and vice verse. They use the
* `MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`. * `MapToEntityAsync(TCreateInput)` is used to create an entity from `TCreateInput`.
* `MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`. * `MapToEntityAsync(TUpdateInput, TEntity)` is used to update an existing entity from `TUpdateInput`.
#### Query Projection
`GetAsync` and `GetListAsync` get the entities from the database, then map them to DTOs in the memory. If your DTO uses only a few properties of a large entity, you can project the query to the DTO instead, so the database returns only the columns you need.
Implement the `IQueryProjector<TEntity, TDto>` interface to define a projection:
````csharp
using System.Linq;
using Volo.Abp.ObjectMapping;
namespace MyProject.Books;
public class BookProjector : IQueryProjector<Book, BookDto>
{
public IQueryable<BookDto> ProjectTo(IQueryable<Book> source)
{
return source.Select(book => new BookDto
{
Id = book.Id,
Name = book.Name
});
}
}
````
You don't have to write the `Select` by hand. Both [Mapperly](https://mapperly.riok.app/) and [AutoMapper](https://docs.automapper.org) can project an `IQueryable`, refer to their own documentation for it and to the [object to object mapping document](../../infrastructure/object-to-object-mapping.md) for their ABP integrations. Your existing maps are not used for the projection, a projector is always a class implementing `IQueryProjector<TSource, TDestination>`.
ABP registers the projectors by convention, you don't need to configure anything else. Implement a projector once for an entity and DTO pair, and use the `ReplaceServices` option of the `DependencyAttribute` to replace an existing one. Filters (like soft delete and multi-tenancy), sorting and paging are still applied to the query before the projection.
> A projection must return one row per entity. The total count and the paging are calculated on the entity query before the projection runs, so a projection that filters out rows (an inner join to an optional relation) or multiplies them (a join to a collection) returns a page that doesn't match the reported total count. Use a left join for optional relations.
The projector is synchronous, so it can not obtain the query of another aggregate root, which is only
available through the asynchronous `GetQueryableAsync`. Override `CreateGetOutputDtoQueryOrNullAsync` or
`CreateGetListOutputDtoQueryOrNullAsync` for that. They replace the projector for that application service:
````csharp
public class BookAppService : ReadOnlyAppService<Book, BookDto, Guid>
{
private readonly IBookDtoQuery _bookDtoQuery;
//...
protected override async Task<IQueryable<BookDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<Book> query)
{
return await _bookDtoQuery.ProjectAsync(query);
}
}
//The projection is a class of its own, so the other application services returning a BookDto reuse it
public class BookDtoQuery : IBookDtoQuery, ITransientDependency
{
private readonly IReadOnlyRepository<Author, Guid> _authorRepository;
//...
public async Task<IQueryable<BookDto>> ProjectAsync(IQueryable<Book> books)
{
var authors = await _authorRepository.GetQueryableAsync();
return from book in books
join author in authors on book.AuthorId equals author.Id into bookAuthors
from bookAuthor in bookAuthors.DefaultIfEmpty()
select new BookDto
{
Id = book.Id,
Name = book.Name,
AuthorName = bookAuthor != null ? bookAuthor.Name : null
};
}
}
````
Both queries must come from the same database context, otherwise they can not be executed as a single query,
and the provider has to be able to translate the join. The one row per entity rule above applies here too,
that's why the example uses a left join. A joined column can not be used for the sorting, and the paging is
based on the entity query, since both are applied before this method is called.
A projector is resolved by the `(entity, DTO)` type pair, just like an `IObjectMapper<TSource, TDestination>`, so registering one enables the projection for every application service using that pair. It replaces the way the DTOs are read:
* `GetListAsync` doesn't use `MapToGetListOutputDtosAsync` anymore.
* `GetAsync` doesn't use `GetEntityByIdAsync` and `MapToGetOutputDtoAsync` anymore, as long as the application service can create a query for a single entity. `ReadOnlyAppService` and `CrudAppService` already do that. A class deriving from `AbstractKeyReadOnlyAppService` has to override `CreateEntityQueryOrNullAsync`, otherwise `GetAsync` keeps loading the entity and mapping it.
The rest of the pipeline is untouched. The authorization policies are still checked, `CreateFilteredQueryAsync`, `ApplySorting` and `ApplyPaging` are still used, the data filters (like soft delete and multi-tenancy) are still applied, and the create, update and delete methods still use the [IObjectMapper](../../infrastructure/object-to-object-mapping.md).
> If an application service needs to keep using the entity based extension points, override the `GetOutputDtoQueryProjector` or `GetListOutputDtoQueryProjector` property and return `null`:
````csharp
public class BookAppService : CrudAppService<Book, BookDto, Guid>
{
protected override IQueryProjector<Book, BookDto>? GetOutputDtoQueryProjector => null;
protected override IQueryProjector<Book, BookDto>? GetListOutputDtoQueryProjector => null;
//...
}
````
## Miscellaneous ## Miscellaneous
### Working with Streams ### Working with Streams

1
docs/en/framework/ui/mvc-razor-pages/overall.md

@ -64,7 +64,6 @@ There are a set of standard JavaScript/CSS libraries that comes pre-installed an
- [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers. - [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers.
- [Select2](https://select2.org/) for better select/combo boxes. - [Select2](https://select2.org/) for better select/combo boxes.
- [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps. - [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps.
- [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars.
You can use these libraries directly in your applications, without needing to manually import your page. You can use these libraries directly in your applications, without needing to manually import your page.

1
docs/en/framework/ui/mvc-razor-pages/theming.md

@ -51,7 +51,6 @@ All the themes must depend on the [@abp/aspnetcore.mvc.ui.theme.shared](https://
* [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers. * [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers.
* [Select2](https://select2.org/) for better select/combo boxes. * [Select2](https://select2.org/) for better select/combo boxes.
* [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps. * [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps.
* [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars.
These libraries are selected as the base libraries and available to the applications and modules. These libraries are selected as the base libraries and available to the applications and modules.

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginScriptBundleContributor.cs

@ -1,12 +0,0 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar;
public class MalihuCustomScrollbarPluginScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js");
}
}

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginStyleBundleContributor.cs

@ -1,12 +0,0 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar;
public class MalihuCustomScrollbarPluginStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.css");
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs

@ -7,7 +7,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidationUnobtrusive; using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidationUnobtrusive;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Lodash; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Lodash;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Luxon; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Luxon;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.SweetAlert2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.SweetAlert2;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago;
@ -23,7 +22,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
typeof(Select2ScriptContributor), typeof(Select2ScriptContributor),
typeof(DatatablesNetBs5ScriptContributor), typeof(DatatablesNetBs5ScriptContributor),
typeof(Sweetalert2ScriptContributor), typeof(Sweetalert2ScriptContributor),
typeof(MalihuCustomScrollbarPluginScriptBundleContributor),
typeof(LuxonScriptContributor), typeof(LuxonScriptContributor),
typeof(TimeagoScriptContributor), typeof(TimeagoScriptContributor),
typeof(BootstrapDatepickerScriptContributor), typeof(BootstrapDatepickerScriptContributor),

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs

@ -5,7 +5,6 @@ using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDaterangepicker;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Core;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5; using Volo.Abp.AspNetCore.Mvc.UI.Packages.DatatablesNetBs5;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome; using Volo.Abp.AspNetCore.Mvc.UI.Packages.FontAwesome;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Select2;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -16,7 +15,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
typeof(BootstrapStyleContributor), typeof(BootstrapStyleContributor),
typeof(FontAwesomeStyleContributor), typeof(FontAwesomeStyleContributor),
typeof(Select2StyleContributor), typeof(Select2StyleContributor),
typeof(MalihuCustomScrollbarPluginStyleBundleContributor),
typeof(DatatablesNetBs5StyleContributor), typeof(DatatablesNetBs5StyleContributor),
typeof(BootstrapDatepickerStyleContributor), typeof(BootstrapDatepickerStyleContributor),
typeof(BootstrapDaterangepickerStyleContributor) typeof(BootstrapDaterangepickerStyleContributor)

93
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs

@ -2,12 +2,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping; using Volo.Abp.ObjectMapping;
using Volo.Abp.Threading;
namespace Volo.Abp.Application.Services; namespace Volo.Abp.Application.Services;
@ -44,6 +46,20 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
protected virtual string? GetListPolicyName { get; set; } protected virtual string? GetListPolicyName { get; set; }
/// <summary>
/// Used by the <see cref="CreateGetOutputDtoQueryOrNullAsync"/> to project the query to the <typeparamref name="TGetOutputDto"/>.
/// The <see cref="GetEntityByIdAsync"/> and the <see cref="MapToGetOutputDtoAsync"/> are not used while the query is projected.
/// </summary>
protected virtual IQueryProjector<TEntity, TGetOutputDto>? GetOutputDtoQueryProjector
=> LazyServiceProvider.LazyGetService<IQueryProjector<TEntity, TGetOutputDto>>();
/// <summary>
/// Used by the <see cref="CreateGetListOutputDtoQueryOrNullAsync"/> to project the query to the <typeparamref name="TGetListOutputDto"/>.
/// The <see cref="MapToGetListOutputDtosAsync"/> is not used while the query is projected.
/// </summary>
protected virtual IQueryProjector<TEntity, TGetListOutputDto>? GetListOutputDtoQueryProjector
=> LazyServiceProvider.LazyGetService<IQueryProjector<TEntity, TGetListOutputDto>>();
protected AbstractKeyReadOnlyAppService(IReadOnlyRepository<TEntity> repository) protected AbstractKeyReadOnlyAppService(IReadOnlyRepository<TEntity> repository)
{ {
ReadOnlyRepository = repository; ReadOnlyRepository = repository;
@ -53,6 +69,19 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
{ {
await CheckGetPolicyAsync(); await CheckGetPolicyAsync();
var dtoQuery = await CreateGetOutputDtoQueryOrNullAsync(id);
if (dtoQuery != null)
{
//TGetOutputDto has no class constraint, so a default value can not be used to detect the missing entity
var dtos = await AsyncExecuter.ToListAsync(dtoQuery.Take(1), GetCancellationToken());
if (dtos.Count == 0)
{
throw new EntityNotFoundException<TEntity>(id);
}
return dtos[0];
}
var entity = await GetEntityByIdAsync(id); var entity = await GetEntityByIdAsync(id);
return await MapToGetOutputDtoAsync(entity); return await MapToGetOutputDtoAsync(entity);
@ -65,7 +94,6 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
var query = await CreateFilteredQueryAsync(input); var query = await CreateFilteredQueryAsync(input);
var totalCount = await AsyncExecuter.CountAsync(query); var totalCount = await AsyncExecuter.CountAsync(query);
var entities = new List<TEntity>();
var entityDtos = new List<TGetListOutputDto>(); var entityDtos = new List<TGetListOutputDto>();
if (totalCount > 0) if (totalCount > 0)
@ -73,8 +101,16 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
query = ApplySorting(query, input); query = ApplySorting(query, input);
query = ApplyPaging(query, input); query = ApplyPaging(query, input);
entities = await AsyncExecuter.ToListAsync(query); var dtoQuery = await CreateGetListOutputDtoQueryOrNullAsync(query);
entityDtos = await MapToGetListOutputDtosAsync(entities); if (dtoQuery != null)
{
entityDtos = await AsyncExecuter.ToListAsync(dtoQuery);
}
else
{
var entities = await AsyncExecuter.ToListAsync(query);
entityDtos = await MapToGetListOutputDtosAsync(entities);
}
} }
return new PagedResultDto<TGetListOutputDto>( return new PagedResultDto<TGetListOutputDto>(
@ -85,6 +121,57 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
protected abstract Task<TEntity> GetEntityByIdAsync(TKey id); protected abstract Task<TEntity> GetEntityByIdAsync(TKey id);
private CancellationToken GetCancellationToken()
{
return LazyServiceProvider
.LazyGetService<ICancellationTokenProvider>(NullCancellationTokenProvider.Instance)
.FallbackToProvider();
}
/// <summary>
/// Should create a query that selects the entity with the given <paramref name="id"/>.
/// It returns null by default, then the entity is not projected.
/// </summary>
/// <param name="id">The id of the entity.</param>
protected virtual Task<IQueryable<TEntity>?> CreateEntityQueryOrNullAsync(TKey id)
{
return Task.FromResult<IQueryable<TEntity>?>(null);
}
/// <summary>
/// Projects the query of the entity with the given <paramref name="id"/> to the <typeparamref name="TGetOutputDto"/>.
/// It uses the <see cref="GetOutputDtoQueryProjector"/> and the <see cref="CreateEntityQueryOrNullAsync"/> by default,
/// and the <see cref="GetEntityByIdAsync"/> is used when it returns null.
/// Override it to await other queries, like the query of another aggregate root to join.
/// </summary>
/// <param name="id">The id of the entity.</param>
protected virtual async Task<IQueryable<TGetOutputDto>?> CreateGetOutputDtoQueryOrNullAsync(TKey id)
{
var queryProjector = GetOutputDtoQueryProjector;
if (queryProjector == null)
{
return null;
}
var query = await CreateEntityQueryOrNullAsync(id);
return query == null ? null : queryProjector.ProjectTo(query);
}
/// <summary>
/// Projects the given entity query to the <typeparamref name="TGetListOutputDto"/>.
/// It uses the <see cref="GetListOutputDtoQueryProjector"/> by default,
/// and the <see cref="MapToGetListOutputDtosAsync"/> is used when it returns null.
/// Override it to await other queries, like the query of another aggregate root to join.
/// The projection must return one row per entity: the total count is already calculated and the paging is
/// already applied, so adding or removing rows makes the page inconsistent with the total count.
/// </summary>
/// <param name="query">The sorted and paged entity query.</param>
protected virtual Task<IQueryable<TGetListOutputDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<TEntity> query)
{
return Task.FromResult(GetListOutputDtoQueryProjector?.ProjectTo(query));
}
protected virtual async Task CheckGetPolicyAsync() protected virtual async Task CheckGetPolicyAsync()
{ {
await CheckPolicyAsync(GetPolicyName); await CheckPolicyAsync(GetPolicyName);

7
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs

@ -84,6 +84,13 @@ public abstract class CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto,
return await Repository.GetAsync(id); return await Repository.GetAsync(id);
} }
protected override async Task<IQueryable<TEntity>?> CreateEntityQueryOrNullAsync(TKey id)
{
var query = await Repository.GetQueryableAsync();
return query.Where(e => e.Id!.Equals(id));
}
protected override void MapToEntity(TUpdateInput updateInput, TEntity entity) protected override void MapToEntity(TUpdateInput updateInput, TEntity entity)
{ {
if (updateInput is IEntityDto<TKey> entityDto) if (updateInput is IEntityDto<TKey> entityDto)

7
framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs

@ -47,6 +47,13 @@ public abstract class ReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputD
return await Repository.GetAsync(id); return await Repository.GetAsync(id);
} }
protected override async Task<IQueryable<TEntity>?> CreateEntityQueryOrNullAsync(TKey id)
{
var query = await Repository.GetQueryableAsync();
return query.Where(e => e.Id!.Equals(id));
}
protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query) protected override IQueryable<TEntity> ApplyDefaultSorting(IQueryable<TEntity> query)
{ {
if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>()) if (typeof(TEntity).IsAssignableTo<ICreationAuditedObject>())

11
framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
@ -18,6 +19,14 @@ public class AbpObjectMappingModule : AbpModule
typeof(IObjectMapper<,>) typeof(IObjectMapper<,>)
).ConvertAll(t => new ServiceIdentifier(t)) ).ConvertAll(t => new ServiceIdentifier(t))
); );
//Register types for IQueryProjector<TSource, TDestination> if implements
foreach (var serviceType in ReflectionHelper.GetImplementedGenericTypes(
onServiceExposingContext.ImplementationType,
typeof(IQueryProjector<,>)))
{
onServiceExposingContext.ExposedTypes.AddIfNotContains(new ServiceIdentifier(serviceType));
}
}); });
} }

24
framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjector.cs

@ -0,0 +1,24 @@
using System.Linq;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ObjectMapping;
/// <summary>
/// Maps a query to another.
/// Implement this interface to project a query on the data store side, instead of loading the
/// source objects into the memory and mapping them one by one.
/// Implement it once for a source and destination pair. Use the ReplaceServices option of the
/// DependencyAttribute to replace an existing implementation.
/// </summary>
/// <typeparam name="TSource">Type of the source objects</typeparam>
/// <typeparam name="TDestination">Type of the destination objects</typeparam>
public interface IQueryProjector<TSource, TDestination> : ITransientDependency
{
/// <summary>
/// Projects the given query. The returned query must be built on top of it and must keep its order,
/// with a single destination object for each source object, using expressions the query provider can
/// translate. The caller may have already sorted, paged or counted the source query.
/// </summary>
/// <param name="source">The query to project</param>
IQueryable<TDestination> ProjectTo(IQueryable<TSource> source);
}

23
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/Book.cs

@ -0,0 +1,23 @@
using System;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Application.Services.QueryProjection;
public class Book : Entity<Guid>
{
public string Name { get; set; } = default!;
public int Price { get; set; }
public Book()
{
}
public Book(Guid id, string name, int price)
: base(id)
{
Name = name;
Price = price;
}
}

27
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyAppService.cs

@ -0,0 +1,27 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookAbstractKeyAppService : AbstractKeyReadOnlyAppService<Book, BookDto, Guid>
{
public BookAbstractKeyAppService(IReadOnlyRepository<Book> repository)
: base(repository)
{
}
protected override async Task<Book> GetEntityByIdAsync(Guid id)
{
var query = await ReadOnlyRepository.GetQueryableAsync();
return await AsyncExecuter.FirstAsync(query, book => book.Id == id);
}
protected override IQueryable<Book> ApplyDefaultSorting(IQueryable<Book> query)
{
return query.OrderBy(book => book.Id);
}
}

35
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyProjectingAppService.cs

@ -0,0 +1,35 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookAbstractKeyProjectingAppService : AbstractKeyReadOnlyAppService<Book, BookDto, Guid>
{
public BookAbstractKeyProjectingAppService(IReadOnlyRepository<Book> repository)
: base(repository)
{
}
protected override async Task<Book> GetEntityByIdAsync(Guid id)
{
var query = await ReadOnlyRepository.GetQueryableAsync();
return await AsyncExecuter.FirstAsync(query, book => book.Id == id);
}
protected override async Task<IQueryable<Book>?> CreateEntityQueryOrNullAsync(Guid id)
{
var query = await ReadOnlyRepository.GetQueryableAsync();
return query.Where(book => book.Id == id);
}
protected override IQueryable<Book> ApplyDefaultSorting(IQueryable<Book> query)
{
return query.OrderBy(book => book.Id);
}
}

13
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAppService.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookAppService : CrudAppService<Book, BookDto, Guid>
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}

46
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAsyncProjectionAppService.cs

@ -0,0 +1,46 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookAsyncProjectionAppService : CrudAppService<Book, BookDto, Guid>
{
public const string Marker = "-async";
private readonly IBookNameSuffixProvider _suffixProvider;
public BookAsyncProjectionAppService(
IRepository<Book, Guid> repository,
IBookNameSuffixProvider suffixProvider)
: base(repository)
{
_suffixProvider = suffixProvider;
}
protected override async Task<IQueryable<BookDto>?> CreateGetOutputDtoQueryOrNullAsync(Guid id)
{
var query = await Repository.GetQueryableAsync();
return await ProjectAsync(query.Where(book => book.Id == id));
}
protected override async Task<IQueryable<BookDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<Book> query)
{
return await ProjectAsync(query);
}
private async Task<IQueryable<BookDto>> ProjectAsync(IQueryable<Book> query)
{
var suffix = await _suffixProvider.GetAsync();
return query.Select(book => new BookDto
{
Id = book.Id,
Name = book.Name + suffix
});
}
}

34
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookCustomizedAppService.cs

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookCustomizedAppService : CrudAppService<Book, BookDto, Guid>
{
public const string Marker = "-customized";
public BookCustomizedAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
protected override async Task<Book> GetEntityByIdAsync(Guid id)
{
var book = await base.GetEntityByIdAsync(id);
book.Name += Marker;
return book;
}
protected override Task<BookDto> MapToGetOutputDtoAsync(Book entity)
{
return Task.FromResult(new BookDto { Id = entity.Id, Name = entity.Name + Marker });
}
protected override Task<List<BookDto>> MapToGetListOutputDtosAsync(List<Book> entities)
{
return Task.FromResult(entities.ConvertAll(entity => new BookDto { Id = entity.Id, Name = entity.Name + Marker }));
}
}

15
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailAppService.cs

@ -0,0 +1,15 @@
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookDetailAppService :
ReadOnlyAppService<Book, BookDetailDto, BookDto, Guid, PagedAndSortedResultRequestDto>
{
public BookDetailAppService(IReadOnlyRepository<Book, Guid> repository)
: base(repository)
{
}
}

9
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailDto.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookDetailDto : EntityDto<Guid>
{
public string Name { get; set; } = default!;
}

18
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailProjector.cs

@ -0,0 +1,18 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookDetailProjector : IQueryProjector<Book, BookDetailDto>
{
public const string Marker = "-detail";
public IQueryable<BookDetailDto> ProjectTo(IQueryable<Book> source)
{
return source.Select(book => new BookDetailDto
{
Id = book.Id,
Name = book.Name + Marker
});
}
}

9
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDto.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookDto : EntityDto<Guid>
{
public string Name { get; set; } = default!;
}

13
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteAppService.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookLiteAppService : CrudAppService<Book, BookLiteDto, Guid>
{
public BookLiteAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}

9
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteDto.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookLiteDto : EntityDto<Guid>
{
public string Name { get; set; } = default!;
}

48
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookObjectMapper.cs

@ -0,0 +1,48 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookObjectMapper :
IObjectMapper<Book, BookDto>,
IObjectMapper<Book, BookLiteDto>,
IObjectMapper<BookDto, Book>,
ITransientDependency
{
public const string Marker = "-mapped";
public BookDto Map(Book source)
{
return new BookDto { Id = source.Id, Name = source.Name + Marker };
}
public BookDto Map(Book source, BookDto destination)
{
destination.Id = source.Id;
destination.Name = source.Name + Marker;
return destination;
}
BookLiteDto IObjectMapper<Book, BookLiteDto>.Map(Book source)
{
return new BookLiteDto { Id = source.Id, Name = source.Name + Marker };
}
BookLiteDto IObjectMapper<Book, BookLiteDto>.Map(Book source, BookLiteDto destination)
{
destination.Id = source.Id;
destination.Name = source.Name + Marker;
return destination;
}
Book IObjectMapper<BookDto, Book>.Map(BookDto source)
{
return new Book(source.Id, source.Name, 0);
}
Book IObjectMapper<BookDto, Book>.Map(BookDto source, Book destination)
{
destination.Name = source.Name;
return destination;
}
}

29
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookPolicyCheckedAppService.cs

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookPolicyCheckedException : Exception
{
}
public class BookPolicyCheckedAppService : CrudAppService<Book, BookDto, Guid>
{
public BookPolicyCheckedAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
protected override Task CheckGetPolicyAsync()
{
throw new BookPolicyCheckedException();
}
protected override Task CheckGetListPolicyAsync()
{
throw new BookPolicyCheckedException();
}
}

18
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookProjector.cs

@ -0,0 +1,18 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookProjector : IQueryProjector<Book, BookDto>
{
public const string Marker = "-projected";
public IQueryable<BookDto> ProjectTo(IQueryable<Book> source)
{
return source.Select(book => new BookDto
{
Id = book.Id,
Name = book.Name + Marker
});
}
}

13
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookReadOnlyAppService.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookReadOnlyAppService : ReadOnlyAppService<Book, BookDto, Guid>
{
public BookReadOnlyAppService(IReadOnlyRepository<Book, Guid> repository)
: base(repository)
{
}
}

106
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookRepository.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
[ExposeServices(
typeof(IRepository<Book, Guid>),
typeof(IReadOnlyRepository<Book, Guid>),
typeof(IReadOnlyRepository<Book>))]
public class BookRepository : RepositoryBase<Book, Guid>, ISingletonDependency
{
private readonly List<Book> _books = new();
public BookRepository()
: base("InMemory")
{
}
public override Task<IQueryable<Book>> GetQueryableAsync()
{
return Task.FromResult(_books.AsQueryable());
}
[Obsolete("Use GetQueryableAsync method.")]
protected override IQueryable<Book> GetQueryable()
{
return _books.AsQueryable();
}
public override Task<Book> GetAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default)
{
var book = _books.FirstOrDefault(x => x.Id == id);
if (book == null)
{
throw new EntityNotFoundException(typeof(Book), id);
}
return Task.FromResult(book);
}
public override Task<Book> FindAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default)
{
return Task.FromResult(_books.FirstOrDefault(x => x.Id == id));
}
public override Task<Book> FindAsync(Expression<Func<Book, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
{
return Task.FromResult(_books.AsQueryable().FirstOrDefault(predicate));
}
public override Task<List<Book>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return Task.FromResult(_books.ToList());
}
public override Task<List<Book>> GetListAsync(Expression<Func<Book, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
{
return Task.FromResult(_books.AsQueryable().Where(predicate).ToList());
}
public override Task<List<Book>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default)
{
return Task.FromResult(_books.Skip(skipCount).Take(maxResultCount).ToList());
}
public override Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult((long)_books.Count);
}
public override Task<Book> InsertAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
_books.Add(entity);
return Task.FromResult(entity);
}
public override Task<Book> UpdateAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
return Task.FromResult(entity);
}
public override Task DeleteAsync(Book entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
_books.Remove(entity);
return Task.CompletedTask;
}
public override Task DeleteAsync(Expression<Func<Book, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
{
_books.RemoveAll(new Predicate<Book>(predicate.Compile()));
return Task.CompletedTask;
}
public override Task DeleteDirectAsync(Expression<Func<Book, bool>> predicate, CancellationToken cancellationToken = default)
{
return DeleteAsync(predicate);
}
}

13
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructAppService.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookStructAppService : ReadOnlyAppService<Book, BookStructDto, Guid>
{
public BookStructAppService(IReadOnlyRepository<Book, Guid> repository)
: base(repository)
{
}
}

10
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructDto.cs

@ -0,0 +1,10 @@
using System;
namespace Volo.Abp.Application.Services.QueryProjection;
public struct BookStructDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}

16
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructProjector.cs

@ -0,0 +1,16 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookStructProjector : IQueryProjector<Book, BookStructDto>
{
public IQueryable<BookStructDto> ProjectTo(IQueryable<Book> source)
{
return source.Select(book => new BookStructDto
{
Id = book.Id,
Name = book.Name
});
}
}

33
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookWithoutProjectionAppService.cs

@ -0,0 +1,33 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Application.Services.QueryProjection;
public class BookWithoutProjectionAppService : CrudAppService<Book, BookDto, Guid>
{
public const string Marker = "-not-projected";
protected override IQueryProjector<Book, BookDto>? GetOutputDtoQueryProjector => null;
protected override IQueryProjector<Book, BookDto>? GetListOutputDtoQueryProjector => null;
public BookWithoutProjectionAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
protected override Task<BookDto> MapToGetOutputDtoAsync(Book entity)
{
return Task.FromResult(new BookDto { Id = entity.Id, Name = entity.Name + Marker });
}
protected override Task<List<BookDto>> MapToGetListOutputDtosAsync(List<Book> entities)
{
return Task.FromResult(entities.ConvertAll(entity => new BookDto { Id = entity.Id, Name = entity.Name + Marker }));
}
}

19
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/IBookNameSuffixProvider.cs

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Application.Services.QueryProjection;
public interface IBookNameSuffixProvider
{
Task<string> GetAsync();
}
public class BookNameSuffixProvider : IBookNameSuffixProvider, ITransientDependency
{
public async Task<string> GetAsync()
{
await Task.Yield();
return BookAsyncProjectionAppService.Marker;
}
}

196
framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/QueryProjection_Tests.cs

@ -0,0 +1,196 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
using Xunit;
namespace Volo.Abp.Application.Services.QueryProjection;
public class QueryProjection_Tests : AbpDddApplicationTestBase
{
private readonly Guid _bookId = Guid.NewGuid();
public QueryProjection_Tests()
{
var repository = GetRequiredService<IRepository<Book, Guid>>();
repository.InsertAsync(new Book(_bookId, "Hitchhiker's Guide", 42)).GetAwaiter().GetResult();
}
[Fact]
public void Should_Resolve_The_Projector_Independent_From_The_Class_Name()
{
ServiceProvider.GetService<IQueryProjector<Book, BookDto>>()
.ShouldBeOfType<BookProjector>();
}
[Fact]
public async Task Should_Project_On_The_Query_For_CrudAppService()
{
var appService = GetRequiredService<BookAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookProjector.Marker);
}
[Fact]
public async Task Should_Project_On_The_Query_For_ReadOnlyAppService()
{
var appService = GetRequiredService<BookReadOnlyAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookProjector.Marker);
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_If_The_Projected_Entity_Does_Not_Exist()
{
var appService = GetRequiredService<BookAppService>();
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid()));
}
[Fact]
public async Task Should_Use_The_Object_Mapper_If_No_Projector_Was_Registered()
{
var appService = GetRequiredService<BookLiteAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookObjectMapper.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookObjectMapper.Marker);
}
[Fact]
public async Task Should_Use_The_Entity_Based_Overrides_If_The_Projection_Was_Disabled()
{
var appService = GetRequiredService<BookWithoutProjectionAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookWithoutProjectionAppService.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookWithoutProjectionAppService.Marker);
}
[Fact]
public async Task Should_Not_Use_The_Entity_Based_Overrides_While_Projecting()
{
var appService = GetRequiredService<BookCustomizedAppService>();
var dto = await appService.GetAsync(_bookId);
dto.Name.ShouldEndWith(BookProjector.Marker);
dto.Name.ShouldNotContain(BookCustomizedAppService.Marker);
var items = (await appService.GetListAsync(new PagedAndSortedResultRequestDto())).Items;
items[0].Name.ShouldEndWith(BookProjector.Marker);
items[0].Name.ShouldNotContain(BookCustomizedAppService.Marker);
}
[Fact]
public async Task Should_Use_The_Object_Mapper_If_The_Entity_Query_Can_Not_Be_Created()
{
var appService = GetRequiredService<BookAbstractKeyAppService>();
//GetAsync has no query to project, it falls back to GetEntityByIdAsync
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookObjectMapper.Marker);
//GetListAsync always has a query, so it is still projected
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookProjector.Marker);
}
[Fact]
public async Task Should_Use_The_Asynchronously_Created_Projection_Query()
{
var appService = GetRequiredService<BookAsyncProjectionAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookAsyncProjectionAppService.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookAsyncProjectionAppService.Marker);
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_From_An_Overridden_Projection_Query()
{
var appService = GetRequiredService<BookAsyncProjectionAppService>();
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid()));
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_For_A_Value_Type_Dto()
{
var appService = GetRequiredService<BookStructAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldBe("Hitchhiker's Guide");
await Should.ThrowAsync<EntityNotFoundException>(async () => await appService.GetAsync(Guid.NewGuid()));
}
[Fact]
public async Task Should_Not_Project_On_The_Create_And_Update_Paths()
{
var appService = GetRequiredService<BookAppService>();
var created = await appService.CreateAsync(new BookDto { Name = "New Book" });
created.Name.ShouldEndWith(BookObjectMapper.Marker);
var updated = await appService.UpdateAsync(created.Id, new BookDto { Name = "Updated Book" });
updated.Name.ShouldEndWith(BookObjectMapper.Marker);
}
[Fact]
public async Task Should_Use_A_Different_Projector_For_The_Get_And_The_List()
{
var appService = GetRequiredService<BookDetailAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookDetailProjector.Marker);
(await appService.GetListAsync(new PagedAndSortedResultRequestDto()))
.Items[0].Name.ShouldEndWith(BookProjector.Marker);
}
[Fact]
public async Task Should_Apply_Paging_And_Sorting_Before_Projecting()
{
var repository = GetRequiredService<IRepository<Book, Guid>>();
await repository.InsertAsync(new Book(Guid.NewGuid(), "A Book", 1));
await repository.InsertAsync(new Book(Guid.NewGuid(), "Z Book", 2));
var appService = GetRequiredService<BookAppService>();
var firstPage = await appService.GetListAsync(
new PagedAndSortedResultRequestDto { MaxResultCount = 1, Sorting = "Name" });
firstPage.TotalCount.ShouldBe(3);
firstPage.Items.Count.ShouldBe(1);
firstPage.Items[0].Name.ShouldBe("A Book" + BookProjector.Marker);
var secondPage = await appService.GetListAsync(
new PagedAndSortedResultRequestDto { MaxResultCount = 1, SkipCount = 1, Sorting = "Name" });
secondPage.Items[0].Name.ShouldBe("Hitchhiker's Guide" + BookProjector.Marker);
}
[Fact]
public async Task Should_Project_A_Single_Entity_From_An_AbstractKey_Application_Service()
{
var appService = GetRequiredService<BookAbstractKeyProjectingAppService>();
(await appService.GetAsync(_bookId)).Name.ShouldEndWith(BookProjector.Marker);
}
[Fact]
public async Task Should_Check_The_Policies_While_Projecting()
{
var appService = GetRequiredService<BookPolicyCheckedAppService>();
await Should.ThrowAsync<BookPolicyCheckedException>(async () => await appService.GetAsync(_bookId));
await Should.ThrowAsync<BookPolicyCheckedException>(async () =>
await appService.GetListAsync(new PagedAndSortedResultRequestDto()));
}
}

2
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.Applications;
using Volo.Abp.EntityFrameworkCore.Domain; using Volo.Abp.EntityFrameworkCore.Domain;
using Volo.Abp.EntityFrameworkCore.Sqlite; using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.EntityFrameworkCore.TestApp.FifthContext; using Volo.Abp.EntityFrameworkCore.TestApp.FifthContext;
@ -92,6 +93,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
options.Configure(abpDbContextConfigurationContext => options.Configure(abpDbContextConfigurationContext =>
{ {
abpDbContextConfigurationContext.UseSqlite().AddAbpDbContextOptionsExtension(); abpDbContextConfigurationContext.UseSqlite().AddAbpDbContextOptionsExtension();
abpDbContextConfigurationContext.DbContextOptions.AddInterceptors(new SqlCommandCapture());
}); });
}); });
} }

14
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionAppService.cs

@ -0,0 +1,14 @@
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class EntityWithIntPkProjectionAppService : ReadOnlyAppService<EntityWithIntPk, EntityWithIntPkProjectionDto, int>
{
public EntityWithIntPkProjectionAppService(IReadOnlyRepository<EntityWithIntPk, int> repository)
: base(repository)
{
}
}

8
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionDto.cs

@ -0,0 +1,8 @@
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class EntityWithIntPkProjectionDto : EntityDto<int>
{
public string Name { get; set; }
}

17
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjector.cs

@ -0,0 +1,17 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class EntityWithIntPkProjector : IQueryProjector<EntityWithIntPk, EntityWithIntPkProjectionDto>
{
public IQueryable<EntityWithIntPkProjectionDto> ProjectTo(IQueryable<EntityWithIntPk> source)
{
return source.Select(entity => new EntityWithIntPkProjectionDto
{
Id = entity.Id,
Name = entity.Name
});
}
}

15
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionAppService.cs

@ -0,0 +1,15 @@
using System;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class PersonProjectionAppService : ReadOnlyAppService<Person, PersonProjectionDto, Guid>
{
public PersonProjectionAppService(IReadOnlyRepository<Person, Guid> repository)
: base(repository)
{
}
}

9
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionDto.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class PersonProjectionDto : EntityDto<Guid>
{
public string Name { get; set; }
}

17
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjector.cs

@ -0,0 +1,17 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class PersonProjector : IQueryProjector<Person, PersonProjectionDto>
{
public IQueryable<PersonProjectionDto> ProjectTo(IQueryable<Person> source)
{
return source.Select(person => new PersonProjectionDto
{
Id = person.Id,
Name = person.Name
});
}
}

51
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityAppService.cs

@ -0,0 +1,51 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.EntityFrameworkCore.Applications;
//City is another aggregate root, so Person has no City navigation property to project.
public class PersonWithCityAppService : ReadOnlyAppService<Person, PersonWithCityDto, Guid>
{
private readonly IReadOnlyRepository<City, Guid> _cityRepository;
public PersonWithCityAppService(
IReadOnlyRepository<Person, Guid> repository,
IReadOnlyRepository<City, Guid> cityRepository)
: base(repository)
{
_cityRepository = cityRepository;
}
protected override async Task<IQueryable<PersonWithCityDto>?> CreateGetOutputDtoQueryOrNullAsync(Guid id)
{
var people = await CreateEntityQueryOrNullAsync(id);
return people == null ? null : await JoinCitiesAsync(people);
}
protected override async Task<IQueryable<PersonWithCityDto>?> CreateGetListOutputDtoQueryOrNullAsync(IQueryable<Person> query)
{
return await JoinCitiesAsync(query);
}
//left join, an inner join would drop the people without a city and break the total count
private async Task<IQueryable<PersonWithCityDto>> JoinCitiesAsync(IQueryable<Person> people)
{
var cities = await _cityRepository.GetQueryableAsync();
return from person in people
join city in cities on person.CityId equals city.Id into personCities
from personCity in personCities.DefaultIfEmpty()
select new PersonWithCityDto
{
Id = person.Id,
Name = person.Name,
CityName = personCity != null ? personCity.Name : null
};
}
}

12
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityDto.cs

@ -0,0 +1,12 @@
#nullable enable
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class PersonWithCityDto : EntityDto<Guid>
{
public string Name { get; set; }
public string? CityName { get; set; }
}

197
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/QueryProjection_Tests.cs

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Application;
using Volo.Abp.Threading;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class QueryProjection_Tests : EntityFrameworkCoreTestBase
{
private readonly PersonProjectionAppService _personProjectionAppService;
public QueryProjection_Tests()
{
_personProjectionAppService = GetRequiredService<PersonProjectionAppService>();
}
[Fact]
public async Task Should_Get_A_Projected_Entity()
{
var dto = await _personProjectionAppService.GetAsync(TestDataBuilder.UserDouglasId);
dto.Id.ShouldBe(TestDataBuilder.UserDouglasId);
dto.Name.ShouldBe("Douglas");
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_For_A_Missing_Entity()
{
(await Should.ThrowAsync<EntityNotFoundException<Person>>(
async () => await _personProjectionAppService.GetAsync(Guid.NewGuid()))
).EntityType.ShouldBe(typeof(Person));
}
[Fact]
public async Task Should_Get_A_Projected_List()
{
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto());
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.Count.ShouldBe((int)result.TotalCount);
result.Items.ShouldContain(x => x.Name == "Douglas");
}
[Fact]
public async Task Should_Get_A_Projected_Entity_With_A_Non_Guid_Key()
{
var appService = GetRequiredService<EntityWithIntPkProjectionAppService>();
var entity = await WithUnitOfWorkAsync(
async () => await GetRequiredService<IRepository<EntityWithIntPk, int>>().FirstAsync());
(await appService.GetAsync(entity.Id)).Name.ShouldBe(entity.Name);
}
[Fact]
public async Task Should_Apply_The_Soft_Delete_Filter_While_Projecting()
{
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto());
result.Items.ShouldNotContain(x => x.Id == TestDataBuilder.UserJohnDeletedId);
}
[Fact]
public async Task Should_Only_Select_The_Projected_Columns()
{
await WithUnitOfWorkAsync(async () =>
{
var repository = GetRequiredService<IReadOnlyRepository<Person, Guid>>();
var projector = GetRequiredService<IQueryProjector<Person, PersonProjectionDto>>();
var sql = projector.ProjectTo(await repository.GetQueryableAsync()).ToQueryString();
sql.ShouldContain("\"Name\"");
sql.ShouldNotContain("\"Birthday\"");
sql.ShouldNotContain("\"ExtraProperties\"");
//the data filters are still a part of the query
sql.ShouldContain("Is_Deleted");
});
}
[Fact]
public async Task Should_Join_Another_Aggregate_While_Projecting()
{
var appService = GetRequiredService<PersonWithCityAppService>();
var dto = await appService.GetAsync(TestDataBuilder.UserDouglasId);
dto.CityName.ShouldBe("London");
var result = await appService.GetListAsync(new PagedAndSortedResultRequestDto());
result.Items.ShouldContain(x => x.CityName == "London");
}
[Fact]
public async Task Should_Keep_The_Total_Count_While_Joining_Another_Aggregate()
{
await WithUnitOfWorkAsync(async () =>
{
await GetRequiredService<IRepository<Person, Guid>>()
.InsertAsync(new Person(Guid.NewGuid(), "PersonWithoutCity", 30), autoSave: true);
});
var result = await GetRequiredService<PersonWithCityAppService>()
.GetListAsync(new PagedAndSortedResultRequestDto());
result.Items.Count.ShouldBe((int)result.TotalCount);
result.Items.ShouldContain(x => x.Name == "PersonWithoutCity" && x.CityName == null);
}
[Fact]
public async Task Should_Execute_The_Projected_Query_From_The_Application_Service()
{
System.Collections.Concurrent.ConcurrentQueue<string> commands;
using (SqlCommandCapture.Begin(out commands))
{
await GetRequiredService<PersonProjectionAppService>()
.GetListAsync(new PagedAndSortedResultRequestDto());
}
//the application service must run the projection itself, not materialize the entities first
var selects = commands.Where(x => x.Contains("FROM \"People\"") && !x.Contains("COUNT")).ToList();
var select = selects.ShouldHaveSingleItem();
select.ShouldContain("\"Name\"");
select.ShouldNotContain("\"Birthday\"");
select.ShouldNotContain("\"ExtraProperties\"");
}
[Fact]
public async Task Should_Apply_The_Multi_Tenancy_Filter_While_Projecting()
{
var result = await GetRequiredService<PersonProjectionAppService>()
.GetListAsync(new PagedAndSortedResultRequestDto());
result.Items.ShouldNotContain(x => x.Name.StartsWith(TestDataBuilder.TenantId1.ToString()));
}
[Fact]
public async Task Should_Use_The_Ambient_Cancellation_Token_While_Projecting()
{
var cancellationTokenProvider = GetRequiredService<ICancellationTokenProvider>();
var appService = GetRequiredService<PersonProjectionAppService>();
//the entity path passes it through Repository.GetAsync, so the projected path must not regress
using (cancellationTokenProvider.Use(new CancellationToken(canceled: true)))
{
await Should.ThrowAsync<OperationCanceledException>(async () =>
await appService.GetAsync(TestDataBuilder.UserDouglasId));
}
}
[Fact]
public async Task Should_Execute_The_Projected_Query_From_The_Application_Service_For_A_Single_Entity()
{
System.Collections.Concurrent.ConcurrentQueue<string> commands;
using (SqlCommandCapture.Begin(out commands))
{
await GetRequiredService<PersonProjectionAppService>().GetAsync(TestDataBuilder.UserDouglasId);
}
var selects = commands.Where(x => x.Contains("FROM \"People\"")).ToList();
var select = selects.ShouldHaveSingleItem();
select.ShouldContain("\"Name\"");
select.ShouldNotContain("\"Birthday\"");
select.ShouldNotContain("\"ExtraProperties\"");
}
[Fact]
public async Task Should_Apply_The_Data_Filters_Of_The_Joined_Aggregate()
{
var cityRepository = GetRequiredService<IRepository<City, Guid>>();
await WithUnitOfWorkAsync(async () =>
{
var london = await cityRepository.GetAsync(TestDataBuilder.LondonCityId);
await cityRepository.DeleteAsync(london, autoSave: true);
});
var result = await GetRequiredService<PersonWithCityAppService>()
.GetListAsync(new PagedAndSortedResultRequestDto());
//the soft deleted city is filtered out, the left join still keeps the person
result.Items.ShouldContain(x => x.Name == "Douglas" && x.CityName == null);
}
}

40
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/SqlCommandCapture.cs

@ -0,0 +1,40 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Volo.Abp.EntityFrameworkCore.Applications;
public class SqlCommandCapture : DbCommandInterceptor
{
private static readonly AsyncLocal<ConcurrentQueue<string>> Commands = new();
public static IDisposable Begin(out ConcurrentQueue<string> commands)
{
commands = new ConcurrentQueue<string>();
Commands.Value = commands;
return new DisposeAction(() => Commands.Value = null);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
Commands.Value?.Enqueue(command.CommandText);
return base.ReaderExecuting(command, eventData, result);
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
Commands.Value?.Enqueue(command.CommandText);
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
}

39
framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyQueryProjection_Tests.cs

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Mapperly.SampleClasses;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Mapperly;
public class AbpMapperlyQueryProjection_Tests : AbpIntegratedTest<MapperlyTestModule>
{
[Fact]
public void Should_Project_A_Queryable()
{
var queryProjector = ServiceProvider.GetRequiredService<IQueryProjector<MyEntity, MyEntityDto>>();
var entities = new List<MyEntity>
{
new MyEntity { Id = Guid.NewGuid(), Number = 42 }
}.AsQueryable();
var dtos = queryProjector.ProjectTo(entities).ToList();
dtos.Count.ShouldBe(1);
dtos[0].Id.ShouldBe(entities.First().Id);
dtos[0].Number.ShouldBe(42);
}
[Fact]
public void Should_Register_A_Projector_Only_Once()
{
//MyEntityQueryProjector also matches the class name convention of ExposeServicesAttribute
ServiceProvider.GetServices<IQueryProjector<MyEntity, MyEntityDto>>().ShouldHaveSingleItem();
}
}

11
framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityQueryProjector.cs

@ -0,0 +1,11 @@
using System.Linq;
using Riok.Mapperly.Abstractions;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.Mapperly.SampleClasses;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class MyEntityQueryProjector : IQueryProjector<MyEntity, MyEntityDto>
{
public partial IQueryable<MyEntityDto> ProjectTo(IQueryable<MyEntity> source);
}

15
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionAppService.cs

@ -0,0 +1,15 @@
using System;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.MongoDB.Applications;
public class PersonProjectionAppService : ReadOnlyAppService<Person, PersonProjectionDto, Guid>
{
public PersonProjectionAppService(IReadOnlyRepository<Person, Guid> repository)
: base(repository)
{
}
}

9
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionDto.cs

@ -0,0 +1,9 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Abp.MongoDB.Applications;
public class PersonProjectionDto : EntityDto<Guid>
{
public string Name { get; set; }
}

17
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjector.cs

@ -0,0 +1,17 @@
using System.Linq;
using Volo.Abp.ObjectMapping;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.MongoDB.Applications;
public class PersonProjector : IQueryProjector<Person, PersonProjectionDto>
{
public IQueryable<PersonProjectionDto> ProjectTo(IQueryable<Person> source)
{
return source.Select(person => new PersonProjectionDto
{
Id = person.Id,
Name = person.Name
});
}
}

46
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/QueryProjection_Tests.cs

@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Entities;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.MongoDB.Applications;
[Collection(MongoTestCollection.Name)]
public class QueryProjection_Tests : MongoDbTestBase
{
private readonly PersonProjectionAppService _personProjectionAppService;
public QueryProjection_Tests()
{
_personProjectionAppService = GetRequiredService<PersonProjectionAppService>();
}
[Fact]
public async Task Should_Get_A_Projected_Entity()
{
var dto = await _personProjectionAppService.GetAsync(TestDataBuilder.UserDouglasId);
dto.Id.ShouldBe(TestDataBuilder.UserDouglasId);
dto.Name.ShouldBe("Douglas");
}
[Fact]
public async Task Should_Throw_EntityNotFoundException_For_A_Missing_Entity()
{
await Should.ThrowAsync<EntityNotFoundException<Person>>(
async () => await _personProjectionAppService.GetAsync(Guid.NewGuid()));
}
[Fact]
public async Task Should_Get_A_Projected_List()
{
var result = await _personProjectionAppService.GetListAsync(new PagedAndSortedResultRequestDto());
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(x => x.Name == "Douglas");
}
}

5
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -10,7 +10,6 @@
@using Volo.Abp.AspNetCore.Mvc.UI.Layout @using Volo.Abp.AspNetCore.Mvc.UI.Layout
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Anchor @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Anchor
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Clipboard @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Clipboard
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.MalihuCustomScrollbar
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Popper @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Popper
@using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs @using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs
@using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Abp.AspNetCore.Mvc.UI.Theming
@ -38,7 +37,6 @@
@section styles { @section styles {
<abp-style-bundle name="@typeof(IndexModel).FullName"> <abp-style-bundle name="@typeof(IndexModel).FullName">
<abp-style type="@typeof(PrismjsStyleBundleContributor)"/> <abp-style type="@typeof(PrismjsStyleBundleContributor)"/>
<abp-style type="@typeof(MalihuCustomScrollbarPluginStyleBundleContributor)"/>
<abp-style src="/Pages/Documents/Shared/Styles/vs.css"/> <abp-style src="/Pages/Documents/Shared/Styles/vs.css"/>
<abp-style src="/Pages/Documents/Project/index.css"/> <abp-style src="/Pages/Documents/Project/index.css"/>
@if (DocsUiOptions.Value.EnableEnlargeImage) @if (DocsUiOptions.Value.EnableEnlargeImage)
@ -64,7 +62,6 @@
} }
</script> </script>
<abp-script-bundle name="@typeof(IndexModel).FullName"> <abp-script-bundle name="@typeof(IndexModel).FullName">
<abp-script type="@typeof(MalihuCustomScrollbarPluginScriptBundleContributor)"/>
<abp-script type="@typeof(ClipboardScriptBundleContributor)"/> <abp-script type="@typeof(ClipboardScriptBundleContributor)"/>
<abp-script type="@typeof(AnchorJsScriptBundleContributor)"/> <abp-script type="@typeof(AnchorJsScriptBundleContributor)"/>
<abp-script type="@typeof(PrismjsScriptBundleContributor)"/> <abp-script type="@typeof(PrismjsScriptBundleContributor)"/>
@ -306,7 +303,7 @@
<div class="docs-content"> <div class="docs-content">
<div class="top-container mb-3"> <div class="top-container mb-3">
<div class="w-100"> <div class="w-100">
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mCustomScrollbar-1"> <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb" id="document-node-wrapper"> <ol class="breadcrumb" id="document-node-wrapper">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="@Model.CreateVersionLink(Model.LatestVersionInfo, Model.GetSpecificVersionOrLatest())"><i class="fa fa-home"></i></a> <a href="@Model.CreateVersionLink(Model.LatestVersionInfo, Model.GetSpecificVersionOrLatest())"><i class="fa fa-home"></i></a>

8
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js

@ -90,16 +90,16 @@ var doc = doc || {};
var filterDocumentItems = function (filterText) { var filterDocumentItems = function (filterText) {
$navigation $navigation
.find('.mCSB_container .opened') .find('.opened')
.removeClass('opened'); .removeClass('opened');
$navigation $navigation
.find('.mCSB_container > li, .mCSB_container > li ul') .find('> li, > li ul')
.hide(); .hide();
if (!filterText) { if (!filterText) {
$navigation.find('.mCSB_container > li').show(); $navigation.find('> li').show();
$navigation $navigation
.find('.mCSB_container .selected-tree > ul') .find('.selected-tree > ul')
.show(); .show();
$searchAllDocument.hide(); $searchAllDocument.hide();
return; return;

24
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js

@ -61,8 +61,6 @@
}; };
$(document).ready(function () { $(document).ready(function () {
handleCustomScrolls();
var $myNav = $('#docs-sticky-index'); var $myNav = $('#docs-sticky-index');
if ($myNav.length === 0) { if ($myNav.length === 0) {
@ -123,30 +121,8 @@
initMenuToggle(); initMenuToggle();
scrollToHashLink(); scrollToHashLink();
}); });
$(window).resize(function () {
handleCustomScrolls();
});
}); });
function handleCustomScrolls() {
$('#sidebar-scroll').mCustomScrollbar({
theme: 'minimal',
alwaysShowScrollbar: 0,
});
$('#scroll-index').mCustomScrollbar({
theme: 'minimal-dark',
alwaysShowScrollbar: 0,
});
$('.mCustomScrollbar-1').mCustomScrollbar({
theme: 'minimal-dark',
alwaysShowScrollbar: 0,
horizontalScroll: true,
});
}
function initMenuToggle() { function initMenuToggle() {
$('li:not(.last-link) a.tree-toggle').off('click'); $('li:not(.last-link) a.tree-toggle').off('click');
$('li:not(.last-link) span.plus-icon i.fa-chevron-right').off('click'); $('li:not(.last-link) span.plus-icon i.fa-chevron-right').off('click');

14
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css

@ -46,12 +46,6 @@ body div.code-toolbar > .toolbar span {
body div.code-toolbar > .toolbar a { body div.code-toolbar > .toolbar a {
cursor: copy; cursor: copy;
} }
body .mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools,
body .mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools {
opacity: 1 !important;
filter: "alpha(opacity=1)";
-ms-filter: "alpha(opacity=1)";
}
body .alert-criteria .options-header { body .alert-criteria .options-header {
display: none; display: none;
} }
@ -338,6 +332,8 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
display: block; display: block;
height: calc(100vh - 350px); height: calc(100vh - 350px);
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
margin-right: -4px !important; margin-right: -4px !important;
margin-top: 20px !important; margin-top: 20px !important;
} }
@ -420,9 +416,6 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
.docs-page .docs-sidebar .docs-tree-list ul li.selected-tree.last-link > span .fa { .docs-page .docs-sidebar .docs-tree-list ul li.selected-tree.last-link > span .fa {
transform: rotate(0deg); transform: rotate(0deg);
} }
.docs-page .docs-sidebar .docs-tree-list .mCSB_scrollTools {
right: -20px;
}
.docs-page .docs-content { .docs-page .docs-content {
display: inline-block; display: inline-block;
overflow-x: scroll; overflow-x: scroll;
@ -449,6 +442,7 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
margin-bottom: 0; margin-bottom: 0;
flex-wrap: nowrap; flex-wrap: nowrap;
overflow-x: auto; overflow-x: auto;
scrollbar-width: thin;
white-space: nowrap; white-space: nowrap;
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -733,6 +727,8 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
} }
.docs-page .docs-page-index #scroll-index { .docs-page .docs-page-index #scroll-index {
max-height: 38vh; max-height: 38vh;
overflow-y: auto;
scrollbar-width: thin;
} }
.docs-page .docs-page-index .scroll-top-btn { .docs-page .docs-page-index .scroll-top-btn {
display: none; display: none;

2
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css.map

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["vs.scss"],"names":[],"mappings":"AAGA;EACI;EACA;;AAEA;EACI;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAIA;EACI;;AAGJ;AAAA;AAAA;AAAA;EAII;;AAIR;AAAA;EAEI;;AAGJ;EACI;;AAEA;EACI;;AAEJ;EACI;;AAIR;AAAA;EAEI;EACA;EACA;;AAGJ;EACI;;AAKA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EAEI;EACA;;AAIJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EAEI;EACA;EACA;EACA;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EA5GJ;IA6GQ;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;;AAOR;EAEI;;AAGJ;EACI;IAEI;;;;AAOpB;EACI;;AAEA;EACI;EACA;EACA,KAtOK;EAuOL,QAvOK;EAwOL;EACA;EACA;EACA;EACA;EACA;EACA;;AAIQ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAMhB;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;EAII;;AAIR;EACI;EACA;;AAEJ;EACI;EACA;;AAEA;EACI;;AAIR;EACI;;AAEA;EAII;;AAIR;EACI;EACA;EACA;;AAIR;EACI;;AAIR;EAEI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAIR;EACI;;AAGI;EACI;;AAGJ;EACI;;AAGI;EACI;;AAQhB;EACI;EACA;;AAIA;EACI;EACA;;AAMA;EACI;;AAOpB;EACI;;AAKhB;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;EACA;EACA;EACA;EACA;;AAMA;EACI;EACA;EACA;EACA;EACA;;AACA;EACI;;AAEJ;EACI;EACA;;AAMhB;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAEJ;EACI;;AAGJ;EACI;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;;AAQhB;EACI;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;EAII;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;EAMI;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AACA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAMR;EACI;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;EACA;EACA,KA90BC;EA+0BD;EACA;EACA;;AAGI;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAMA;EACI;;AAMA;EACI;;AAQxB;EACI;;AAIR;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA,eA15BC;EA25BD;;AAEA;EACI;EACA;EACA;;AACA;EACI;;AAGR;EACI;EACA,SAx6BH;;AA06BD;EACI;EACA;EACA;;AAGJ;EACI;;;AAMhB;EACI;IACI;;EAEA;IACI;;EAGJ;IACI;;EAGJ;AAAA;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;;EAQR;IACI;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGI;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAIR;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;;EAEA;IACI;;EAKZ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIR;IACI;IACA;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAMZ;IACI;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAGJ;IACI;;EAIA;IACI;;EAEJ;IACI;;EAGJ;IACI;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;EAKZ;IACI;IACA;IACA;;EAEA;IACI;IACA;;EAIR;IACI;;EAEA;IACI;;;AAMhB;EAIgB;IACI;;EAEJ;IACI;;EAMI;IACI;IACA;;;AAS5B;EAEQ;IACI;IACA;;EAEJ;IAII;;EAHA;IACI;;;AAOhB;EAEQ;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA","file":"vs.css"} {"version":3,"sourceRoot":"","sources":["vs.scss"],"names":[],"mappings":"AAGA;EACI;EACA;;AAEA;EACI;EACA;;AAIJ;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAGJ;EACI;EACA;EACA;;AAIA;EACI;;AAGJ;AAAA;AAAA;AAAA;EAII;;AAIR;AAAA;EAEI;;AAGJ;EACI;;AAEA;EACI;;AAEJ;EACI;;AAMR;EACI;;AAGJ;EACI;;AAMA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;;AAIJ;EACI;EACA;;AAMZ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAGA;EACI;EACA;;AAOZ;EAGI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EA5HJ;IA6HQ;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;;EAEA;IACI;;;AAOhB;EACI;;AAEA;EAHJ;IAIQ;;;;AAMhB;EACI;;AAEA;EACI;EACA;EACA,KA5OK;EA6OL,QA7OK;EA8OL;EACA;EACA;EACA;EACA;EACA;EACA;;AAIQ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAMhB;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;;AAEJ;EACI;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;EAII;;AAIR;EACI;EACA;;AAEJ;EACI;EACA;;AAEA;EACI;;AAIR;EACI;;AAEA;EAII;;AAIR;EACI;EACA;EACA;;AAIR;EACI;;AAIR;EAEI;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAIR;EACI;;AAGI;EACI;;AAGJ;EACI;;AAGI;EACI;;AAQhB;EACI;EACA;;AAIA;EACI;EACA;;AAMA;EACI;;AAUhC;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AACA;EACI;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;EACA;EACA;EACA;EACA;;AACA;EACI;;AAEJ;EACI;EACA;;AAMhB;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAEJ;EACI;;AAGJ;EACI;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;;AAEA;EACI;;AAGJ;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;AAKJ;EACI;EACA;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAMA;EACI;;AAQhB;EACI;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;EAII;EACA;EACA;;AAGJ;AAAA;AAAA;AAAA;AAAA;AAAA;EAMI;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGI;EACI;;AAIR;EACI;;AAGJ;AAAA;EAEI;EACA;;AAGJ;EACI;EACA;;AAGJ;EACI;;AAGJ;EACI;;AAKZ;EACI;EACA;EACA;EACA;;AACA;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AACA;EACI;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAMR;EACI;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;;AAKZ;EACI;EACA;;AAEA;EACI;EACA;EACA,KAp1BC;EAq1BD;EACA;EACA;;AAGI;EACI;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;AAMA;EACI;;AAMA;EACI;;AAQxB;EACI;;AAIR;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAIR;EACI;EACA;EACA;EACA;EACA,eAl6BC;EAm6BD;;AAEA;EACI;EACA;EACA;;AACA;EACI;;AAGR;EACI;EACA,SAh7BH;;AAk7BD;EACI;EACA;EACA;;AAGJ;EACI;;;AAMhB;EACI;IACI;;EAEA;IACI;;EAGJ;IACI;;EAGJ;AAAA;IAEI;IACA;IACA;IACA;IACA;IACA;IACA;;EAQR;IACI;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAGI;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAIR;IACI;;EAGJ;IACI;IACA;;EAGJ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA;;EAEA;IACI;;EAKZ;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAIR;IACI;IACA;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAMZ;IACI;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAGJ;IACI;;EAIA;IACI;;EAEJ;IACI;;EAGJ;IACI;IACA;IACA;IACA;;EAGJ;IACI;IACA;IACA;IACA;;EAKZ;IACI;IACA;IACA;;EAEA;IACI;IACA;;EAIR;IACI;;EAEA;IACI;;;AAMhB;EAIgB;IACI;;EAEJ;IACI;;EAMI;IACI;IACA;;;AAS5B;EAEQ;IACI;IACA;;EAEJ;IAII;;EAHA;IACI;;;AAOhB;EAEQ;IACI;IACA;IACA;IACA;;EAEA;IACI;IACA;IACA","file":"vs.css"}

15
modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss

@ -63,13 +63,6 @@ body {
} }
} }
.mCS-autoHide > .mCustomScrollBox > .mCSB_scrollTools,
.mCS-autoHide > .mCustomScrollBox ~ .mCSB_scrollTools {
opacity: 1 !important;
filter: 'alpha(opacity=1)';
-ms-filter: 'alpha(opacity=1)';
}
// the options-header only exists to host the floating button; hide it while the bar is inline // the options-header only exists to host the floating button; hide it while the bar is inline
// (non-scrolled), so the inline bar keeps its original look. It reappears once .scrolledMore collapses. // (non-scrolled), so the inline bar keeps its original look. It reappears once .scrolledMore collapses.
.alert-criteria .options-header { .alert-criteria .options-header {
@ -419,6 +412,8 @@ body {
display: block; display: block;
height: calc(100vh - 350px); height: calc(100vh - 350px);
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
margin-right: -4px !important; margin-right: -4px !important;
margin-top: 20px !important; margin-top: 20px !important;
} }
@ -531,9 +526,6 @@ body {
} }
} }
} }
.mCSB_scrollTools{
right: -20px;
}
} }
} }
@ -563,6 +555,7 @@ body {
margin-bottom: 0; margin-bottom: 0;
flex-wrap: nowrap; flex-wrap: nowrap;
overflow-x: auto; overflow-x: auto;
scrollbar-width: thin;
white-space: nowrap; white-space: nowrap;
padding-bottom: 10px; padding-bottom: 10px;
@ -915,6 +908,8 @@ body {
#scroll-index { #scroll-index {
max-height: 38vh; max-height: 38vh;
overflow-y: auto;
scrollbar-width: thin;
} }
.scroll-top-btn { .scroll-top-btn {

4
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css

@ -4,8 +4,12 @@
.custom-scroll-content { .custom-scroll-content {
max-height: 400px; max-height: 400px;
overflow-y: auto;
scrollbar-width: thin;
} }
.custom-scroll-container > .col-md-4 { .custom-scroll-container > .col-md-4 {
max-height: 500px; max-height: 500px;
overflow-y: auto;
scrollbar-width: thin;
} }

9
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js

@ -100,15 +100,6 @@ var abp = abp || {};
}); });
}); });
}); });
$(function () {
$('.custom-scroll-content').mCustomScrollbar({
theme: 'minimal-dark',
});
$('.custom-scroll-container > .col-md-4').mCustomScrollbar({
theme: 'minimal-dark',
});
});
}; };
}; };
})(jQuery); })(jQuery);

9
modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/permission-management-modal.js

@ -295,15 +295,6 @@ var abp = abp || {};
}); });
}); });
$(function () {
$('.custom-scroll-content').mCustomScrollbar({
theme: 'minimal-dark',
});
$('.custom-scroll-container > .col-4').mCustomScrollbar({
theme: 'minimal-dark',
});
});
initSelectAllInThisTab(); initSelectAllInThisTab();
setSelectAllInAllTabs(); setSelectAllInAllTabs();

1
npm/packs/aspnetcore.mvc.ui.theme.shared/package.json

@ -19,7 +19,6 @@
"@abp/jquery-validation-unobtrusive": "~10.7.0-rc.3", "@abp/jquery-validation-unobtrusive": "~10.7.0-rc.3",
"@abp/lodash": "~10.7.0-rc.3", "@abp/lodash": "~10.7.0-rc.3",
"@abp/luxon": "~10.7.0-rc.3", "@abp/luxon": "~10.7.0-rc.3",
"@abp/malihu-custom-scrollbar-plugin": "~10.7.0-rc.3",
"@abp/moment": "~10.7.0-rc.3", "@abp/moment": "~10.7.0-rc.3",
"@abp/select2": "~10.7.0-rc.3", "@abp/select2": "~10.7.0-rc.3",
"@abp/sweetalert2": "~10.7.0-rc.3", "@abp/sweetalert2": "~10.7.0-rc.3",

1
npm/packs/docs/package.json

@ -7,7 +7,6 @@
"dependencies": { "dependencies": {
"@abp/anchor-js": "~10.7.0-rc.3", "@abp/anchor-js": "~10.7.0-rc.3",
"@abp/clipboard": "~10.7.0-rc.3", "@abp/clipboard": "~10.7.0-rc.3",
"@abp/malihu-custom-scrollbar-plugin": "~10.7.0-rc.3",
"@abp/popper.js": "~10.7.0-rc.3", "@abp/popper.js": "~10.7.0-rc.3",
"@abp/prismjs": "~10.7.0-rc.3" "@abp/prismjs": "~10.7.0-rc.3"
}, },

129
npm/packs/malihu-custom-scrollbar-plugin/README.md

@ -1,129 +0,0 @@
## ℹ️ Description
ABP Framework is a complete open-source infrastructure to create modern web applications by following the best practices and conventions of software development. This package is a part of the [ABP Framework](https://abp.io) and contains client-side files.
For more information, check out the below links:
🔗Official Website: https://abp.io
🔗Commercial Website: https://commercial.abp.io
🔗Commercial Demo: https://commercial.abp.io/demo
🔗GitHub Repository: https://github.com/abpframework/abp
🔗Official Theme: https://www.LeptonTheme.com
🔗Documentation: https://docs.abp.io
🔗Community: https://community.abp.io
🔗Blog: https://blog.abp.io
🔗Books: https://abp.io/books
🔗Twitter: https://twitter.com/abpframework
🔗Discord: https://community.abp.io/discord
🔗Stackoverflow: https://stackoverflow.com/questions/tagged/abp
🔗YouTube: https://www.youtube.com/@Volosoft
## 🤔 Why ABP Platform?
Why should you use the ABP.IO Platform instead of creating a new solution from scratch?
You can find the answer here 👉🏻 [Why ABP Platform?](https://docs.abp.io/en/commercial/latest/why-abp-io-platform)
## 🚀 Key Features of the ABP Framework
🟡 Modularity
🟡 Multi-Tenancy
🟡 Bootstrap Tag Helpers
🟡 Dynamic Forms
🟡 Authentication
🟡 Authorization
🟡 Distributed Event Bus
🟡 BLOB Storing
🟡 Text Templating
🟡 Tooling: ABP CLI
🟡 Cross-Cutting Concerns
🟡 Bundling & Minification
🟡 Virtual File System
🟡 Theming
🟡 Background Jobs
🟡 DDD Infrastructure
🟡 Auto REST APIs
🟡 Dynamic Client Proxies
🟡 Multiple Database Providers
🟡 Data filtering
🟡 Test Infrastructure
🟡 Audit Logging
🟡 Object to Object Mapping
🟡 Email & SMS Abstractions
🟡 Localization
🟡 Setting Management
🟡 Extension Methods
🟡 Aspect Oriented Programming
🟡 Dependency Injection
## 🧐 How It Works?
The following page explains how you use the ABP.IO Platform as a .NET developer 👉 [How it works?](https://commercial.abp.io/how-it-works)
### 📘 Supported Database Providers
🔵 Entity Framework Core
🔵 MongoDB
🔵 Dapper
### 🎴 Supported UI Frameworks
🔵 Angular
🔵 Razor Pages
🔵 Blazor Web Assembly
🔵 Blazor Server
🔵 MAUI with Blazor Hybrid
## 📫 Bug & Support
Support for open-source ABP Framework client-side packages is available at [GitHub Issues](https://github.com/abpframework/abp/issues), and the commercial support is available at [support.abp.io](https://support.abp.io).

5
npm/packs/malihu-custom-scrollbar-plugin/abp.resourcemapping.js

@ -1,5 +0,0 @@
module.exports = {
mappings: {
"@node_modules/malihu-custom-scrollbar-plugin/*.*": "@libs/malihu-custom-scrollbar-plugin/"
}
}

32
npm/packs/malihu-custom-scrollbar-plugin/package.json

@ -1,32 +0,0 @@
{
"version": "10.7.0-rc.3",
"name": "@abp/malihu-custom-scrollbar-plugin",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git",
"directory": "npm/packs/malihu-custom-scrollbar-plugin"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@abp/core": "~10.7.0-rc.3",
"malihu-custom-scrollbar-plugin": "^3.1.5"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",
"license": "LGPL-3.0",
"keywords": [
"aspnetcore",
"boilerplate",
"framework",
"web",
"best-practices",
"angular",
"maui",
"blazor",
"mvc",
"csharp",
"webapp"
]
}

2
npm/packs/select2/package.json

@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@abp/core": "~10.7.0-rc.3", "@abp/core": "~10.7.0-rc.3",
"select2": "^4.0.13" "select2": "4.0.13"
}, },
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io", "homepage": "https://abp.io",

Loading…
Cancel
Save