Browse Source

Merge branch 'dev' into auto-merge/rel-4-1/7

pull/6783/head
Muhammed Altuğ 5 years ago
committed by GitHub
parent
commit
03cf042be3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      .github/workflows/auto-pr.yml
  2. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  3. 5
      docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md
  4. 41
      docs/en/Entity-Framework-Core.md
  5. 41
      docs/en/MongoDB.md
  6. 5
      docs/en/Repositories.md
  7. 4
      docs/en/Samples/Microservice-Demo.md
  8. 10
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/Branding.razor
  9. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor
  10. 16
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor
  11. 2
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor
  12. 9
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs
  13. 49
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpIdNameTagHelper.cs
  14. 8
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml
  15. 41
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs
  16. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  17. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  18. 41
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  19. 37
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs
  20. 9
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs
  21. 26
      framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs
  22. 4
      framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs
  23. 76
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs
  24. 52
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IBasicRepository.cs
  25. 26
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs
  26. 95
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs
  27. 40
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreBulkOperationProvider.cs
  28. 11
      framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs
  29. 7
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs
  30. 39
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbBulkOperationProvider.cs
  31. 21
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs
  32. 173
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs
  33. 27
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs
  34. 7
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlOptions.cs
  35. 6
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs
  36. 2
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs
  37. 13
      framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs
  38. 6
      framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs
  39. 69
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs
  40. 30
      modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml
  41. 37
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs
  42. 4
      modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js
  43. 14
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml
  44. 5
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml
  45. 7
      modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml
  46. 29
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml
  47. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Menus/MyProjectNameMenuContributor.cs
  48. 3
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json
  49. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs
  50. 3
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json
  51. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs
  52. 3
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json
  53. 17
      test/AbpPerfTest/AbpPerfTest.WithAbp/AbpPerfTest.WithAbp.csproj
  54. 55
      test/AbpPerfTest/AbpPerfTest.WithAbp/AppModule.cs
  55. 87
      test/AbpPerfTest/AbpPerfTest.WithAbp/Controllers/BookController.cs
  56. 14
      test/AbpPerfTest/AbpPerfTest.WithAbp/Controllers/HomeController.cs
  57. 15
      test/AbpPerfTest/AbpPerfTest.WithAbp/Dtos/BookDto.cs
  58. 11
      test/AbpPerfTest/AbpPerfTest.WithAbp/Dtos/CreateUpdateBookDto.cs
  59. 19
      test/AbpPerfTest/AbpPerfTest.WithAbp/Entities/Book.cs
  60. 28
      test/AbpPerfTest/AbpPerfTest.WithAbp/EntityFramework/BookDbContext.cs
  61. 29
      test/AbpPerfTest/AbpPerfTest.WithAbp/EntityFramework/BookDbContextFactory.cs
  62. 48
      test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/20201222135738_Added_Books.Designer.cs
  63. 31
      test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/20201222135738_Added_Books.cs
  64. 46
      test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/BookDbContextModelSnapshot.cs
  65. 21
      test/AbpPerfTest/AbpPerfTest.WithAbp/Program.cs
  66. 28
      test/AbpPerfTest/AbpPerfTest.WithAbp/Properties/launchSettings.json
  67. 18
      test/AbpPerfTest/AbpPerfTest.WithAbp/Startup.cs
  68. 9
      test/AbpPerfTest/AbpPerfTest.WithAbp/appsettings.Development.json
  69. 13
      test/AbpPerfTest/AbpPerfTest.WithAbp/appsettings.json
  70. 15
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/AbpPerfTest.WithoutAbp.csproj
  71. 93
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Controllers/BookController.cs
  72. 14
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Controllers/HomeController.cs
  73. 15
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Dtos/BookDto.cs
  74. 11
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Dtos/CreateUpdateBookDto.cs
  75. 20
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Entities/Book.cs
  76. 27
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/EntityFramework/BookDbContext.cs
  77. 29
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/EntityFramework/BookDbContextFactory.cs
  78. 47
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/20201222132615_Added_Books.Designer.cs
  79. 31
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/20201222132615_Added_Books.cs
  80. 45
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/BookDbContextModelSnapshot.cs
  81. 17
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Program.cs
  82. 28
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Properties/launchSettings.json
  83. 45
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/Startup.cs
  84. 9
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/appsettings.Development.json
  85. 13
      test/AbpPerfTest/AbpPerfTest.WithoutAbp/appsettings.json
  86. 22
      test/AbpPerfTest/AbpPerfTest.sln
  87. 205
      test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx
  88. 205
      test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx
  89. BIN
      test/AbpPerfTest/_results/get-insert-with-abp.png
  90. BIN
      test/AbpPerfTest/_results/get-insert-without-abp.png

12
.github/workflows/auto-pr.yml

@ -1,10 +1,10 @@
name: Merge branch dev with rel-4.1
name: Merge branch dev with rel-4.2
on:
push:
branches:
- rel-4.1
- rel-4.2
jobs:
merge-dev-with-rel-4-1:
merge-dev-with-rel:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -17,8 +17,8 @@ jobs:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
branch: auto-merge/rel-4-1/${{github.run_number}}
title: Merge branch dev with rel-4.1
body: This PR generated automatically to merge dev with rel-4.1. Please review the changed files before merging to prevent any errors that may occur.
branch: auto-merge/rel-4-2/${{github.run_number}}
title: Merge branch dev with rel-4.2
body: This PR generated automatically to merge dev with rel-4.2. Please review the changed files before merging to prevent any errors that may occur.
reviewers: ${{github.actor}}
token: ${{ github.token }}

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

@ -37,7 +37,7 @@
"CreateArticleTitleInfo": "Title of the article to be shown on the article list.",
"CreateArticleUrlInfo": "Original GitHub/External URL of the article.",
"CreateArticleSummaryInfo": "A short summary of the article to be shown on the article list.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Upload 16:9 aspect ratio pictures for the best view.",
"CreateArticleCoverInfo": "For creating an effective article, add a cover photo. Upload 16:9 aspect ratio pictures for the best view. Maximum file size: 1MB.",
"ThisExtensionIsNotAllowed": "This extension is not allowed.",
"TheFileIsTooLarge": "The file is too large.",
"GoToTheArticle": "Go to the Article",

5
docs/en/Community-Articles/2020-12-10-How-to-Integrate-the-Telerik-Blazor-Component/POST.md

@ -52,7 +52,10 @@ abp new TelerikComponents --ui blazor --database-provider ef
```html
...
<link rel="stylesheet" href="https:unpkg.com/@progress/kendo-theme-default@latest/dist/all.css" />
<link rel="stylesheet" href="_content/Telerik.UI.for.Blazor/css/kendo-theme-default/all.css" />
<!-- For Trial licenses use
<link rel="stylesheet" href="_content/Telerik.UI.for.Blazor.Trial/css/kendo-theme-default/all.css" />
-->
<script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
<!-- For Trial licenses use
<script src="_content/Telerik.UI.for.Blazor.Trial/js/telerik-blazor.js" defer></script>

41
docs/en/Entity-Framework-Core.md

@ -735,6 +735,47 @@ Configure<AbpDbContextOptions>(options =>
});
````
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing`IEfCoreBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomEfCoreBulkOperationProvider : IEfCoreBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TDbContext, TEntity>(IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
// Your logic here.
}
}
```
## See Also
* [Entities](Entities.md)

41
docs/en/MongoDB.md

@ -382,3 +382,44 @@ context.Services.AddMongoDbContext<OtherMongoDbContext>(options =>
```
In this example, `OtherMongoDbContext` implements `IBookStoreMongoDbContext`. This feature allows you to have multiple MongoDbContext (one per module) on development, but single MongoDbContext (implements all interfaces of all MongoDbContexts) on runtime.
### Customize Bulk Operations
If you have better logic or using an external library for bulk operations, you can override the logic via implementing `IMongoDbBulkOperationProvider`.
- You may use example template below:
```csharp
public class MyCustomMongoDbBulkOperationProvider : IMongoDbBulkOperationProvider, ITransientDependency
{
public async Task DeleteManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task InsertManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
public async Task UpdateManyAsync<TEntity>(IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken)
where TEntity : class, IEntity
{
// Your logic here.
}
}
```

