Browse Source

Merge branch 'dev' into maliming/replace-owl-carousel-with-swiper

pull/26005/head
maliming 2 weeks ago
parent
commit
e674972f70
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. 10
      docs/en/framework/ui/mvc-razor-pages/bundling-minification.md
  7. 7
      docs/en/framework/ui/mvc-razor-pages/overall.md
  8. 7
      docs/en/framework/ui/mvc-razor-pages/theming.md
  9. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs
  10. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs
  11. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginScriptBundleContributor.cs
  12. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/MalihuCustomScrollbar/MalihuCustomScrollbarPluginStyleBundleContributor.cs
  13. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/OwlCarousel/OwlCarouselScriptContributor.cs
  14. 15
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/OwlCarousel/OwlCarouselStyleContributor.cs
  15. 17
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs
  16. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalScriptContributor.cs
  17. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Bundling/SharedThemeGlobalStyleContributor.cs
  18. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
  19. 93
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyReadOnlyAppService.cs
  20. 7
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs
  21. 7
      framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ReadOnlyAppService.cs
  22. 11
      framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/AbpObjectMappingModule.cs
  23. 24
      framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/IQueryProjector.cs
  24. 23
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/Book.cs
  25. 27
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyAppService.cs
  26. 35
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAbstractKeyProjectingAppService.cs
  27. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAppService.cs
  28. 46
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookAsyncProjectionAppService.cs
  29. 34
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookCustomizedAppService.cs
  30. 15
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailAppService.cs
  31. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailDto.cs
  32. 18
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDetailProjector.cs
  33. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookDto.cs
  34. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteAppService.cs
  35. 9
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookLiteDto.cs
  36. 48
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookObjectMapper.cs
  37. 29
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookPolicyCheckedAppService.cs
  38. 18
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookProjector.cs
  39. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookReadOnlyAppService.cs
  40. 106
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookRepository.cs
  41. 13
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructAppService.cs
  42. 10
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructDto.cs
  43. 16
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookStructProjector.cs
  44. 33
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/BookWithoutProjectionAppService.cs
  45. 19
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/IBookNameSuffixProvider.cs
  46. 196
      framework/test/Volo.Abp.Ddd.Application.Tests/Volo/Abp/Application/Services/QueryProjection/QueryProjection_Tests.cs
  47. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  48. 14
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionAppService.cs
  49. 8
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjectionDto.cs
  50. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/EntityWithIntPkProjector.cs
  51. 15
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionAppService.cs
  52. 9
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjectionDto.cs
  53. 17
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonProjector.cs
  54. 51
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityAppService.cs
  55. 12
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/PersonWithCityDto.cs
  56. 197
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/QueryProjection_Tests.cs
  57. 40
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Applications/SqlCommandCapture.cs
  58. 39
      framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyQueryProjection_Tests.cs
  59. 11
      framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityQueryProjector.cs
  60. 15
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionAppService.cs
  61. 9
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjectionDto.cs
  62. 17
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/PersonProjector.cs
  63. 46
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Applications/QueryProjection_Tests.cs
  64. 5
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  65. 8
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/index.js
  66. 24
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Scripts/vs.js
  67. 14
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css
  68. 2
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.css.map
  69. 15
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Shared/Styles/vs.scss
  70. 4
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.css
  71. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js
  72. 9
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/permission-management-modal.js
  73. 1
      npm/packs/aspnetcore.mvc.ui.theme.shared/package.json
  74. 2
      npm/packs/bootstrap-datepicker/abp.resourcemapping.js
  75. 2
      npm/packs/bootstrap-datepicker/package.json
  76. 2048
      npm/packs/bootstrap-datepicker/src/bootstrap-datepicker.js
  77. 2
      npm/packs/bootstrap-daterangepicker/abp.resourcemapping.js
  78. 3
      npm/packs/bootstrap-daterangepicker/package.json
  79. 410
      npm/packs/bootstrap-daterangepicker/src/daterangepicker.css
  80. 2
      npm/packs/bootstrap-daterangepicker/src/daterangepicker.js
  81. 1
      npm/packs/docs/package.json
  82. 5
      npm/packs/jquery-form/abp.resourcemapping.js
  83. 32
      npm/packs/jquery-form/package.json
  84. 22
      npm/packs/jquery-form/src/jquery.form.min.js
  85. 2
      npm/packs/jquery-validation-unobtrusive/abp.resourcemapping.js
  86. 3
      npm/packs/jquery-validation-unobtrusive/package.json
  87. 436
      npm/packs/jquery-validation-unobtrusive/src/jquery.validate.unobtrusive.js
  88. 129
      npm/packs/malihu-custom-scrollbar-plugin/README.md
  89. 5
      npm/packs/malihu-custom-scrollbar-plugin/abp.resourcemapping.js
  90. 32
      npm/packs/malihu-custom-scrollbar-plugin/package.json
  91. 0
      npm/packs/owl.carousel/README.md
  92. 5
      npm/packs/owl.carousel/abp.resourcemapping.js
  93. 15
      npm/packs/owl.carousel/package.json
  94. 2
      npm/packs/select2/package.json
  95. 6
      npm/packs/timeago/abp.resourcemapping.js
  96. 2
      npm/packs/timeago/package.json
  97. 164
      npm/packs/timeago/src/abp.timeago.js
  98. 129
      npm/packs/toastr/README.md
  99. 5
      npm/packs/toastr/abp.resourcemapping.js

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

