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:
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
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
name: calibreapp/image-actions
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Repo
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Compress Images
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.
* `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.
* `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.
#### 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(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
### 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.
- [Select2](https://select2.org/) for better select/combo boxes.
- [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.

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.
* [Select2](https://select2.org/) for better select/combo boxes.
* [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.

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.Lodash;
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.SweetAlert2;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago;
@ -23,7 +22,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
typeof(Select2ScriptContributor),
typeof(DatatablesNetBs5ScriptContributor),
typeof(Sweetalert2ScriptContributor),
typeof(MalihuCustomScrollbarPluginScriptBundleContributor),
typeof(LuxonScriptContributor),
typeof(TimeagoScriptContributor),
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.DatatablesNetBs5;
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.Modularity;
@ -16,7 +15,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
typeof(BootstrapStyleContributor),
typeof(FontAwesomeStyleContributor),
typeof(Select2StyleContributor),
typeof(MalihuCustomScrollbarPluginStyleBundleContributor),
typeof(DatatablesNetBs5StyleContributor),
typeof(BootstrapDatepickerStyleContributor),
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.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Threading;
namespace Volo.Abp.Application.Services;
@ -44,6 +46,20 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
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)
{
ReadOnlyRepository = repository;
@ -53,6 +69,19 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
{
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);
return await MapToGetOutputDtoAsync(entity);
@ -65,7 +94,6 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
var query = await CreateFilteredQueryAsync(input);
var totalCount = await AsyncExecuter.CountAsync(query);
var entities = new List<TEntity>();
var entityDtos = new List<TGetListOutputDto>();
if (totalCount > 0)
@ -73,8 +101,16 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
entities = await AsyncExecuter.ToListAsync(query);
entityDtos = await MapToGetListOutputDtosAsync(entities);
var dtoQuery = await CreateGetListOutputDtoQueryOrNullAsync(query);
if (dtoQuery != null)
{
entityDtos = await AsyncExecuter.ToListAsync(dtoQuery);
}
else
{
var entities = await AsyncExecuter.ToListAsync(query);
entityDtos = await MapToGetListOutputDtosAsync(entities);
}
}
return new PagedResultDto<TGetListOutputDto>(
@ -85,6 +121,57 @@ public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGet
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()
{
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);
}
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)
{
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);
}
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)
{
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.Modularity;
using Volo.Abp.Reflection;
@ -18,6 +19,14 @@ public class AbpObjectMappingModule : AbpModule
typeof(IObjectMapper<,>)
).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.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.Applications;
using Volo.Abp.EntityFrameworkCore.Domain;
using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.EntityFrameworkCore.TestApp.FifthContext;
@ -92,6 +93,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
options.Configure(abpDbContextConfigurationContext =>
{
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.Packages.Anchor
@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.Prismjs
@using Volo.Abp.AspNetCore.Mvc.UI.Theming
@ -38,7 +37,6 @@
@section styles {
<abp-style-bundle name="@typeof(IndexModel).FullName">
<abp-style type="@typeof(PrismjsStyleBundleContributor)"/>
<abp-style type="@typeof(MalihuCustomScrollbarPluginStyleBundleContributor)"/>
<abp-style src="/Pages/Documents/Shared/Styles/vs.css"/>
<abp-style src="/Pages/Documents/Project/index.css"/>
@if (DocsUiOptions.Value.EnableEnlargeImage)
@ -64,7 +62,6 @@
}
</script>
<abp-script-bundle name="@typeof(IndexModel).FullName">
<abp-script type="@typeof(MalihuCustomScrollbarPluginScriptBundleContributor)"/>
<abp-script type="@typeof(ClipboardScriptBundleContributor)"/>
<abp-script type="@typeof(AnchorJsScriptBundleContributor)"/>
<abp-script type="@typeof(PrismjsScriptBundleContributor)"/>
@ -306,7 +303,7 @@
<div class="docs-content">
<div class="top-container mb-3">
<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">
<li class="breadcrumb-item">
<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) {
$navigation
.find('.mCSB_container .opened')
.find('.opened')
.removeClass('opened');
$navigation
.find('.mCSB_container > li, .mCSB_container > li ul')
.find('> li, > li ul')
.hide();
if (!filterText) {
$navigation.find('.mCSB_container > li').show();
$navigation.find('> li').show();
$navigation
.find('.mCSB_container .selected-tree > ul')
.find('.selected-tree > ul')
.show();
$searchAllDocument.hide();
return;

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

@ -61,8 +61,6 @@
};
$(document).ready(function () {
handleCustomScrolls();
var $myNav = $('#docs-sticky-index');
if ($myNav.length === 0) {
@ -123,30 +121,8 @@
initMenuToggle();
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() {
$('li:not(.last-link) a.tree-toggle').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 {
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 {
display: none;
}
@ -338,6 +332,8 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
display: block;
height: calc(100vh - 350px);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
margin-right: -4px !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 {
transform: rotate(0deg);
}
.docs-page .docs-sidebar .docs-tree-list .mCSB_scrollTools {
right: -20px;
}
.docs-page .docs-content {
display: inline-block;
overflow-x: scroll;
@ -449,6 +442,7 @@ body.scrolledMore:has(#abp-chatbot-container) .alert-criteria .options-header .t
margin-bottom: 0;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: thin;
white-space: nowrap;
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 {
max-height: 38vh;
overflow-y: auto;
scrollbar-width: thin;
}
.docs-page .docs-page-index .scroll-top-btn {
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
// (non-scrolled), so the inline bar keeps its original look. It reappears once .scrolledMore collapses.
.alert-criteria .options-header {
@ -419,6 +412,8 @@ body {
display: block;
height: calc(100vh - 350px);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
margin-right: -4px !important;
margin-top: 20px !important;
}
@ -531,9 +526,6 @@ body {
}
}
}
.mCSB_scrollTools{
right: -20px;
}
}
}
@ -563,6 +555,7 @@ body {
margin-bottom: 0;
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: thin;
white-space: nowrap;
padding-bottom: 10px;
@ -915,6 +908,8 @@ body {
#scroll-index {
max-height: 38vh;
overflow-y: auto;
scrollbar-width: thin;
}
.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 {
max-height: 400px;
overflow-y: auto;
scrollbar-width: thin;
}
.custom-scroll-container > .col-md-4 {
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);

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();
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/lodash": "~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/select2": "~10.7.0-rc.3",
"@abp/sweetalert2": "~10.7.0-rc.3",

1
npm/packs/docs/package.json

@ -7,7 +7,6 @@
"dependencies": {
"@abp/anchor-js": "~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/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": {
"@abp/core": "~10.7.0-rc.3",
"select2": "^4.0.13"
"select2": "4.0.13"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",

Loading…
Cancel
Save