5
docs/en/Repositories.md

@ -87,6 +87,11 @@ If your entity is a soft-delete entity, you can use the `HardDeleteAsync` method
See the [Data Filtering](Data-Filtering.md) documentation for more about soft-delete.
## Bulk Operations
You can execute bulk operations with `InsertManyAsync`, `UpdateManyAsync`, `DeleteManyAsync` methods.
> **WARNING:** ConcurrencyStamp can't be checked at bulk operations!
## Custom Repositories
Default generic repositories will be sufficient for most cases. However, you may need to create a custom repository class for your entity.

4
docs/en/Samples/Microservice-Demo.md

@ -19,7 +19,7 @@ This sample aims to demonstrate a simple yet complete microservice solution;
* Has a **console application** to show the simplest way of using a service by authenticating.
* Uses [Redis](https://redis.io/) for **distributed caching**.
* Uses [RabbitMQ](https://www.rabbitmq.com/) for service-to-service **messaging**.
* Uses [Docker](https://www.docker.com/) & [Kubernates](https://kubernetes.io/) to **deploy** & run all services and applications.
* Uses [Docker](https://www.docker.com/) & [Kubernetes](https://kubernetes.io/) to **deploy** & run all services and applications.
* Uses [Elasticsearch](https://www.elastic.co/products/elasticsearch) & [Kibana](https://www.elastic.co/products/kibana) to store and visualize the logs (written using [Serilog](https://serilog.net/)).
The diagram below shows the system:
@ -1416,4 +1416,4 @@ An Audit Log record has a `CorrelationId` property that can be used to track a r
### Multi-Tenancy
The solution has been configured to provide a [multi-tenant](../Multi-Tenancy.md) system, where each tenant can have their isolated users, roles, permissions and other data.
The solution has been configured to provide a [multi-tenant](../Multi-Tenancy.md) system, where each tenant can have their isolated users, roles, permissions and other data.

10
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/Branding.razor

@ -1,3 +1,9 @@
@using Volo.Abp.Ui.Branding
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
<a class="navbar-brand" href="">@BrandingProvider.AppName</a>
<a class="navbar-brand" href="">
@if (!BrandingProvider.LogoUrl.IsNullOrWhiteSpace())
{
<img src="@BrandingProvider.LogoUrl" alt="@BrandingProvider.AppName" >
}
@BrandingProvider.AppName
</a>

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/FirstLevelNavMenuItem.razor

@ -10,7 +10,7 @@
if (MenuItem.Url != null)
{
<li class="nav-item @cssClass @disabled" id="@elementId">
<a class="nav-link" href="@url">
<a class="nav-link" href="@url" target="@MenuItem.Target">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))

16
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor

@ -3,6 +3,7 @@
@using Volo.Abp.MultiTenancy
@inject ICurrentUser CurrentUser
@inject ICurrentTenant CurrentTenant
@inject IJSRuntime JsRuntime
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
@ -23,7 +24,7 @@
{
@foreach (var menuItem in Menu.Items)
{
<DropdownItem Clicked="@(() => NavigateTo(menuItem.Url))">@menuItem.DisplayName</DropdownItem>
<DropdownItem Clicked="@(() => NavigateToAsync(menuItem.Url, menuItem.Target))">@menuItem.DisplayName</DropdownItem>
}
}
<DropdownDivider />
@ -37,14 +38,21 @@
</AuthorizeView>
@code{
private void NavigateTo(string uri)
private async Task NavigateToAsync(string uri, string target = null)
{
Navigation.NavigateTo(uri);
if (target == "_blank")
{
await JsRuntime.InvokeVoidAsync("open", uri, target);
}
else
{
Navigation.NavigateTo(uri);
}
}
private async Task BeginSignOut()
{
await SignOutManager.SetSignOutState();
NavigateTo("authentication/logout");
await NavigateToAsync("authentication/logout");
}
}

2
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/SecondLevelNavMenuItem.razor

@ -9,7 +9,7 @@
{
if (MenuItem.Url != null)
{
<a class="dropdown-item @cssClass @disabled" href="@url" id="@elementId">
<a class="dropdown-item @cssClass @disabled" href="@url" target="@MenuItem.Target" id="@elementId">
@if (MenuItem.Icon != null)
{
if (MenuItem.Icon.StartsWith("fa"))

9
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs

@ -4,11 +4,13 @@ using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -20,15 +22,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
private readonly HtmlEncoder _htmlEncoder;
private readonly IHtmlGenerator _htmlGenerator;
private readonly IServiceProvider _serviceProvider;
private readonly IStringLocalizer<AbpUiResource> _localizer;
public AbpDynamicFormTagHelperService(
HtmlEncoder htmlEncoder,
IHtmlGenerator htmlGenerator,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IStringLocalizer<AbpUiResource> localizer)
{
_htmlEncoder = htmlEncoder;
_htmlGenerator = htmlGenerator;
_serviceProvider = serviceProvider;
_localizer = localizer;
}
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -191,7 +196,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
var abpButtonTagHelper = _serviceProvider.GetRequiredService<AbpButtonTagHelper>();
var attributes = new TagHelperAttributeList { new TagHelperAttribute("type", "submit") };
abpButtonTagHelper.Text = "Submit";
abpButtonTagHelper.Text = _localizer["Submit"];
abpButtonTagHelper.ButtonType = AbpButtonType.Primary;
return await abpButtonTagHelper.RenderAsync(attributes, context, _htmlEncoder, "button", TagMode.StartTagAndEndTag);

49
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpIdNameTagHelper.cs

@ -0,0 +1,49 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Options;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{
[HtmlTargetElement(Attributes = "abp-id-name")]
public class AbpIdNameTagHelper : AbpTagHelper
{
/// <summary>
/// Make sure this TagHelper is executed first.
/// </summary>
public override int Order => -1000 - 1;
[HtmlAttributeName("abp-id-name")]
public ModelExpression IdNameFor { get; set; }
private readonly MvcViewOptions _mvcViewOptions;
public AbpIdNameTagHelper(IOptions<MvcViewOptions> mvcViewOptions)
{
_mvcViewOptions = mvcViewOptions.Value;
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (IdNameFor != null)
{
if (!context.AllAttributes.Any(x => x.Name.Equals("id", StringComparison.OrdinalIgnoreCase)))
{
var id = TagBuilder.CreateSanitizedId(IdNameFor.Name, _mvcViewOptions.HtmlHelperOptions.IdAttributeDotReplacement);
output.Attributes.Add("id", id);
}
if (!context.AllAttributes.Any(x => x.Name.Equals("name", StringComparison.OrdinalIgnoreCase)))
{
output.Attributes.Add("name", IdNameFor.Name);
}
}
return Task.CompletedTask;
}
}
}

8
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml

@ -1,3 +1,9 @@
@using Volo.Abp.Ui.Branding
@inject IBrandingProvider BrandingProvider
<a class="navbar-brand" href="~/">@BrandingProvider.AppName</a>
<a class="navbar-brand" href="~/">
@if (!BrandingProvider.LogoUrl.IsNullOrWhiteSpace())
{
<img src="@BrandingProvider.LogoUrl" alt="@BrandingProvider.AppName" >
}
@BrandingProvider.AppName
</a>

41
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs

@ -16,6 +16,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Uow;
using Volo.Abp.Users;
@ -125,6 +126,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
protected IAppUrlProvider AppUrlProvider => LazyGetRequiredService(ref _appUrlProvider);
private IAppUrlProvider _appUrlProvider;
protected virtual NoContentResult NoContent() //TODO: Is that true to return empty result like that?
{
return new NoContentResult();
@ -165,5 +169,42 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
return localizer;
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/"; //TODO: ???
}
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj

@ -23,7 +23,7 @@
<ProjectReference Include="..\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" />
<ProjectReference Include="..\Volo.Abp.GlobalFeatures\Volo.Abp.GlobalFeatures.csproj" />
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
<ProjectReference Include="..\Volo.Abp.UI\Volo.Abp.UI.csproj" />
<ProjectReference Include="..\Volo.Abp.UI.Navigation\Volo.Abp.UI.Navigation.csproj" />
</ItemGroup>
<ItemGroup>

3
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -39,6 +39,7 @@ using Volo.Abp.Json;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.UI;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.AspNetCore.Mvc
{
@ -47,7 +48,7 @@ namespace Volo.Abp.AspNetCore.Mvc
typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiModule),
typeof(AbpUiNavigationModule),
typeof(AbpGlobalFeaturesModule)
)]
public class AbpAspNetCoreMvcModule : AbpModule

41
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs

@ -14,6 +14,7 @@ using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Timing;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Uow;
using Volo.Abp.Users;
@ -115,6 +116,9 @@ namespace Volo.Abp.AspNetCore.Mvc
}
private IStringLocalizer _localizer;
protected IAppUrlProvider AppUrlProvider => LazyGetRequiredService(ref _appUrlProvider);
private IAppUrlProvider _appUrlProvider;
protected Type LocalizationResource
{
get => _localizationResource;
@ -148,5 +152,42 @@ namespace Volo.Abp.AspNetCore.Mvc
return localizer;
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl) || AppUrlProvider.IsRedirectAllowedUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/";
}
}
}

37
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Authentication/ChallengeAccountController.cs

@ -66,42 +66,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Authentication
return NoContent();
}
protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
private string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
private string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual string GetAppHomeUrl()
{
return "~/";
}
}
}