10
docs/en/framework/ui/mvc-razor-pages/bundling-minification.md

@ -35,7 +35,7 @@ The simplest way of creating a bundle is to use `abp-script-bundle` or `abp-styl
<abp-style-bundle name="MyGlobalBundle">
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/libs/datatables.net-bs5/css/dataTables.bootstrap5.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
````
@ -65,7 +65,7 @@ The `name` is **optional** for the razor bundle tag helpers. If you don't define
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/libs/datatables.net-bs5/css/dataTables.bootstrap5.css" />
@if (ViewBag.IncludeCustomStyles != false)
{
<abp-style src="/styles/my-global-style.css" />
@ -115,7 +115,7 @@ public class MyWebModule : AbpModule
bundle.AddFiles(
"/libs/jquery/jquery.js",
"/libs/bootstrap/js/bootstrap.js",
"/libs/toastr/toastr.min.js",
"/libs/lodash/lodash.min.js",
"/scripts/my-global-scripts.js"
);
});
@ -220,7 +220,7 @@ Contributors can also be used in the bundle tag helpers. Example:
<abp-style-bundle>
<abp-style type="@typeof(BootstrapStyleContributor)" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/libs/datatables.net-bs5/css/dataTables.bootstrap5.css" />
</abp-style-bundle>
````
@ -386,7 +386,7 @@ Configure<AbpBundlingOptions>(options =>
````html
<link rel="preload" href="/__bundles/Basic.Global.F4FA61F368098407A4C972D0A6914137.css?_v=637697363694828051" as="style" onload="this.rel='stylesheet'"/>
<script defer src="/libs/timeago/locales/jquery.timeago.en.js?_v=637674729040000000"></script>
<script defer src="/libs/timeago/timeago.full.min.js?_v=637674729040000000"></script>
````
### External/CDN file Support

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

@ -57,14 +57,11 @@ There are a set of standard JavaScript/CSS libraries that comes pre-installed an
- [JQuery Validation](https://jqueryvalidation.org/) for client side & [unobtrusive](https://github.com/aspnet/jquery-validation-unobtrusive) validation
- [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
- [SweetAlert](https://sweetalert.js.org/) to show fancy alert message and confirmation dialogs.
- [Toastr](https://github.com/CodeSeven/toastr) to show toast notifications.
- [Lodash](https://lodash.com/) as a utility library.
- [Luxon](https://moment.github.io/luxon/) for date/time operations.
- [JQuery Form](https://github.com/jquery-form/form) for AJAX forms.
- [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers.
- [Select2](https://select2.org/) for better select/combo boxes.
- [Timeago](http://timeago.yarp.com/) to show automatically updating fuzzy timestamps.
- [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars.
- [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps.
You can use these libraries directly in your applications, without needing to manually import your page.
@ -140,7 +137,7 @@ ABP provides a flexible and modular Bundling & Minification system to create bun
<abp-style-bundle>
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
<abp-style src="/libs/font-awesome/css/font-awesome.css" />
<abp-style src="/libs/toastr/toastr.css" />
<abp-style src="/libs/datatables.net-bs5/css/dataTables.bootstrap5.css" />
<abp-style src="/styles/my-global-style.css" />
</abp-style-bundle>
```

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