9
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpBackgroundJobsHangfireModule.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -13,6 +14,12 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
)]
public class AbpBackgroundJobsHangfireModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(serviceProvider =>
serviceProvider.GetRequiredService<AbpDashboardOptionsProvider>().Get());
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
@ -29,4 +36,4 @@ namespace Volo.Abp.BackgroundJobs.Hangfire
return null;
}
}
}
}

26
framework/src/Volo.Abp.BackgroundJobs.HangFire/Volo/Abp/BackgroundJobs/Hangfire/AbpDashboardOptionsProvider.cs

@ -0,0 +1,26 @@
using System.Linq;
using Hangfire;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundJobs.Hangfire
{
public class AbpDashboardOptionsProvider : ITransientDependency
{
protected AbpBackgroundJobOptions AbpBackgroundJobOptions { get; }
public AbpDashboardOptionsProvider(IOptions<AbpBackgroundJobOptions> abpBackgroundJobOptions)
{
AbpBackgroundJobOptions = abpBackgroundJobOptions.Value;
}
public virtual DashboardOptions Get()
{
return new DashboardOptions
{
DisplayNameFunc = (dashboardContext, job) =>
AbpBackgroundJobOptions.GetJob(job.Args.First().GetType()).JobName
};
}
}
}

4
framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs

@ -133,7 +133,7 @@ namespace System
{
if (str.IsNullOrEmpty())
{
return null;
return str;
}
if (postFixes.IsNullOrEmpty())
@ -174,7 +174,7 @@ namespace System
{
if (str.IsNullOrEmpty())
{
return null;
return str;
}
if (preFixes.IsNullOrEmpty())

76
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/BasicRepositoryBase.cs

@ -1,9 +1,13 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Linq;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
@ -17,6 +21,14 @@ namespace Volo.Abp.Domain.Repositories
{
public IServiceProvider ServiceProvider { get; set; }
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public ICancellationTokenProvider CancellationTokenProvider { get; set; }
protected BasicRepositoryBase()
@ -26,10 +38,59 @@ namespace Volo.Abp.Domain.Repositories
public abstract Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await InsertAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
protected virtual Task SaveChangesAsync(CancellationToken cancellationToken)
{
if (UnitOfWorkManager?.Current != null)
{
return UnitOfWorkManager.Current.SaveChangesAsync(cancellationToken);
}
return Task.CompletedTask;
}
public abstract Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await UpdateAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await DeleteAsync(entity, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
public abstract Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default);
public abstract Task<long> GetCountAsync(CancellationToken cancellationToken = default);
@ -69,5 +130,18 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

52
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IBasicRepository.cs

@ -1,4 +1,5 @@
using System.Threading;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities;
@ -21,7 +22,19 @@ namespace Volo.Abp.Domain.Repositories
Task<TEntity> InsertAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing entity.
/// Inserts multiple new entities.
/// </summary>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <param name="entities">Entities to be inserted.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task InsertManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing entity.
/// </summary>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
@ -32,6 +45,17 @@ namespace Volo.Abp.Domain.Repositories
[NotNull]
Task<TEntity> UpdateAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Updates multiple entities.
/// </summary>
/// <param name="entities">Entities to be updated.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.</param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task UpdateManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes an entity.
/// </summary>
@ -42,6 +66,18 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync([NotNull] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes multiple entities.
/// </summary>
/// <param name="entities">Entities to be deleted.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default);
}
public interface IBasicRepository<TEntity, TKey> : IBasicRepository<TEntity>, IReadOnlyBasicRepository<TEntity, TKey>
@ -57,5 +93,17 @@ namespace Volo.Abp.Domain.Repositories
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default); //TODO: Return true if deleted
/// <summary>
/// Deletes multiple entities by primary keys.
/// </summary>
/// <param name="ids">Primary keys of the each entity.</param>
/// <param name="autoSave">
/// Set true to automatically save changes to database.
/// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database.
/// </param>
/// <param name="cancellationToken">A <see cref="T:System.Threading.CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>Awaitable <see cref="Task"/>.</returns>
Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default);
}
}

26
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs

@ -1,4 +1,5 @@
using System;
using JetBrains.Annotations;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -16,14 +17,6 @@ namespace Volo.Abp.Domain.Repositories
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>, IUnitOfWorkManagerAccessor
where TEntity : class, IEntity
{
public IDataFilter DataFilter { get; set; }
public ICurrentTenant CurrentTenant { get; set; }
public IAsyncQueryableExecuter AsyncExecuter { get; set; }
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public virtual Type ElementType => GetQueryable().ElementType;
public virtual Expression Expression => GetQueryable().Expression;
@ -109,5 +102,20 @@ namespace Volo.Abp.Domain.Repositories
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var id in ids)
{
await DeleteAsync(id, cancellationToken: cancellationToken);
}
if (autoSave)
{
await SaveChangesAsync(cancellationToken);
}
}
}
}

95
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs

@ -1,13 +1,15 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
@ -32,6 +34,8 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
public virtual IGuidGenerator GuidGenerator { get; set; }
public IEfCoreBulkOperationProvider BulkOperationProvider { get; set; }
public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
@ -59,6 +63,32 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return savedEntity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
CheckAndSetId(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
await DbSet.AddRangeAsync(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
DbContext.Attach(entity);
@ -73,6 +103,28 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return updatedEntity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken
);
return;
}
DbSet.UpdateRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
DbSet.Remove(entity);
@ -83,6 +135,27 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
}
}
public override async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync<TDbContext, TEntity>(
this,
entities,
autoSave,
cancellationToken);
return;
}
DbSet.RemoveRange(entities);
if (autoSave)
{
await DbContext.SaveChangesAsync();
}
}
public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return includeDetails
@ -115,6 +188,11 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
return DbSet.AsQueryable();
}
protected override Task SaveChangesAsync(CancellationToken cancellationToken)
{
return DbContext.SaveChangesAsync(cancellationToken);
}
public async override Task<TEntity> FindAsync(
Expression<Func<TEntity, bool>> predicate,
bool includeDetails = true,
@ -252,7 +330,7 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
return includeDetails
? await WithDetails().FirstOrDefaultAsync(e => e.Id.Equals(id), GetCancellationToken(cancellationToken))
: await DbSet.FindAsync(new object[] {id}, GetCancellationToken(cancellationToken));
: await DbSet.FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
@ -265,5 +343,12 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
await DeleteAsync(entity, autoSave, cancellationToken);
}
public async virtual Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await DbSet.Where(x => ids.Contains(x.Id)).ToListAsync();
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