@ -44,14 +44,11 @@ All the themes must depend on the [@abp/aspnetcore.mvc.ui.theme.shared](https://
* [JQuery Validation](https://github.com/jquery-validation/jquery-validation) for client side & [unobtrusive](https://github.com/aspnet/jquery-validation-unobtrusive) validation
* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library.
* [SweetAlert](https://sweetalert.js.org/) to show fancy alert message and confirmation dialogs.
* [Toastr](https://github.com/CodeSeven/toastr) to show toast notifications.
* [Lodash](https://lodash.com/) as a utility library.
* [Luxon](https://moment.github.io/luxon/) for date/time operations.
* [JQuery Form](https://github.com/jquery-form/form) for AJAX forms.
* [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers.
* [Select2](https://select2.org/) for better select/combo boxes.
* [Timeago](http://timeago.yarp.com/) to show automatically updating fuzzy timestamps.
* [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars.
* [timeago.js](https://timeago.org/) to show automatically updating fuzzy timestamps.
These libraries are selected as the base libraries and available to the applications and modules.
@ -60,7 +57,7 @@ These libraries are selected as the base libraries and available to the applicat
There are some abstractions in the ABP to make your code independent from some of these libraries too. Examples;
* [Tag Helpers](tag-helpers) makes it easy to generate the Bootstrap UIs.
* JavaScript [Message](javascript-api/message.md) and [Notification](javascript-api/notify.md) APIs provides abstractions to use the Sweetalert and Toastr.
* JavaScript [Message](javascript-api/message.md) and [Notification](javascript-api/notify.md) APIs provides abstractions to use the Sweetalert and the built-in toast implementation.
* [Forms & Validation](forms-validation.md) system automatically handles the validation, so you mostly don't directly type any validation code.
### The Standard Layouts

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs

@ -2,7 +2,6 @@
using Volo.Abp.AspNetCore.Mvc.UI.Packages.BootstrapDatepicker;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQueryValidation;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Moment;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
@ -38,11 +37,6 @@ public class AbpAspNetCoreMvcUiPackagesModule : AbpModule
new NameValue("zh-Hant", "zh-tw"),
new NameValue("de-DE", "de"));
//Timeago
options.AddLanguageFilesMapOrUpdate(TimeagoScriptContributor.PackageName,
new NameValue("zh-Hans", "zh-CN"),
new NameValue("zh-Hant", "zh-TW"));
//JQueryValidation
options.AddLanguageFilesMapOrUpdate(JQueryValidationScriptContributor.PackageName,
new NameValue("zh-Hans", "zh"),

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/BootstrapDatepicker/BootstrapDatepickerScriptContributor.cs

@ -14,7 +14,7 @@ public class BootstrapDatepickerScriptContributor : BundleContributor
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/bootstrap-datepicker/bootstrap-datepicker.min.js");
context.Files.AddIfNotContains("/libs/bootstrap-datepicker/bootstrap-datepicker.js");
}
public override void ConfigureDynamicResources(BundleConfigurationContext context)

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

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/OwlCarousel/OwlCarouselScriptContributor.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel;
[DependsOn(typeof(JQueryScriptContributor))]
public class OwlCarouselScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/owl.carousel/owl.carousel.min.js");
}
}

15
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/OwlCarousel/OwlCarouselStyleContributor.cs

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.OwlCarousel;
public class OwlCarouselStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
//TODO: Theming!
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.carousel.min.css");
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.theme.default.min.css");
context.Files.AddIfNotContains("/libs/owl.carousel/assets/owl.theme.green.min.css");
}
}

17
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/Timeago/TimeagoScriptContributor.cs

@ -1,8 +1,6 @@
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago;
@ -10,20 +8,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Packages.Timeago;
[DependsOn(typeof(JQueryScriptContributor))]
public class TimeagoScriptContributor : BundleContributor
{
public const string PackageName = "jquery.timeago";
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.AddIfNotContains("/libs/timeago/jquery.timeago.js");
}
public override void ConfigureDynamicResources(BundleConfigurationContext context)
{
var fileName = context.LazyServiceProvider.LazyGetRequiredService<IOptions<AbpLocalizationOptions>>().Value.GetCurrentUICultureLanguageFilesMap(PackageName);
var filePath = $"/libs/timeago/locales/jquery.timeago.{fileName}.js";
if (context.FileProvider.GetFileInfo(filePath).Exists)
{
context.Files.Add(filePath);
}
context.Files.AddIfNotContains("/libs/timeago/timeago.full.min.js");
context.Files.AddIfNotContains("/libs/abp/timeago/abp.timeago.js");
}
}

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)

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js

@ -72,7 +72,7 @@
}
abp.dom.initializers.initializeTimeAgos = function ($timeagos) {
$timeagos.timeago();
abp.timeago.render($timeagos.toArray());
}
abp.dom.initializers.initializeAutocompleteSelects = function ($autocompleteSelects) {
@ -217,6 +217,7 @@
args.$el.findWithSelf('[data-bs-toggle="tooltip"]').each(function () {
$('#' + $(this).attr('aria-describedby')).remove();
});
abp.timeago.cancel(args.$el.findWithSelf('[timeago-id]').toArray());
});
abp.event.on('abp.configurationInitialized', function () {

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",

2
npm/packs/bootstrap-datepicker/abp.resourcemapping.js

@ -1,6 +1,6 @@
module.exports = {
mappings: {
"@node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js": "@libs/bootstrap-datepicker/",
"@node_modules/@abp/bootstrap-datepicker/src/bootstrap-datepicker.js": "@libs/bootstrap-datepicker/",
"@node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css": "@libs/bootstrap-datepicker/",
"@node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.css.map": "@libs/bootstrap-datepicker/",
"@node_modules/bootstrap-datepicker/dist/locales/*.*": "@libs/bootstrap-datepicker/locales/"

2
npm/packs/bootstrap-datepicker/package.json

@ -10,7 +10,7 @@
"access": "public"
},
"dependencies": {
"bootstrap-datepicker": "^1.10.1"
"bootstrap-datepicker": "1.10.1"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",

2048
npm/packs/bootstrap-datepicker/src/bootstrap-datepicker.js

File diff suppressed because it is too large

2
npm/packs/bootstrap-daterangepicker/abp.resourcemapping.js

@ -1,6 +1,6 @@
module.exports = {
mappings: {
"@node_modules/@abp/bootstrap-daterangepicker/src/daterangepicker.js": "@libs/bootstrap-daterangepicker/",
"@node_modules/bootstrap-daterangepicker/daterangepicker.css": "@libs/bootstrap-daterangepicker/",
"@node_modules/@abp/bootstrap-daterangepicker/src/daterangepicker.css": "@libs/bootstrap-daterangepicker/",
}
}

3
npm/packs/bootstrap-daterangepicker/package.json

@ -10,8 +10,7 @@
"access": "public"
},
"dependencies": {
"@abp/moment": "~10.7.0-rc.3",
"bootstrap-daterangepicker": "^3.1.0"
"@abp/moment": "~10.7.0-rc.3"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",

410
npm/packs/bootstrap-daterangepicker/src/daterangepicker.css

@ -0,0 +1,410 @@
.daterangepicker {
position: absolute;
color: inherit;
background-color: #fff;
border-radius: 4px;
border: 1px solid #ddd;
width: 278px;
max-width: none;
padding: 0;
margin-top: 7px;
top: 100px;
left: 20px;
z-index: 3001;
display: none;
font-family: arial;
font-size: 15px;
line-height: 1em;
}
.daterangepicker:before, .daterangepicker:after {
position: absolute;
display: inline-block;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.daterangepicker:before {
top: -7px;
border-right: 7px solid transparent;
border-left: 7px solid transparent;
border-bottom: 7px solid #ccc;
}
.daterangepicker:after {
top: -6px;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
}
.daterangepicker.opensleft:before {
right: 9px;
}
.daterangepicker.opensleft:after {
right: 10px;
}
.daterangepicker.openscenter:before {
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
}
.daterangepicker.openscenter:after {
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
}
.daterangepicker.opensright:before {
left: 9px;
}
.daterangepicker.opensright:after {
left: 10px;
}
.daterangepicker.drop-up {
margin-top: -7px;
}
.daterangepicker.drop-up:before {
top: initial;
bottom: -7px;
border-bottom: initial;
border-top: 7px solid #ccc;
}
.daterangepicker.drop-up:after {
top: initial;
bottom: -6px;
border-bottom: initial;
border-top: 6px solid #fff;
}
.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {
float: none;
}
.daterangepicker.single .drp-selected {
display: none;
}
.daterangepicker.show-calendar .drp-calendar {
display: block;
}
.daterangepicker.show-calendar .drp-buttons {
display: block;
}
.daterangepicker.auto-apply .drp-buttons {
display: none;
}
.daterangepicker .drp-calendar {
display: none;
max-width: 270px;
}
.daterangepicker .drp-calendar.left {
padding: 8px 0 8px 8px;
}
.daterangepicker .drp-calendar.right {
padding: 8px;
}
.daterangepicker .drp-calendar.single .calendar-table {
border: none;
}
.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {
color: #fff;
border: solid black;
border-width: 0 2px 2px 0;
border-radius: 0;
display: inline-block;
padding: 3px;
}
.daterangepicker .calendar-table .next span {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.daterangepicker .calendar-table .prev span {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {
white-space: nowrap;
text-align: center;
vertical-align: middle;
min-width: 32px;
width: 32px;
height: 24px;
line-height: 24px;
font-size: 12px;
border-radius: 4px;
border: 1px solid transparent;
white-space: nowrap;
cursor: pointer;
}
.daterangepicker .calendar-table {
border: 1px solid #fff;
border-radius: 4px;
background-color: #fff;
}
.daterangepicker .calendar-table table {
width: 100%;
margin: 0;
border-spacing: 0;
border-collapse: collapse;
}
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
background-color: #eee;
border-color: transparent;
color: inherit;
}
.daterangepicker td.week, .daterangepicker th.week {
font-size: 80%;
color: #ccc;
}
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
background-color: #fff;
border-color: transparent;
color: #999;
}
.daterangepicker td.in-range {
background-color: #ebf4f8;
border-color: transparent;
color: #000;
border-radius: 0;
}
.daterangepicker td.start-date {
border-radius: 4px 0 0 4px;
}
.daterangepicker td.end-date {
border-radius: 0 4px 4px 0;
}
.daterangepicker td.start-date.end-date {
border-radius: 4px;
}
.daterangepicker td.active, .daterangepicker td.active:hover {
background-color: #357ebd;
border-color: transparent;
color: #fff;
}
.daterangepicker th.month {
width: auto;
}
.daterangepicker td.disabled, .daterangepicker option.disabled {
color: #999;
cursor: not-allowed;
text-decoration: line-through;
}
.daterangepicker select.monthselect, .daterangepicker select.yearselect {
font-size: 12px;
padding: 1px;
height: auto;
margin: 0;
cursor: default;
}
.daterangepicker select.monthselect {
margin-right: 2%;
width: 56%;
}
.daterangepicker select.yearselect {
width: 40%;
}
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
width: 50px;
margin: 0 auto;
background: #eee;
border: 1px solid #eee;
padding: 2px;
outline: 0;
font-size: 12px;
}
.daterangepicker .calendar-time {
text-align: center;
margin: 4px auto 0 auto;
line-height: 30px;
position: relative;
}
.daterangepicker .calendar-time select.disabled {
color: #ccc;
cursor: not-allowed;
}
.daterangepicker .drp-buttons {
clear: both;
text-align: right;
padding: 8px;
border-top: 1px solid #ddd;
display: none;
line-height: 12px;
vertical-align: middle;
}
.daterangepicker .drp-selected {
display: inline-block;
font-size: 12px;
padding-right: 8px;
}
.daterangepicker .drp-buttons .btn {
margin-left: 8px;
font-size: 12px;
font-weight: bold;
padding: 4px 8px;
}
.daterangepicker.show-ranges.single.rtl .drp-calendar.left {
border-right: 1px solid #ddd;
}
.daterangepicker.show-ranges.single.ltr .drp-calendar.left {
border-left: 1px solid #ddd;
}
.daterangepicker.show-ranges.rtl .drp-calendar.right {
border-right: 1px solid #ddd;
}
.daterangepicker.show-ranges.ltr .drp-calendar.left {
border-left: 1px solid #ddd;
}
.daterangepicker .ranges {
float: none;
text-align: left;
margin: 0;
}
.daterangepicker.show-calendar .ranges {
margin-top: 8px;
}
.daterangepicker .ranges ul {
list-style: none;
margin: 0 auto;
padding: 0;
width: 100%;
}
.daterangepicker .ranges li {
font-size: 12px;
padding: 8px 12px;
cursor: pointer;
}
.daterangepicker .ranges li:hover {
background-color: #eee;
}
.daterangepicker .ranges li.active {
background-color: #08c;
color: #fff;
}
/* Larger Screen Styling */
@media (min-width: 564px) {
.daterangepicker {
width: auto;
}
.daterangepicker .ranges ul {
width: 140px;
}
.daterangepicker.single .ranges ul {
width: 100%;
}
.daterangepicker.single .drp-calendar.left {
clear: none;
}
.daterangepicker.single .ranges, .daterangepicker.single .drp-calendar {
float: left;
}
.daterangepicker {
direction: ltr;
text-align: left;
}
.daterangepicker .drp-calendar.left {
clear: left;
margin-right: 0;
}
.daterangepicker .drp-calendar.left .calendar-table {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.daterangepicker .drp-calendar.right {
margin-left: 0;
}
.daterangepicker .drp-calendar.right .calendar-table {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.daterangepicker .drp-calendar.left .calendar-table {
padding-right: 8px;
}
.daterangepicker .ranges, .daterangepicker .drp-calendar {
float: left;
}
}
@media (min-width: 730px) {
.daterangepicker .ranges {
width: auto;
}
.daterangepicker .ranges {
float: left;
}
.daterangepicker.rtl .ranges {
float: right;
}
.daterangepicker .drp-calendar.left {
clear: none !important;
}
}

2
npm/packs/bootstrap-daterangepicker/src/daterangepicker.js

@ -4,6 +4,8 @@
* @copyright: Copyright (c) 2012-2019 Dan Grossman. All rights reserved.
* @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
* @website: http://www.daterangepicker.com/
* Patched copy: autoApply works together with timePicker, predefined ranges start at the beginning of the day,
* and Function.prototype.bind is used instead of the jQuery.proxy API deprecated in jQuery 4.
*/
// Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
(function (root, factory) {

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"
},

5
npm/packs/jquery-form/abp.resourcemapping.js

@ -1,5 +0,0 @@
module.exports = {
mappings: {
"@node_modules/@abp/jquery-form/src/jquery.form.min.js": "@libs/jquery-form/"
}
}

32
npm/packs/jquery-form/package.json

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

22
npm/packs/jquery-form/src/jquery.form.min.js

File diff suppressed because one or more lines are too long

2
npm/packs/jquery-validation-unobtrusive/abp.resourcemapping.js

@ -1,5 +1,5 @@
module.exports = {
mappings: {
"@node_modules/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js": "@libs/jquery-validation-unobtrusive/"
"@node_modules/@abp/jquery-validation-unobtrusive/src/jquery.validate.unobtrusive.js": "@libs/jquery-validation-unobtrusive/"
}
}

3
npm/packs/jquery-validation-unobtrusive/package.json

@ -10,8 +10,7 @@
"access": "public"
},
"dependencies": {
"@abp/jquery-validation": "~10.7.0-rc.3",
"jquery-validation-unobtrusive": "^4.0.0"
"@abp/jquery-validation": "~10.7.0-rc.3"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",

436
npm/packs/jquery-validation-unobtrusive/src/jquery.validate.unobtrusive.js

@ -0,0 +1,436 @@
/**
* @license
* Unobtrusive validation support library for jQuery and jQuery Validate
* Copyright (c) .NET Foundation. All rights reserved.
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
* @version v4.0.0
* Patched copy: uses JSON.parse, typeof and Function.prototype.bind instead of the jQuery APIs removed or deprecated in jQuery 4.
*/
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
/*global document: false, jQuery: false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports
module.exports = factory(require('jquery-validation'));
} else {
// Browser global
jQuery.validator.unobtrusive = factory(jQuery);
}
}(function ($) {
var $jQval = $.validator,
adapters,
data_validation = "unobtrusiveValidation";
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function splitAndTrim(value) {
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
}
function escapeAttributeValue(value) {
// As mentioned on http://api.jquery.com/category/selectors/
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? JSON.parse(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
}
function onErrors(event, validator) { // 'this' is the form element
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
function onSuccess(error) { // 'this' is the form element
var container = error.data("unobtrusiveContainer");
if (container) {
var replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? JSON.parse(replaceAttrValue) : null;
container.addClass("field-validation-valid").removeClass("field-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container.empty();
}
}
}
function onReset(event) { // 'this' is the form element
var $form = $(this),
key = '__jquery_unobtrusive_validation_form_reset';
if ($form.data(key)) {
return;
}
// Set a flag that indicates we're currently resetting the form.
$form.data(key, true);
try {
$form.data("validator").resetForm();
} finally {
$form.removeData(key);
}
$form.find(".validation-summary-errors")
.addClass("validation-summary-valid")
.removeClass("validation-summary-errors");
$form.find(".field-validation-error")
.addClass("field-validation-valid")
.removeClass("field-validation-error")
.removeData("unobtrusiveContainer")
.find(">*") // If we were using valmsg-replace, get the underlying error
.removeData("unobtrusiveContainer");
}
function validationInfo(form) {
var $form = $(form),
result = $form.data(data_validation),
onResetProxy = onReset.bind(form),
defaultOptions = $jQval.unobtrusive.options || {},
execInContext = function (name, args) {
var func = defaultOptions[name];
func && typeof func === "function" && func.apply(form, args);
};
if (!result) {
result = {
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
},
attachValidation: function () {
$form
.off("reset." + data_validation)
.on("reset." + data_validation, onResetProxy)
.validate(this.options);
},
validate: function () { // a validation function that is called by unobtrusive Ajax
$form.validate();
return $form.valid();
}
};
$form.data(data_validation, result);
}
return result;
}
$jQval.unobtrusive = {
adapters: [],
parseElement: function (element, skipAttach) {
/// <summary>
/// Parses a single HTML element for unobtrusive validation attributes.
/// </summary>
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
/// validation to the form. If parsing just this single element, you should specify true.
/// If parsing several elements, you should specify false, and manually attach the validation
/// to the form when you are finished. The default is false.</param>
var $element = $(element),
form = $element.parents("form")[0],
valInfo, rules, messages;
if (!form) { // Cannot do client-side validation without a form
return;
}
valInfo = validationInfo(form);
valInfo.options.rules[element.name] = rules = {};
valInfo.options.messages[element.name] = messages = {};
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name,
message = $element.attr(prefix),
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () {
paramValues[this] = $element.attr(prefix + this);
});
this.adapt({
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
});
$.extend(rules, { "__dummy__": true });
if (!skipAttach) {
valInfo.attachValidation();
}
},
parse: function (selector) {
/// <summary>
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
/// attribute values.
/// </summary>
/// <param name="selector" type="String">Any valid jQuery selector.</param>
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
// element with data-val=true
var $selector = $(selector),
$forms = $selector.parents()
.addBack()
.filter("form")
.add($selector.find("form"))
.has("[data-val=true]");
$selector.find("[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
$forms.each(function () {
var info = validationInfo(this);
if (info) {
info.attachValidation();
}
});
}
};
adapters = $jQval.unobtrusive.adapters;
adapters.add = function (adapterName, params, fn) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
/// mmmm is the parameter name).</param>
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
/// attributes into jQuery Validate rules and/or messages.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
adapters.addBool = function (adapterName, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has no parameter values.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, function (options) {
setValidationValues(options, ruleName || adapterName, true);
});
};
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a minimum value.</param>
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a maximum value.</param>
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
/// have both a minimum and maximum value.</param>
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the minimum value. The default is "min".</param>
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the maximum value. The default is "max".</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
var min = options.params.min,
max = options.params.max;
if (min && max) {
setValidationValues(options, minMaxRuleName, [min, max]);
}
else if (min) {
setValidationValues(options, minRuleName, min);
}
else if (max) {
setValidationValues(options, maxRuleName, max);
}
});
};
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has a single value.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
/// The default is "val".</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [attribute || "val"], function (options) {
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
});
};
$jQval.addMethod("__dummy__", function (value, element, params) {
return true;
});
$jQval.addMethod("regex", function (value, element, params) {
var match;
if (this.optional(element)) {
return true;
}
match = new RegExp(params).exec(value);
return (match && (match.index === 0) && (match[0].length === value.length));
});
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
var match;
if (nonalphamin) {
match = value.match(/\W/g);
match = match && match.length >= nonalphamin;
}
return match;
});
if ($jQval.methods.extension) {
adapters.addSingleVal("accept", "mimtype");
adapters.addSingleVal("extension", "extension");
} else {
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
// validating the extension, and ignore mime-type validations as they are not supported.
adapters.addSingleVal("extension", "extension", "accept");
}
adapters.addSingleVal("regex", "pattern");
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
adapters.add("equalto", ["other"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
setValidationValues(options, "equalTo", element);
});
adapters.add("required", function (options) {
// jQuery Validate equates "required" with "mandatory" for checkbox elements
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
setValidationValues(options, "required", true);
}
});
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
// For checkboxes and radio buttons, only pick up values from checked fields.
if (field.is(":checkbox")) {
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
}
else if (field.is(":radio")) {
return field.filter(":checked").val() || '';
}
return field.val();
};
});
setValidationValues(options, "remote", value);
});
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
if (options.params.min) {
setValidationValues(options, "minlength", options.params.min);
}
if (options.params.nonalphamin) {
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
}
if (options.params.regex) {
setValidationValues(options, "regex", options.params.regex);
}
});
adapters.add("fileextensions", ["extensions"], function (options) {
setValidationValues(options, "extension", options.params.extensions);
});
$(function () {
$jQval.unobtrusive.parse(document);
});
return $jQval.unobtrusive;
}));

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"
]
}

0
npm/packs/jquery-form/README.md → npm/packs/owl.carousel/README.md

5
npm/packs/owl.carousel/abp.resourcemapping.js

@ -0,0 +1,5 @@
module.exports = {
mappings: {
"@node_modules/owl.carousel/dist/**/*.*": "@libs/owl.carousel/"
}
}

15
npm/packs/toastr/package.json → npm/packs/owl.carousel/package.json

@ -1,20 +1,19 @@
{
"version": "10.7.0-rc.3",
"name": "@abp/toastr",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git",
"directory": "npm/packs/toastr"
},
"name": "@abp/owl.carousel",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@abp/jquery": "~10.7.0-rc.3",
"toastr": "^2.1.4"
"@abp/core": "~10.7.0-rc.3",
"owl.carousel": "^2.3.4"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",
"repository": {
"type": "git",
"url": "https://github.com/abpframework/abp.git"
},
"license": "LGPL-3.0",
"keywords": [
"aspnetcore",

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",

6
npm/packs/timeago/abp.resourcemapping.js

@ -1,6 +1,6 @@
module.exports = {
mappings: {
"@node_modules/timeago/jquery.timeago.js": "@libs/timeago/",
"@node_modules/timeago/locales/*.*": "@libs/timeago/locales/"
"@node_modules/timeago.js/dist/*.js": "@libs/timeago/",
"@node_modules/@abp/timeago/src/*.*": "@libs/abp/timeago/"
}
}
}

2
npm/packs/timeago/package.json

@ -11,7 +11,7 @@
},
"dependencies": {
"@abp/jquery": "~10.7.0-rc.3",
"timeago": "^1.6.7"
"timeago.js": "^4.0.2"
},
"gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431",
"homepage": "https://abp.io",

164
npm/packs/timeago/src/abp.timeago.js

@ -0,0 +1,164 @@
var abp = abp || {};
(function ($) {
if (typeof timeago === 'undefined') {
throw "abp/timeago library requires the timeago.js library included to the page!";
}
abp.timeago = abp.timeago || {};
// ABP culture name -> timeago.js locale name (only the ones that don't match the two-letter language code)
abp.timeago.localeMap = {
'en': 'en_US',
'zh': 'zh_CN',
'zh-Hans': 'zh_CN',
'zh-Hant': 'zh_TW',
'zh-CN': 'zh_CN',
'zh-TW': 'zh_TW',
'pt': 'pt_BR',
'nb': 'nb_NO',
'no': 'nb_NO',
'nn': 'nn_NO',
'hi': 'hi_IN',
'id': 'id_ID',
'bn': 'bn_IN'
};
abp.timeago.getLocale = function (cultureName) {
cultureName = cultureName || (abp.localization && abp.localization.currentCulture && abp.localization.currentCulture.cultureName) || 'en';
if (abp.timeago.localeMap[cultureName]) {
return abp.timeago.localeMap[cultureName];
}
var language = cultureName.split('-')[0];
return abp.timeago.localeMap[language] || language;
};
abp.timeago.format = function (date, options) {
return timeago.format(date, abp.timeago.getLocale(), options);
};
var toNodeList = function (nodes) {
if (!nodes) {
return [];
}
return Array.prototype.filter.call(nodes.nodeType ? [nodes] : nodes, function (node) {
return node && node.getAttribute;
});
};
// <time> elements carry the date in the "datetime" attribute, other elements may carry it in "title"
var getDateAttribute = function (node) {
var datetime = node.getAttribute('datetime');
if (datetime || node.tagName === 'TIME') {
return datetime;
}
return node.getAttribute('title');
};
abp.timeago.render = function (nodes, options) {
var nodeList = toNodeList(nodes).filter(function (node) {
var datetime = getDateAttribute(node);
if (!datetime) {
return false;
}
node.setAttribute('datetime', datetime);
return true;
});
if (!nodeList.length) {
return nodeList;
}
return timeago.render(nodeList, abp.timeago.getLocale(), options);
};
abp.timeago.cancel = function (nodes) {
if (nodes === undefined) {
timeago.cancel();
return;
}
toNodeList(nodes).forEach(function (node) {
timeago.cancel(node);
});
};
// Locales that ship with ABP's default languages but are missing in timeago.full.min.js
var slavicIndex = function (number, index) {
return (index % 2 === 1 && number >= 5) ? 1 : 0;
};
timeago.register('cs', function (number, index) {
return [
[['právě teď', 'právě teď']],
[['před %s vteřinami', 'za %s vteřiny'], ['před %s vteřinami', 'za %s vteřin']],
[['před minutou', 'za minutu']],
[['před %s minutami', 'za %s minuty'], ['před %s minutami', 'za %s minut']],
[['před hodinou', 'za hodinu']],
[['před %s hodinami', 'za %s hodiny'], ['před %s hodinami', 'za %s hodin']],
[['včera', 'zítra']],
[['před %s dny', 'za %s dny'], ['před %s dny', 'za %s dnů']],
[['minulý týden', 'příští týden']],
[['před %s týdny', 'za %s týdny'], ['před %s týdny', 'za %s týdnů']],
[['minulý měsíc', 'příští měsíc']],
[['před %s měsíci', 'za %s měsíce'], ['před %s měsíci', 'za %s měsíců']],
[['před rokem', 'příští rok']],
[['před %s lety', 'za %s roky'], ['před %s lety', 'za %s let']]
][index][slavicIndex(number, index)];
});
timeago.register('sk', function (number, index) {
return [
[['práve teraz', 'práve teraz']],
[['pred %s sekundami', 'o %s sekundy'], ['pred %s sekundami', 'o %s sekúnd']],
[['pred minútou', 'o minútu']],
[['pred %s minútami', 'o %s minúty'], ['pred %s minútami', 'o %s minút']],
[['pred hodinou', 'o hodinu']],
[['pred %s hodinami', 'o %s hodiny'], ['pred %s hodinami', 'o %s hodín']],
[['pred %s dňom', 'o %s deň']],
[['pred %s dňami', 'o %s dni'], ['pred %s dňami', 'o %s dní']],
[['pred %s týždňom', 'o %s týždeň']],
[['pred %s týždňami', 'o %s týždne'], ['pred %s týždňami', 'o %s týždňov']],
[['pred %s mesiacom', 'o %s mesiac']],
[['pred %s mesiacmi', 'o %s mesiace'], ['pred %s mesiacmi', 'o %s mesiacov']],
[['pred %s rokom', 'o %s rok']],
[['pred %s rokmi', 'o %s roky'], ['pred %s rokmi', 'o %s rokov']]
][index][slavicIndex(number, index)];
});
if (!$) {
return;
}
$.timeago = function (date) {
if (date && date.jquery) {
date = date[0];
}
if (date && date.nodeType === 1) {
date = getDateAttribute(date);
}
return abp.timeago.format(date);
};
$.fn.timeago = function (action, options) {
if (action === 'dispose') {
abp.timeago.cancel(this.toArray());
return this;
}
if (action === 'update' && options !== undefined && options !== null) {
this.attr('datetime', options instanceof Date ? options.toISOString() : options);
}
abp.timeago.render(this.toArray());
return this;
};
})(window.jQuery);

129
npm/packs/toastr/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/toastr/abp.resourcemapping.js

@ -1,5 +0,0 @@
module.exports = {
mappings: {
"@node_modules/toastr/build/*.*": "@libs/toastr/"
}
}
Loading…
Cancel
Save