40
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/IEfCoreBulkOperationProvider.cs

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore
{
public interface IEfCoreBulkOperationProvider
{
Task InsertManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task UpdateManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
Task DeleteManyAsync<TDbContext, TEntity>(
IEfCoreRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken
)
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity;
}
}

11
framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs

@ -20,7 +20,7 @@ namespace Volo.Abp.Http.ProxyScripting
private readonly AbpApiProxyScriptingOptions _options;
public ProxyScriptManager(
IApiDescriptionModelProvider modelProvider,
IApiDescriptionModelProvider modelProvider,
IServiceProvider serviceProvider,
IJsonSerializer jsonSerializer,
IProxyScriptManagerCache cache,
@ -70,7 +70,14 @@ namespace Volo.Abp.Http.ProxyScripting
private string CreateCacheKey(ProxyScriptingModel model)
{
return _jsonSerializer.Serialize(model).ToMd5();
return _jsonSerializer.Serialize(new
{
model.GeneratorType,
model.Modules,
model.Controllers,
model.Actions,
model.Properties
}).ToMd5();
}
}
}

7
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
@ -309,5 +310,11 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
await DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await AsyncExecuter.ToListAsync(GetQueryable().Where(x => ids.Contains(x.Id)));
DeleteManyAsync(entities, autoSave, cancellationToken);
}
}
}

39
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbBulkOperationProvider.cs

@ -0,0 +1,39 @@
using MongoDB.Driver;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB
{
public interface IMongoDbBulkOperationProvider
{
Task InsertManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task UpdateManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
Task DeleteManyAsync<TEntity>(
IMongoDbRepository<TEntity> repository,
IEnumerable<TEntity> entities,
IClientSessionHandle sessionHandle,
bool autoSave,
CancellationToken cancellationToken
)
where TEntity : class, IEntity;
}
}

21
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepositoryFilterer.cs

@ -14,5 +14,26 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
FilterDefinition<TEntity> CreateEntityFilter(TKey id, bool applyFilters = false);
FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null);
/// <summary>
/// Creates filter for given entities.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="entities">Entities to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
/// <returns>Created <see cref="FilterDefinition{TDocument}"/>.</returns>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false);
/// <summary>
/// Creates filter for given ids.
/// </summary>
/// <remarks>
/// Visit https://docs.mongodb.com/manual/reference/operator/query/in/ to get more information about 'in' operator.
/// </remarks>
/// <param name="ids">Entity Ids to be filtered.</param>
/// <param name="applyFilters">Set true to use GlobalFilters. Default is false.</param>
FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false);
}
}

173
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs

@ -1,3 +1,4 @@
using JetBrains.Annotations;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
@ -15,6 +16,7 @@ using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.Volo.Abp.Domain.Repositories.MongoDB;
namespace Volo.Abp.Domain.Repositories.MongoDB
{
@ -45,6 +47,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
public IAuditPropertySetter AuditPropertySetter { get; set; }
public IMongoDbBulkOperationProvider BulkOperationProvider { get; set; }
public MongoDbRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
{
DbContextProvider = dbContextProvider;
@ -81,6 +85,34 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForAddedEntityAsync(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.InsertManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
if (SessionHandle != null)
{
await Collection.InsertManyAsync(
SessionHandle,
entities,
cancellationToken: cancellationToken);
}
else
{
await Collection.InsertManyAsync(
entities,
cancellationToken: cancellationToken);
}
}
public async override Task<TEntity> UpdateAsync(
TEntity entity,
bool autoSave = false,
@ -131,6 +163,59 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return entity;
}
public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
{
var isSoftDeleteEntity = typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity));
foreach (var entity in entities)
{
SetModificationAuditProperties(entity);
if (isSoftDeleteEntity)
{
SetDeletionAuditProperties(entity);
await TriggerEntityDeleteEventsAsync(entity);
}
else
{
await TriggerEntityUpdateEventsAsync(entity);
}
await TriggerDomainEventsAsync(entity);
SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.UpdateManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
BulkWriteResult result;
List<WriteModel<TEntity>> replaceRequests = new List<WriteModel<TEntity>>();
foreach (var entity in entities)
{
replaceRequests.Add(new ReplaceOneModel<TEntity>(CreateEntityFilter(entity), entity));
}
if (SessionHandle != null)
{
result = await Collection.BulkWriteAsync(SessionHandle, replaceRequests);
}
else
{
result = await Collection.BulkWriteAsync(replaceRequests);
}
if (result.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
public async override Task DeleteAsync(
TEntity entity,
bool autoSave = false,
@ -194,6 +279,73 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
}
}
public override async Task DeleteManyAsync(
IEnumerable<TEntity> entities,
bool autoSave = false,
CancellationToken cancellationToken = default)
{
foreach (var entity in entities)
{
await ApplyAbpConceptsForDeletedEntityAsync(entity);
var oldConcurrencyStamp = SetNewConcurrencyStamp(entity);
}
if (BulkOperationProvider != null)
{
await BulkOperationProvider.DeleteManyAsync(this, entities, SessionHandle, autoSave, cancellationToken);
return;
}
var entitiesCount = entities.Count();
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
UpdateResult updateResult;
if (SessionHandle != null)
{
updateResult = await Collection.UpdateManyAsync(
SessionHandle,
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
else
{
updateResult = await Collection.UpdateManyAsync(
CreateEntitiesFilter(entities),
Builders<TEntity>.Update.Set(x => ((ISoftDelete)x).IsDeleted, true)
);
}
if (updateResult.MatchedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
else
{
DeleteResult deleteResult;
if (SessionHandle != null)
{
deleteResult = await Collection.DeleteManyAsync(
SessionHandle,
CreateEntitiesFilter(entities)
);
}
else
{
deleteResult = await Collection.DeleteManyAsync(
CreateEntitiesFilter(entities)
);
}
if (deleteResult.DeletedCount < entitiesCount)
{
ThrowOptimisticConcurrencyException();
}
}
}
public async override Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().ToListAsync(GetCancellationToken(cancellationToken));
@ -270,6 +422,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
);
}
protected virtual FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
throw new NotImplementedException(
$"{nameof(CreateEntitiesFilter)} is not implemented for MongoDB by default. It should be overriden and implemented by the deriving class!"
);
}
protected virtual async Task ApplyAbpConceptsForAddedEntityAsync(TEntity entity)
{
CheckAndSetId(entity);
@ -477,9 +636,23 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return DeleteAsync(x => x.Id.Equals(id), autoSave, cancellationToken);
}
public virtual async Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
var entities = await GetMongoQueryable()
.Where(x => ids.Contains(x.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
await DeleteManyAsync(entities, autoSave, cancellationToken);
}
protected override FilterDefinition<TEntity> CreateEntityFilter(TEntity entity, bool withConcurrencyStamp = false, string concurrencyStamp = null)
{
return RepositoryFilterer.CreateEntityFilter(entity, withConcurrencyStamp, concurrencyStamp);
}
protected override FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool withConcurrencyStamp = false)
{
return RepositoryFilterer.CreateEntitiesFilter(entities, withConcurrencyStamp);
}
}
}

27
framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepositoryFilterer.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
@ -23,13 +24,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)) && DataFilter.IsEnabled<ISoftDelete>())
{
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete) e).IsDeleted, false));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((ISoftDelete)e).IsDeleted, false));
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
var tenantId = CurrentTenant.Id;
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant) e).TenantId, tenantId));
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IMultiTenant)e).TenantId, tenantId));
}
}
}
@ -72,8 +73,28 @@ namespace Volo.Abp.Domain.Repositories.MongoDB
return Builders<TEntity>.Filter.And(
Builders<TEntity>.Filter.Eq(e => e.Id, entity.Id),
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp) e).ConcurrencyStamp, concurrencyStamp)
Builders<TEntity>.Filter.Eq(e => ((IHasConcurrencyStamp)e).ConcurrencyStamp, concurrencyStamp)
);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TEntity> entities, bool applyFilters = false)
{
return CreateEntitiesFilter(entities.Select(s => s.Id), applyFilters);
}
public FilterDefinition<TEntity> CreateEntitiesFilter(IEnumerable<TKey> ids, bool applyFilters = false)
{
var filters = new List<FilterDefinition<TEntity>>()
{
Builders<TEntity>.Filter.In(e => e.Id, ids),
};
if (applyFilters)
{
AddGlobalFilters(filters);
}
return Builders<TEntity>.Filter.And(filters);
}
}
}

7
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlOptions.cs

@ -1,12 +1,17 @@
namespace Volo.Abp.UI.Navigation.Urls
using System.Collections.Generic;
namespace Volo.Abp.UI.Navigation.Urls
{
public class AppUrlOptions
{
public ApplicationUrlDictionary Applications { get; }
public List<string> RedirectAllowedUrls { get; }
public AppUrlOptions()
{
Applications = new ApplicationUrlDictionary();
RedirectAllowedUrls = new List<string>();
}
}
}

6
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -36,6 +37,11 @@ namespace Volo.Abp.UI.Navigation.Urls
);
}
public bool IsRedirectAllowedUrl(string url)
{
return Options.RedirectAllowedUrls.Any(url.StartsWith);
}
protected virtual Task<string> GetConfiguredUrl(string appName, string urlName)
{
var app = Options.Applications[appName];

2
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/IAppUrlProvider.cs

@ -6,5 +6,7 @@ namespace Volo.Abp.UI.Navigation.Urls
public interface IAppUrlProvider
{
Task<string> GetUrlAsync([NotNull] string appName, [CanBeNull] string urlName = null);
bool IsRedirectAllowedUrl(string url);
}
}

13
framework/test/Volo.Abp.Core.Tests/System/StringExtensions_Tests.cs

@ -182,7 +182,10 @@ namespace System
public void RemovePostFix_Tests()
{
//null case
(null as string).RemovePreFix("Test").ShouldBeNull();
(null as string).RemovePostFix("Test").ShouldBeNull();
//empty case
string.Empty.RemovePostFix("Test").ShouldBe(string.Empty);
//Simple case
"MyTestAppService".RemovePostFix("AppService").ShouldBe("MyTest");
@ -202,7 +205,13 @@ namespace System
[Fact]
public void RemovePreFix_Tests()
{
"Home.Index".RemovePreFix("NotMatchedPostfix").ShouldBe("Home.Index");
//null case
(null as string).RemovePreFix("Test").ShouldBeNull();
//empty case
string.Empty.RemovePreFix("Test").ShouldBe(string.Empty);
"Home.Index".RemovePreFix("NotMatchedPrefix").ShouldBe("Home.Index");
"Home.About".RemovePreFix("Home.").ShouldBe("About");
//Ignore case

6
framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
@ -305,6 +306,11 @@ namespace Volo.Abp.Domain.Repositories
{
throw new NotImplementedException();
}
public Task DeleteManyAsync([NotNull] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
public class MyTestCustomBaseRepository<TEntity> : MyTestDefaultRepository<TEntity>

69
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Repositories;
@ -115,5 +117,72 @@ namespace Volo.Abp.TestApp.Testing
person.Id.ShouldNotBe(Guid.Empty);
}
[Fact]
public async Task InserManyAsync()
{
var entities = new List<Person>
{
new Person(Guid.NewGuid(), "Person 1", 30),
new Person(Guid.NewGuid(), "Person 2", 31),
new Person(Guid.NewGuid(), "Person 3", 32),
new Person(Guid.NewGuid(), "Person 4", 33),
};
await PersonRepository.InsertManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
}
}
[Fact]
public async Task UpdateManyAsync()
{
var entities = await PersonRepository.GetListAsync();
var random = new Random();
entities.ForEach(f => f.Age = random.Next());
await PersonRepository.UpdateManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldNotBeNull();
person.Age.ShouldBe(entity.Age);
}
}
[Fact]
public async Task DeleteManyAsync()
{
var entities = await PersonRepository.GetListAsync();
await PersonRepository.DeleteManyAsync(entities);
foreach (var entity in entities)
{
var person = await PersonRepository.FindAsync(entity.Id);
person.ShouldBeNull();
}
}
[Fact]
public async Task DeleteManyAsync_WithId()
{
var entities = await PersonRepository.GetListAsync();
var ids = entities.Select(s => s.Id).ToArray();
await PersonRepository.DeleteManyAsync(ids);
foreach (var id in ids)
{
var person = await PersonRepository.FindAsync(id);
person.ShouldBeNull();
}
}
}
}

30
modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml

@ -32,22 +32,23 @@
<ul class="list-group">
@for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++)
{
var identityScope = Model.ConsentInput.IdentityScopes[i];
<li class="list-group-item">
<div class="form-check">
<label asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-label">
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-input" />
@Model.ConsentInput.IdentityScopes[i].DisplayName
@if (Model.ConsentInput.IdentityScopes[i].Required)
<label asp-for="@identityScope.Checked" for="ConsentInput_IdentityScopes_@(i)__Checked" class="form-check-label">
<input abp-id-name="@Model.ConsentInput.IdentityScopes[i].Checked" asp-for="@identityScope.Checked" class="form-check-input" />
@identityScope.DisplayName
@if (identityScope.Required)
{
<span><em>(required)</em></span>
}
</label>
</div>
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (Model.ConsentInput.IdentityScopes[i].Description != null)
<input abp-id-name="@Model.ConsentInput.IdentityScopes[i].Name" asp-for="@identityScope.Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (identityScope.Description != null)
{
<div class="consent-description">
@Model.ConsentInput.IdentityScopes[i].Description
@identityScope.Description
</div>
}
</li>
@ -62,22 +63,23 @@
<ul class="list-group">
@for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++)
{
var apiScope = Model.ConsentInput.ApiScopes[i];
<li class="list-group-item">
<div class="form-check">
<label asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-label">
<input asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-input" disabled="@Model.ConsentInput.ApiScopes[i].Required" />
@Model.ConsentInput.ApiScopes[i].DisplayName
@if (Model.ConsentInput.ApiScopes[i].Required)
<label asp-for="@apiScope.Checked" for="ConsentInput_ApiScopes_@(i)__Checked" class="form-check-label">
<input abp-id-name="@Model.ConsentInput.ApiScopes[i].Checked" asp-for="@apiScope.Checked" class="form-check-input" disabled="@apiScope.Required" />
@apiScope.DisplayName
@if (apiScope.Required)
{
<span><em>(required)</em></span>
}
</label>
</div>
<input asp-for="@Model.ConsentInput.ApiScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (Model.ConsentInput.ApiScopes[i].Description != null)
<input abp-id-name="@Model.ConsentInput.ApiScopes[i].Name" asp-for="@apiScope.Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@
@if (apiScope.Description != null)
{
<div class="consent-description">
@Model.ConsentInput.ApiScopes[i].Description
@apiScope.Description
</div>
}
</li>

37
modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs

@ -25,11 +25,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
ObjectMapperContext = typeof(AbpAccountWebModule);
}
protected virtual RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null)
{
return Redirect(GetRedirectUrl(returnUrl, returnUrlHash));
}
protected virtual void CheckIdentityErrors(IdentityResult identityResult)
{
if (!identityResult.Succeeded)
@ -40,33 +35,6 @@ namespace Volo.Abp.Account.Web.Pages.Account
//identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp
}
protected virtual string GetRedirectUrl(string returnUrl, string returnUrlHash = null)
{
returnUrl = NormalizeReturnUrl(returnUrl);
if (!returnUrlHash.IsNullOrWhiteSpace())
{
returnUrl = returnUrl + returnUrlHash;
}
return returnUrl;
}
protected virtual string NormalizeReturnUrl(string returnUrl)
{
if (returnUrl.IsNullOrEmpty())
{
return GetAppHomeUrl();
}
if (Url.IsLocalUrl(returnUrl))
{
return returnUrl;
}
return GetAppHomeUrl();
}
protected virtual void CheckCurrentTenant(Guid? tenantId)
{
if (CurrentTenant.Id != tenantId)
@ -74,10 +42,5 @@ namespace Volo.Abp.Account.Web.Pages.Account
throw new ApplicationException($"Current tenant is different than given tenant. CurrentTenant.Id: {CurrentTenant.Id}, given tenantId: {tenantId}");
}
}
protected virtual string GetAppHomeUrl()
{
return "~/"; //TODO: ???
}
}
}

4
modules/blogging/src/Volo.Blogging.Admin.Web/Pages/Blogging/Admin/Blogs/index.js

@ -66,9 +66,7 @@
{
target: 3,
data: 'creationTime',
render: function (date) {
return date;
},
dataFormat: "datetime"
},
{
target: 4,

14
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml

@ -32,14 +32,15 @@
<abp-tabs name="FeaturesTabs" tab-style="PillVertical" vertical-header-size="_4" class="custom-scroll-container">
@for (var i = 0; i < featureGroups.Count; i++)
{
<abp-tab title="@featureGroups[i].DisplayName" name="v-pills-tab-@featureGroups[i].GetNormalizedGroupName()">
<h4>@featureGroups[i].DisplayName</h4>
var featureGroup = featureGroups[i];
<abp-tab title="@featureGroup.DisplayName" name="v-pills-tab-@featureGroup.GetNormalizedGroupName()">
<h4>@featureGroup.DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="pl-1 pt-1">
@for (var j = 0; j < featureGroups[i].Features.Count; j++)
@for (var j = 0; j < featureGroup.Features.Count; j++)
{
var feature = featureGroups[i].Features[j];
var feature = featureGroup.Features[j];
var disabled = Model.IsDisabled(feature.Provider.Name);
<div class="mt-2">
@ -48,7 +49,7 @@
@if (feature.ValueType is ToggleStringValueType)
{
<abp-input asp-for="@featureGroups[i].Features[j].Value"
<abp-input asp-for="@feature.Value"
type="checkbox"
name="FeatureGroups[@i].Features[@j].BoolValue"
label="@feature.DisplayName"
@ -67,8 +68,9 @@
type = "number";
}
<abp-input asp-for="@featureGroups[i].Features[j].Value"
<abp-input asp-for="@feature.Value"
label="@feature.DisplayName"
name="featureGroups[@i].Features[@j].Value"
disabled="@disabled"
type="@type"
group-data-feature-name="@feature.Name"

5
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml

@ -59,8 +59,9 @@
<abp-tab title="@L["Roles"].Value">
@for (var i = 0; i < Model.Roles.Length; i++)
{
<abp-input asp-for="@Model.Roles[i].IsAssigned" label="@Model.Roles[i].Name" />
<input asp-for="@Model.Roles[i].Name" />
var role = Model.Roles[i];
<abp-input abp-id-name="@Model.Roles[i].IsAssigned" asp-for="@role.IsAssigned" label="@role.Name" />
<input abp-id-name="@Model.Roles[i].Name" asp-for="@role.Name" />
}
</abp-tab>
</abp-tabs>

7
modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml

@ -18,7 +18,7 @@
<abp-modal-header title="@L["Edit"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs name="create-user-modal-tabs">
<abp-tab title="@L["UserInformations"].Value">
<abp-tab title="@L["UserInformations"].Value">
<input asp-for="UserInfo.Id" />
<input asp-for="UserInfo.ConcurrencyStamp" />
<abp-input asp-for="UserInfo.UserName" />
@ -60,8 +60,9 @@
<abp-tab title="@L["Roles"].Value">
@for (var i = 0; i < Model.Roles.Length; i++)
{
<abp-input asp-for="@Model.Roles[i].IsAssigned" label="@Model.Roles[i].Name" />
<input asp-for="@Model.Roles[i].Name" />
var role = Model.Roles[i];
<abp-input abp-id-name="@Model.Roles[i].IsAssigned" asp-for="@role.IsAssigned" label="@role.Name" />
<input abp-id-name="@Model.Roles[i].Name" asp-for="@role.Name" />
}
</abp-tab>
</abp-tabs>

29
modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml

@ -20,25 +20,28 @@
<abp-tabs name="PermissionsTabs" tab-style="PillVertical" vertical-header-size="_4" class="custom-scroll-container">
@for (var i = 0; i < Model.Groups.Count; i++)
{
<abp-tab title="@Model.Groups[i].DisplayName" name="v-pills-tab-@Model.Groups[i].GetNormalizedGroupName()">
<h4>@Model.Groups[i].DisplayName</h4>
var group = Model.Groups[i];
<abp-tab title="@group.DisplayName" name="v-pills-tab-@group.GetNormalizedGroupName()">
<h4>@group.DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="pl-1 pt-1">
<abp-input asp-for="@Model.Groups[i].IsAllPermissionsGranted" name="SelectAllInThisTab"
id="SelectAllInThisTab-@Model.Groups[i].GetNormalizedGroupName()"
data-tab-id="v-pills-tab-@Model.Groups[i].GetNormalizedGroupName()"
<abp-input asp-for="@group.IsAllPermissionsGranted" name="SelectAllInThisTab"
id="SelectAllInThisTab-@group.GetNormalizedGroupName()"
data-tab-id="v-pills-tab-@group.GetNormalizedGroupName()"
label="@L["SelectAllInThisTab"].Value"/>
<hr class="mb-3"/>
@for (var j = 0; j < Model.Groups[i].Permissions.Count; j++)
@for (var j = 0; j < group.Permissions.Count; j++)
{
<abp-input asp-for="@Model.Groups[i].Permissions[j].IsGranted"
label="@Model.Groups[i].Permissions[j].GetShownName(Model.ProviderName)"
disabled="@Model.Groups[i].Permissions[j].IsDisabled(Model.ProviderName)"
group-data-permission-name="@Model.Groups[i].Permissions[j].Name"
group-data-parent-name="@(Model.Groups[i].Permissions[j].ParentName ?? "")"
group-style="margin-left: @(Model.Groups[i].Permissions[j].Depth * 20)px"/>
<input asp-for="@Model.Groups[i].Permissions[j].Name"/>
var permission = group.Permissions[j];
<abp-input asp-for="@permission.IsGranted"
abp-id-name="@Model.Groups[i].Permissions[j].IsGranted"
label="@permission.GetShownName(Model.ProviderName)"
disabled="@permission.IsDisabled(Model.ProviderName)"
group-data-permission-name="@permission.Name"
group-data-parent-name="@(permission.ParentName ?? "")"
group-style="margin-left: @(permission.Depth * 20)px"/>
<input asp-for="@permission.Name" abp-id-name="@Model.Groups[i].Permissions[j].Name"/>
}
</div>
</div>

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

@ -59,11 +59,10 @@ namespace MyCompanyName.MyProjectName.Blazor.Menus
context.Menu.AddItem(new ApplicationMenuItem(
"Account.Manage",
accountStringLocalizer["ManageYourProfile"],
$"{identityServerUrl.EnsureEndsWith('/')}Account/Manage",
$"{identityServerUrl.EnsureEndsWith('/')}Account/Manage?returnUrl={_configuration["App:SelfUrl"]}",
icon: "fa fa-cog",
order: 1000,
null,
"_blank"));
null));
}
return Task.CompletedTask;

3
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json

@ -1,4 +1,7 @@
{
"App": {
"SelfUrl": "https://localhost:44307"
},
"AuthServer": {
"Authority": "https://localhost:44305",
"ClientId": "MyProjectName_Blazor",

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs

@ -77,6 +77,7 @@ namespace MyCompanyName.MyProjectName
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
});
}

3
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json

@ -1,7 +1,8 @@
{
"App": {
"SelfUrl": "https://localhost:44305",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307"
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true"

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs

@ -112,6 +112,7 @@ namespace MyCompanyName.MyProjectName
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
});
Configure<AbpBackgroundJobOptions>(options =>

3
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json

@ -1,7 +1,8 @@
{
"App": {
"SelfUrl": "https://localhost:44301",
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300"
"CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307,https://localhost:44300",
"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true"

17
test/AbpPerfTest/AbpPerfTest.WithAbp/AbpPerfTest.WithAbp.csproj

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="4.0.2" />
<PackageReference Include="Volo.Abp.Autofac" Version="4.0.2" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="4.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

55
test/AbpPerfTest/AbpPerfTest.WithAbp/AppModule.cs

@ -0,0 +1,55 @@
using AbpPerfTest.WithAbp.EntityFramework;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
namespace AbpPerfTest.WithAbp
{
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAutofacModule),
typeof(AbpEntityFrameworkCoreSqlServerModule)
)]
public class AppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<BookDbContext>(options =>
{
options.AddDefaultRepositories();
});
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
});
Configure<AbpUnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled;
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseConfiguredEndpoints();
}
}
}

87
test/AbpPerfTest/AbpPerfTest.WithAbp/Controllers/BookController.cs

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AbpPerfTest.WithAbp.Dtos;
using AbpPerfTest.WithAbp.Entities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories;
namespace AbpPerfTest.WithAbp.Controllers
{
[Route("api/books")]
public class BookController : Controller
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookController(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}
[HttpGet]
public async Task<List<BookDto>> GetListAsync()
{
var books = await _bookRepository.OrderBy(x => x.Id).Take(10).ToListAsync();
return books
.Select(b => new BookDto
{
Id = b.Id,
Name = b.Name,
Price = b.Price,
IsAvailable = b.IsAvailable
})
.ToList();
}
[HttpGet]
[Route("{id}")]
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return new BookDto
{
Id = book.Id,
Name = book.Name,
Price = book.Price,
IsAvailable = book.IsAvailable
};
}
[HttpPost]
public async Task<Guid> CreateAsync([FromBody] CreateUpdateBookDto input)
{
var book = new Book
{
Name = input.Name,
Price = input.Price,
IsAvailable = input.IsAvailable
};
await _bookRepository.InsertAsync(book);
return book.Id;
}
[HttpPut]
[Route("{id}")]
public async Task UpdateAsync(Guid id, [FromBody] CreateUpdateBookDto input)
{
var book = await _bookRepository.GetAsync(id);
book.Name = input.Name;
book.Price = input.Price;
book.IsAvailable = input.IsAvailable;
}
[HttpDelete]
[Route("{id}")]
public async Task DeleteAsync(Guid id)
{
await _bookRepository.DeleteAsync(id);
}
}
}

14
test/AbpPerfTest/AbpPerfTest.WithAbp/Controllers/HomeController.cs

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace AbpPerfTest.WithAbp.Controllers
{
[Route("")]
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return Redirect("/api/books/");
}
}
}

15
test/AbpPerfTest/AbpPerfTest.WithAbp/Dtos/BookDto.cs

@ -0,0 +1,15 @@
using System;
namespace AbpPerfTest.WithAbp.Dtos
{
public class BookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
}
}

11
test/AbpPerfTest/AbpPerfTest.WithAbp/Dtos/CreateUpdateBookDto.cs

@ -0,0 +1,11 @@
namespace AbpPerfTest.WithAbp.Dtos
{
public class CreateUpdateBookDto
{
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
}
}

19
test/AbpPerfTest/AbpPerfTest.WithAbp/Entities/Book.cs

@ -0,0 +1,19 @@
using System;
using Volo.Abp.Domain.Entities;
namespace AbpPerfTest.WithAbp.Entities
{
public class Book : BasicAggregateRoot<Guid>
{
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
public Book()
{
Id = Guid.NewGuid();
}
}
}

28
test/AbpPerfTest/AbpPerfTest.WithAbp/EntityFramework/BookDbContext.cs

@ -0,0 +1,28 @@
using AbpPerfTest.WithAbp.Entities;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace AbpPerfTest.WithAbp.EntityFramework
{
public class BookDbContext : AbpDbContext<BookDbContext>
{
public DbSet<Book> Books { get; set; }
public BookDbContext(DbContextOptions<BookDbContext> builderOptions)
: base(builderOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>(b =>
{
b.ToTable("Books");
b.Property(x => x.Name).HasMaxLength(128);
});
}
}
}

29
test/AbpPerfTest/AbpPerfTest.WithAbp/EntityFramework/BookDbContextFactory.cs

@ -0,0 +1,29 @@
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace AbpPerfTest.WithAbp.EntityFramework
{
public class BookDbContextFactory : IDesignTimeDbContextFactory<BookDbContext>
{
public BookDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration();
var builder = new DbContextOptionsBuilder<BookDbContext>()
.UseSqlServer(configuration.GetConnectionString("Default"));
return new BookDbContext(builder.Options);
}
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false);
return builder.Build();
}
}
}

48
test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/20201222135738_Added_Books.Designer.cs

@ -0,0 +1,48 @@
// <auto-generated />
using System;
using AbpPerfTest.WithAbp.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
namespace AbpPerfTest.WithAbp.Migrations
{
[DbContext(typeof(BookDbContext))]
[Migration("20201222135738_Added_Books")]
partial class Added_Books
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.Entity("AbpPerfTest.WithAbp.Entities.Book", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<float>("Price")
.HasColumnType("real");
b.HasKey("Id");
b.ToTable("Books");
});
#pragma warning restore 612, 618
}
}
}

31
test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/20201222135738_Added_Books.cs

@ -0,0 +1,31 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace AbpPerfTest.WithAbp.Migrations
{
public partial class Added_Books : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
Price = table.Column<float>(type: "real", nullable: false),
IsAvailable = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Books");
}
}
}

46
test/AbpPerfTest/AbpPerfTest.WithAbp/Migrations/BookDbContextModelSnapshot.cs

@ -0,0 +1,46 @@
// <auto-generated />
using System;
using AbpPerfTest.WithAbp.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
namespace AbpPerfTest.WithAbp.Migrations
{
[DbContext(typeof(BookDbContext))]
partial class BookDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.Entity("AbpPerfTest.WithAbp.Entities.Book", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<float>("Price")
.HasColumnType("real");
b.HasKey("Id");
b.ToTable("Books");
});
#pragma warning restore 612, 618
}
}
}

21
test/AbpPerfTest/AbpPerfTest.WithAbp/Program.cs

@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace AbpPerfTest.WithAbp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac();
}
}

28
test/AbpPerfTest/AbpPerfTest.WithAbp/Properties/launchSettings.json

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50732",
"sslPort": 44312
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"AbpPerfTest.WithAbp": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

18
test/AbpPerfTest/AbpPerfTest.WithAbp/Startup.cs

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace AbpPerfTest.WithAbp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<AppModule>();
}
public void Configure(IApplicationBuilder app)
{
app.InitializeApplication();
}
}
}

9
test/AbpPerfTest/AbpPerfTest.WithAbp/appsettings.Development.json

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

13
test/AbpPerfTest/AbpPerfTest.WithAbp/appsettings.json

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=AbpPerfTest_WithAbp;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

15
test/AbpPerfTest/AbpPerfTest.WithoutAbp/AbpPerfTest.WithoutAbp.csproj

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.1" />
</ItemGroup>
</Project>

93
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Controllers/BookController.cs

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AbpPerfTest.WithoutAbp.Dtos;
using AbpPerfTest.WithoutAbp.Entities;
using AbpPerfTest.WithoutAbp.EntityFramework;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AbpPerfTest.WithoutAbp.Controllers
{
[Route("api/books")]
public class BookController : Controller
{
private readonly BookDbContext _bookDbContext;
public BookController(BookDbContext bookDbContext)
{
_bookDbContext = bookDbContext;
}
[HttpGet]
public async Task<List<BookDto>> GetListAsync()
{
var books = await _bookDbContext.Books.OrderBy(x => x.Id).Take(10).ToListAsync();
return books
.Select(b => new BookDto
{
Id = b.Id,
Name = b.Name,
Price = b.Price,
IsAvailable = b.IsAvailable
})
.ToList();
}
[HttpGet]
[Route("{id}")]
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookDbContext.Books.SingleAsync(b => b.Id == id);
return new BookDto
{
Id = book.Id,
Name = book.Name,
Price = book.Price,
IsAvailable = book.IsAvailable
};
}
[HttpPost]
public async Task<Guid> CreateAsync([FromBody] CreateUpdateBookDto input)
{
var book = new Book
{
Name = input.Name,
Price = input.Price,
IsAvailable = input.IsAvailable
};
await _bookDbContext.Books.AddAsync(book);
await _bookDbContext.SaveChangesAsync();
return book.Id;
}
[HttpPut]
[Route("{id}")]
public async Task UpdateAsync(Guid id, [FromBody] CreateUpdateBookDto input)
{
var book = await _bookDbContext.Books.SingleAsync(b => b.Id == id);
book.Name = input.Name;
book.Price = input.Price;
book.IsAvailable = input.IsAvailable;
await _bookDbContext.SaveChangesAsync();
}
[HttpDelete]
[Route("{id}")]
public async Task DeleteAsync(Guid id)
{
var book = await _bookDbContext.Books.SingleAsync(b => b.Id == id);
_bookDbContext.Books.Remove(book);
await _bookDbContext.SaveChangesAsync();
}
}
}

14
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Controllers/HomeController.cs

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc;
namespace AbpPerfTest.WithoutAbp.Controllers
{
[Route("")]
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return Redirect("/api/books/");
}
}
}

15
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Dtos/BookDto.cs

@ -0,0 +1,15 @@
using System;
namespace AbpPerfTest.WithoutAbp.Dtos
{
public class BookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
}
}

11
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Dtos/CreateUpdateBookDto.cs

@ -0,0 +1,11 @@
namespace AbpPerfTest.WithoutAbp.Dtos
{
public class CreateUpdateBookDto
{
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
}
}

20
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Entities/Book.cs

@ -0,0 +1,20 @@
using System;
namespace AbpPerfTest.WithoutAbp.Entities
{
public class Book
{
public Guid Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
public bool IsAvailable { get; set; }
public Book()
{
Id = Guid.NewGuid();
}
}
}

27
test/AbpPerfTest/AbpPerfTest.WithoutAbp/EntityFramework/BookDbContext.cs

@ -0,0 +1,27 @@
using AbpPerfTest.WithoutAbp.Entities;
using Microsoft.EntityFrameworkCore;
namespace AbpPerfTest.WithoutAbp.EntityFramework
{
public class BookDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
public BookDbContext(DbContextOptions<BookDbContext> builderOptions)
: base(builderOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>(b =>
{
b.ToTable("Books");
b.Property(x => x.Name).HasMaxLength(128);
});
}
}
}

29
test/AbpPerfTest/AbpPerfTest.WithoutAbp/EntityFramework/BookDbContextFactory.cs

@ -0,0 +1,29 @@
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace AbpPerfTest.WithoutAbp.EntityFramework
{
public class BookDbContextFactory : IDesignTimeDbContextFactory<BookDbContext>
{
public BookDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration();
var builder = new DbContextOptionsBuilder<BookDbContext>()
.UseSqlServer(configuration.GetConnectionString("Default"));
return new BookDbContext(builder.Options);
}
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false);
return builder.Build();
}
}
}

47
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/20201222132615_Added_Books.Designer.cs

@ -0,0 +1,47 @@
// <auto-generated />
using System;
using AbpPerfTest.WithoutAbp.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AbpPerfTest.WithoutAbp.Migrations
{
[DbContext(typeof(BookDbContext))]
[Migration("20201222132615_Added_Books")]
partial class Added_Books
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.Entity("AbpPerfTest.WithoutAbp.Entities.Book", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<float>("Price")
.HasColumnType("real");
b.HasKey("Id");
b.ToTable("Books");
});
#pragma warning restore 612, 618
}
}
}

31
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/20201222132615_Added_Books.cs

@ -0,0 +1,31 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace AbpPerfTest.WithoutAbp.Migrations
{
public partial class Added_Books : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Books",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: true),
Price = table.Column<float>(type: "real", nullable: false),
IsAvailable = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Books", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Books");
}
}
}

45
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Migrations/BookDbContextModelSnapshot.cs

@ -0,0 +1,45 @@
// <auto-generated />
using System;
using AbpPerfTest.WithoutAbp.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace AbpPerfTest.WithoutAbp.Migrations
{
[DbContext(typeof(BookDbContext))]
partial class BookDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.UseIdentityColumns()
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.Entity("AbpPerfTest.WithoutAbp.Entities.Book", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsAvailable")
.HasColumnType("bit");
b.Property<string>("Name")
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.Property<float>("Price")
.HasColumnType("real");
b.HasKey("Id");
b.ToTable("Books");
});
#pragma warning restore 612, 618
}
}
}

17
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Program.cs

@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace AbpPerfTest.WithoutAbp
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}

28
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Properties/launchSettings.json

@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:16268",
"sslPort": 44323
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"AbpPerfTest.WithoutAbp": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5003;http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

45
test/AbpPerfTest/AbpPerfTest.WithoutAbp/Startup.cs

@ -0,0 +1,45 @@
using AbpPerfTest.WithoutAbp.EntityFramework;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AbpPerfTest.WithoutAbp
{
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<BookDbContext>(options =>
{
options.UseSqlServer(_configuration.GetConnectionString("Default"));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

9
test/AbpPerfTest/AbpPerfTest.WithoutAbp/appsettings.Development.json

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

13
test/AbpPerfTest/AbpPerfTest.WithoutAbp/appsettings.json

@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=AbpPerfTest_WithoutAbp;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

22
test/AbpPerfTest/AbpPerfTest.sln

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AbpPerfTest.WithoutAbp", "AbpPerfTest.WithoutAbp\AbpPerfTest.WithoutAbp.csproj", "{E3406CA0-9B0C-45FC-A3C0-8179A9C4A3E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AbpPerfTest.WithAbp", "AbpPerfTest.WithAbp\AbpPerfTest.WithAbp.csproj", "{13021286-B5D8-4A3E-8F36-5256D32638A7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E3406CA0-9B0C-45FC-A3C0-8179A9C4A3E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3406CA0-9B0C-45FC-A3C0-8179A9C4A3E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3406CA0-9B0C-45FC-A3C0-8179A9C4A3E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3406CA0-9B0C-45FC-A3C0-8179A9C4A3E5}.Release|Any CPU.Build.0 = Release|Any CPU
{13021286-B5D8-4A3E-8F36-5256D32638A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13021286-B5D8-4A3E-8F36-5256D32638A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13021286-B5D8-4A3E-8F36-5256D32638A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13021286-B5D8-4A3E-8F36-5256D32638A7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

205
test/AbpPerfTest/_jmeter/SimpleTestPlan.jmx

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create a book" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Get book id" enabled="true">
<stringProp name="JSONPostProcessor.referenceNames">BookId</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$</stringProp>
<stringProp name="JSONPostProcessor.match_numbers"></stringProp>
<stringProp name="Scope.variable"></stringProp>
</JSONPostProcessor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get book by id" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update a book" enabled="false">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">PUT</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get books" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete a book" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5001</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">DELETE</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
</hashTree>
</jmeterTestPlan>

205
test/AbpPerfTest/_jmeter/SimpleTestPlanWithoutAbp.jmx

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create a book" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<JSONPostProcessor guiclass="JSONPostProcessorGui" testclass="JSONPostProcessor" testname="Get book id" enabled="true">
<stringProp name="JSONPostProcessor.referenceNames">BookId</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$</stringProp>
<stringProp name="JSONPostProcessor.match_numbers"></stringProp>
<stringProp name="Scope.variable"></stringProp>
</JSONPostProcessor>
<hashTree/>
</hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get book by id" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update a book" enabled="false">
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&#xd;
&quot;name&quot;: &quot;${__RandomString(5, abcdefghijklmnopqrstuvwxyz)}&quot;,&#xd;
&quot;price&quot;: ${__Random(1,999)},&#xd;
&quot;isAvailable&quot;: true&#xd;
}&#xd;
</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">PUT</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get books" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Delete a book" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">5003</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/books/${BookId}</stringProp>
<stringProp name="HTTPSampler.method">DELETE</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
</hashTree>
</hashTree>
</jmeterTestPlan>

BIN
test/AbpPerfTest/_results/get-insert-with-abp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
test/AbpPerfTest/_results/get-insert-without-abp.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Loading…
Cancel
Save