Browse Source

Merge branch 'dev' into liangshiwei/upgrade_nuget_packages

pull/10077/head
liangshiwei 4 years ago
parent
commit
fc1d421a23
  1. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en-GB.json
  2. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json
  3. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json
  4. 41
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  5. 22
      docs/en/Background-Jobs-Hangfire.md
  6. 46
      docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md
  7. 5
      docs/en/Tutorials/Part-1.md
  8. 5
      docs/en/Tutorials/Part-10.md
  9. 7
      docs/en/Tutorials/Part-2.md
  10. 5
      docs/en/Tutorials/Part-3.md
  11. 5
      docs/en/Tutorials/Part-4.md
  12. 7
      docs/en/Tutorials/Part-5.md
  13. 7
      docs/en/Tutorials/Part-6.md
  14. 5
      docs/en/Tutorials/Part-7.md
  15. 5
      docs/en/Tutorials/Part-8.md
  16. 5
      docs/en/Tutorials/Part-9.md
  17. 6
      docs/zh-Hans/Text-Templating.md
  18. 14
      framework/Volo.Abp.sln
  19. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs
  20. 7
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs
  21. 25
      framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs
  22. 31
      framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs
  23. 1
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs
  24. 6
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpRabbitMqBackgroundJobOptions.cs
  25. 22
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs
  26. 35
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs
  27. 42
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
  28. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
  29. 17
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AddModuleInfoOutput.cs
  30. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs
  31. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  32. 34
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs
  33. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs
  34. 110
      framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs
  35. 8
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  36. 10
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  37. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  38. 3
      framework/src/Volo.Abp.Http.Client.Web/FodyWeavers.xml
  39. 30
      framework/src/Volo.Abp.Http.Client.Web/FodyWeavers.xsd
  40. 25
      framework/src/Volo.Abp.Http.Client.Web/Volo.Abp.Http.Client.Web.csproj
  41. 42
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/AbpHttpClientWebModule.cs
  42. 13
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyControllerFeatureProvider.cs
  43. 4
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyHelper.cs
  44. 12
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs
  45. 3
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs
  46. 2
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ApiVersionInfo.cs
  47. 278
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs
  48. 6
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestContext.cs
  49. 13
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs
  50. 31
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestTypeValue.cs
  51. 17
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs
  52. 63
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
  53. 19
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs
  54. 267
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs
  55. 12
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs
  56. 34
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityClientOptions.cs
  57. 14
      framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs
  58. 4
      framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs
  59. 1
      framework/src/Volo.Abp.Swashbuckle/Volo.Abp.Swashbuckle.csproj
  60. 42
      framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleModule.cs
  61. 13
      framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyControllerFeatureProvider.cs
  62. 12
      framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyOptions.cs
  63. 11
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs
  64. 14
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs
  65. 27
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs
  66. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs
  67. 7
      framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs
  68. 8
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  69. 20
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithSoftDelete.cs
  70. 8
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  71. 22
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  72. 5
      framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs
  73. 24
      framework/test/Volo.Abp.IdentityModel.Tests/Volo.Abp.IdentityModel.Tests.csproj
  74. 51
      framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityClientOptions_Tests.cs
  75. 12
      framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityModelTestBase.cs
  76. 10
      framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityModelTestModule.cs
  77. 40
      framework/test/Volo.Abp.IdentityModel.Tests/appsettings.json
  78. 13
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs
  79. 15
      modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/AccountClientProxy.Generated.cs
  80. 1
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs
  81. 1
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs
  82. 22
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs
  83. 18
      modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingModelBuilderConfigurationOptions.cs
  84. 14
      modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbContextExtensions.cs
  85. 14
      modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/AuditLoggingMongoModelBuilderConfigurationOptions.cs
  86. 12
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs
  87. 18
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsModelBuilderConfigurationOptions.cs
  88. 16
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobsMongoDbContextExtensions.cs
  89. 14
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobsMongoModelBuilderConfigurationOptions.cs
  90. 2
      modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile
  91. 15
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDbContextModelCreatingExtensions.cs
  92. 18
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringModelBuilderConfigurationOptions.cs
  93. 18
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringMongoDbContextExtensions.cs
  94. 14
      modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringMongoModelBuilderConfigurationOptions.cs
  95. 6
      modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs
  96. 5
      modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj
  97. 3123
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.all.js
  98. 2
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.all.min.js
  99. 1319
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.css
  100. 3121
      modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.js

3
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en-GB.json

@ -9,6 +9,7 @@
"OfficialBlog": "Official blog",
"CommercialHomePage": "Commercial home page",
"CommercialSupportWebSite": "Commercial support web site",
"CommunityWebSite": "ABP community web site"
"CommunityWebSite": "ABP community web site",
"ManageAccount": "My Account | ABP.IO"
}
}

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

@ -9,6 +9,7 @@
"OfficialBlog": "Official blog",
"CommercialHomePage": "Commercial home page",
"CommercialSupportWebSite": "Commercial support web site",
"CommunityWebSite": "ABP community web site"
"CommunityWebSite": "ABP community web site",
"ManageAccount": "My Account | ABP.IO"
}
}

3
abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/tr.json

@ -9,6 +9,7 @@
"OfficialBlog": "Resmi blog",
"CommercialHomePage": "Kurumsal ana sayfa",
"CommercialSupportWebSite": "Kurumsal destek web sitesi",
"CommunityWebSite": "ABP topluluk web sitesi"
"CommunityWebSite": "ABP topluluk web sitesi",
"ManageAccount": "Hesabım | ABP.IO"
}
}

41
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -310,6 +310,45 @@
"DeleteInvoice" : "Delete Invoice",
"SuccessfullyDeleted": "Successfully deleted!",
"PaymentStateSetTo" : "Payment state set to {0}",
"ChangeState": "Change State"
"ChangeState": "Change State",
"Permission:TrialLicense" : "Trial License",
"Permission:ManageTrialLicense": "Manage Trial License",
"Menu:TrialLicenses": "Trial Licenses",
"TrialLicenses": "Trial Licenses",
"UserNameFilter": "Username",
"TrialLicenseStatusFilter": "Status",
"TrialLicenseStartDateFilter": "Start date",
"TrialLicenseEndDateFilter": "End date",
"FirsName": "First name",
"LastName": "Last name",
"Status": "Status",
"StartDate": "Start date",
"EndDate": "End date",
"CompanyName": "Company name",
"PurchasedDate": "Purchased date",
"OrganizationDetail": "Organization Detail",
"SendActivationMail": "Send Activation Mail",
"ActivationMailSentSuccessfully": "Activation mail sent successfully!",
"TrialLicenseStatus": "Trial license status",
"TrialLicenseDetail": "Trial License Detail",
"AcceptsMarketingCommunications": "Marketing Communications",
"PurposeOfUsage": "Purpose of usage",
"CountryName": "Country name",
"CompanySize": "Company size",
"DetailTrialLicense": "Details",
"Requested": "Requested",
"Activated": "Activated",
"PurchasedToNormalLicense": "Purchased",
"Expired": "Expired",
"TrialLicenseDeletionWarningMessage": "Trial license and if any organization and qa organization will be deleted!",
"IsTrial": "Is trial",
"Volo.AbpIo.Commercial:030000": "You already used your trial period.",
"Volo.AbpIo.Commercial:030001": "This organization name already exists.",
"Volo.AbpIo.Commercial:030002": "Once activated, trial license cannot be set to requested!",
"Volo.AbpIo.Commercial:030003": "There is no such status!",
"Volo.AbpIo.Commercial:030004": "Status could not be changed due to an unexpected error!",
"Volo.AbpIo.Commercial:030005": "Start date and end date cannot be given when the trial license is in the requested state!",
"Volo.AbpIo.Commercial:030006": "End date must always be greater than start date!",
"Volo.AbpIo.Commercial:030007": "This trial license has already been activated once!"
}
}

22
docs/en/Background-Jobs-Hangfire.md

@ -88,17 +88,21 @@ To make it secure by default, only local requests are allowed, however you can c
You can integrate the Hangfire dashboard to [ABP authorization system](Authorization.md) using the **AbpHangfireAuthorizationFilter**
class. This class is defined in the `Volo.Abp.Hangfire` package. The following example, checks if the current user is logged in to the application:
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }
});
```csharp
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }
});
```
If you want to require an additional permission, you can pass it into the constructor as below:
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter("MyHangFireDashboardPermissionName") }
});
```csharp
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter("MyHangFireDashboardPermissionName") }
});
```
**Important**: `UseHangfireDashboard` should be called after the authentication middleware in your `Startup` class (probably at the last line). Otherwise,
**Important**: `UseHangfireDashboard` should be called after the authentication and authorization middlewares in your `Startup` class (probably at the last line). Otherwise,
authorization will always fail!

46
docs/en/Community-Articles/2020-08-07-Passwordless-Authentication/POST.md

@ -194,16 +194,11 @@ We implemented token generation infrastructure, now it's time validate the token
```csharp
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using Volo.Abp.Users;
using Volo.Abp.Identity.AspNetCore;
namespace PasswordlessAuthentication.Web.Controllers
{
@ -211,9 +206,12 @@ namespace PasswordlessAuthentication.Web.Controllers
{
protected IdentityUserManager UserManager { get; }
public PasswordlessController(IdentityUserManager userManager)
protected AbpSignInManager SignInManager { get; }
public PasswordlessController(IdentityUserManager userManager, AbpSignInManager signInManager)
{
UserManager = userManager;
SignInManager = signInManager;
}
public virtual async Task<IActionResult> Login(string token, string userId)
@ -228,45 +226,15 @@ namespace PasswordlessAuthentication.Web.Controllers
await UserManager.UpdateSecurityStampAsync(user);
var roles = await UserManager.GetRolesAsync(user);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(CreateClaims(user, roles), IdentityConstants.ApplicationScheme)
);
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, principal);
await SignInManager.SignInAsync(user, isPersistent: false);
return Redirect("/");
}
private static IEnumerable<Claim> CreateClaims(IUser user, IEnumerable<string> roles)
{
var claims = new List<Claim>
{
new Claim("sub", user.Id.ToString()),
new Claim(AbpClaimTypes.UserId, user.Id.ToString()),
new Claim(AbpClaimTypes.Email, user.Email),
new Claim(AbpClaimTypes.UserName, user.UserName),
new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString().ToLower()),
};
if (!string.IsNullOrWhiteSpace(user.PhoneNumber))
{
claims.Add(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber));
}
foreach (var role in roles)
{
claims.Add(new Claim(AbpClaimTypes.Role, role));
}
return claims;
}
}
}
```
We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we create claims of the user then call `HttpContext.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL.
We created an endpoint for `/Passwordless/Login` that gets the token and the user Id. In this action, we find the user via repository and validate the token via `UserManager.VerifyUserTokenAsync()` method. If it's valid, we call `SignInManager.SignInAsync` to be able to create an encrypted cookie and add it to the current response. Finally we redirect the page to the root URL.
That's all! We created a passwordless login with 7 steps.

5
docs/en/Tutorials/Part-1.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial

5
docs/en/Tutorials/Part-10.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## Introduction
We have created `Book` and `Author` functionalities for the book store application. However, currently there is no relation between these entities.

7
docs/en/Tutorials/Part-2.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
@ -85,6 +90,8 @@ acme.bookStore.books.book.create({
});
````
> If you downloaded the source code of the tutorial and following the steps from the sample, you should also pass the `authorId` parameter to the create method for **creating a new book**.
You should see a message in the console something like that:
````text

5
docs/en/Tutorials/Part-3.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial

5
docs/en/Tutorials/Part-4.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial

7
docs/en/Tutorials/Part-5.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### Video Tutorial
@ -627,4 +632,4 @@ private async Task ConfigureMainMenuAsync(MenuConfigurationContext context)
## The Next Part
See the [next part](Part-6.md) of this tutorial.
See the [next part](Part-6.md) of this tutorial.

7
docs/en/Tutorials/Part-6.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## Introduction
In the previous parts, we've used the ABP infrastructure to easily build some services;
@ -270,4 +275,4 @@ This part covered the domain layer of the authors functionality of the book stor
## The Next Part
See the [next part](Part-7.md) of this tutorial.
See the [next part](Part-7.md) of this tutorial.

5
docs/en/Tutorials/Part-7.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## Introduction
This part explains how to configure the database integration for the `Author` entity introduced in the previous part.

5
docs/en/Tutorials/Part-8.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## Introduction
This part explains to create an application layer for the `Author` entity created before.

5
docs/en/Tutorials/Part-9.md

@ -34,6 +34,11 @@ This tutorial has multiple versions based on your **UI** and **Database** prefer
* [Blazor UI with EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI with MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> If you encounter the "filename too long" or "unzip error" on Windows, it's probably related to the Windows maximum file path limitation. Windows has a maximum file path limitation of 250 characters. To solve this, [enable the long path option in Windows 10](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> If you face long path errors related to Git, try the following command to enable long paths in Windows. See https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
## Introduction
This part explains how to create a CRUD page for the `Author` entity introduced in the previous parts.

6
docs/zh-Hans/Text-Templating.md

@ -187,9 +187,9 @@ var result = await _templateRenderer.RenderAsync(
示例中我们并没有创建模型类,但是创建了一个匿名对象模型.
### 大驼峰 与 小驼峰
### PascalCase 与 snake_case
PascalCase 属性名(如 `UserName`) 在模板中用做小驼峰(如 `userName`).
PascalCase 属性名(如 `UserName`) 在模板中使用蛇形命名(如 `user_name`).
## 本地化
@ -454,4 +454,4 @@ public class MyTemplateContentProvider
* 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo).
* [本地化系统](Localization.md).
* [虚拟文件系统](Virtual-File-System.md).
* [虚拟文件系统](Virtual-File-System.md).

14
framework/Volo.Abp.sln

@ -383,10 +383,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.TextTemplating.Scr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.MongoDB.Tests.SecondContext", "test\Volo.Abp.MongoDB.Tests.SecondContext\Volo.Abp.MongoDB.Tests.SecondContext.csproj", "{90B1866A-EF99-40B9-970E-B898E5AA523F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.IdentityModel.Tests", "test\Volo.Abp.IdentityModel.Tests\Volo.Abp.IdentityModel.Tests.csproj", "{40C6740E-BFCA-4D37-8344-3D84E2044BB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Threading.Tests", "test\Volo.Abp.Threading.Tests\Volo.Abp.Threading.Tests.csproj", "{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Auditing.Contracts", "src\Volo.Abp.Auditing.Contracts\Volo.Abp.Auditing.Contracts.csproj", "{508B6355-AD28-4E60-8549-266D21DBF2CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.Web", "src\Volo.Abp.Http.Client.Web\Volo.Abp.Http.Client.Web.csproj", "{F7407459-8AFA-45E4-83E9-9BB01412CC08}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1145,6 +1149,10 @@ Global
{90B1866A-EF99-40B9-970E-B898E5AA523F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90B1866A-EF99-40B9-970E-B898E5AA523F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90B1866A-EF99-40B9-970E-B898E5AA523F}.Release|Any CPU.Build.0 = Release|Any CPU
{40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40C6740E-BFCA-4D37-8344-3D84E2044BB2}.Release|Any CPU.Build.0 = Release|Any CPU
{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -1153,6 +1161,10 @@ Global
{508B6355-AD28-4E60-8549-266D21DBF2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{508B6355-AD28-4E60-8549-266D21DBF2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{508B6355-AD28-4E60-8549-266D21DBF2CF}.Release|Any CPU.Build.0 = Release|Any CPU
{F7407459-8AFA-45E4-83E9-9BB01412CC08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7407459-8AFA-45E4-83E9-9BB01412CC08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7407459-8AFA-45E4-83E9-9BB01412CC08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7407459-8AFA-45E4-83E9-9BB01412CC08}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1346,8 +1358,10 @@ Global
{C996F458-98FB-483D-9306-4701290E2FC1} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{75D8DADB-3FA9-4C1D-B23A-DBFD08133B7C} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{90B1866A-EF99-40B9-970E-B898E5AA523F} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{40C6740E-BFCA-4D37-8344-3D84E2044BB2} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{508B6355-AD28-4E60-8549-266D21DBF2CF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{F7407459-8AFA-45E4-83E9-9BB01412CC08} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/AbpJsonOptionsSetup.cs

@ -30,10 +30,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Json
options.JsonSerializerOptions.Converters.Add(new ObjectToInferredTypesConverter());
options.JsonSerializerOptions.Converters.Add(new AbpHasExtraPropertiesJsonConverterFactory());
// Remove after this PR.
// https://github.com/dotnet/runtime/pull/57525
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
}
}

7
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Response/AbpNoContentActionFilter.cs

@ -18,8 +18,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Response
await next();
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK
&& context.Result == null)
if (!context.HttpContext.Response.HasStarted &&
context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK &&
context.Result == null)
{
var returnType = context.ActionDescriptor.GetReturnType();
if (returnType == typeof(Task) || returnType == typeof(void))
@ -29,4 +30,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Response
}
}
}
}
}

25
framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs

@ -0,0 +1,25 @@
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
namespace Volo.Abp.Authorization
{
public class PermissionsRequirement : IAuthorizationRequirement
{
public string[] PermissionNames { get; }
public bool RequiresAll { get; }
public PermissionsRequirement([NotNull]string[] permissionNames, bool requiresAll)
{
Check.NotNull(permissionNames, nameof(permissionNames));
PermissionNames = permissionNames;
RequiresAll = requiresAll;
}
public override string ToString()
{
return $"PermissionsRequirement: {string.Join(", ", PermissionNames)}";
}
}
}

31
framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs

@ -0,0 +1,31 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Authorization.Permissions;
namespace Volo.Abp.Authorization
{
public class PermissionsRequirementHandler : AuthorizationHandler<PermissionsRequirement>
{
private readonly IPermissionChecker _permissionChecker;
public PermissionsRequirementHandler(IPermissionChecker permissionChecker)
{
_permissionChecker = permissionChecker;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionsRequirement requirement)
{
var multiplePermissionGrantResult = await _permissionChecker.IsGrantedAsync(context.User, requirement.PermissionNames);
if (requirement.RequiresAll ?
multiplePermissionGrantResult.AllGranted :
multiplePermissionGrantResult.Result.Any(x => x.Value == PermissionGrantResult.Granted))
{
context.Succeed(requirement);
}
}
}
}

1
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs

@ -31,6 +31,7 @@ namespace Volo.Abp.Authorization
context.Services.AddAuthorizationCore();
context.Services.AddSingleton<IAuthorizationHandler, PermissionRequirementHandler>();
context.Services.AddSingleton<IAuthorizationHandler, PermissionsRequirementHandler>();
context.Services.TryAddTransient<DefaultAuthorizationPolicyProvider>();

6
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpRabbitMqBackgroundJobOptions.cs

@ -15,10 +15,16 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
/// </summary>
public string DefaultQueueNamePrefix { get; set; }
/// <summary>
/// Default value: "AbpBackgroundJobsDelayed."
/// </summary>
public string DefaultDelayedQueueNamePrefix { get; set;}
public AbpRabbitMqBackgroundJobOptions()
{
JobQueues = new Dictionary<Type, JobQueueConfiguration>();
DefaultQueueNamePrefix = "AbpBackgroundJobs.";
DefaultDelayedQueueNamePrefix = "AbpBackgroundJobsDelayed.";
}
}
}

22
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -64,7 +65,8 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
return AbpRabbitMqBackgroundJobOptions.JobQueues.GetOrDefault(typeof(TArgs)) ??
new JobQueueConfiguration(
typeof(TArgs),
AbpRabbitMqBackgroundJobOptions.DefaultQueueNamePrefix + JobConfiguration.JobName
AbpRabbitMqBackgroundJobOptions.DefaultQueueNamePrefix + JobConfiguration.JobName,
AbpRabbitMqBackgroundJobOptions.DefaultDelayedQueueNamePrefix + JobConfiguration.JobName
);
}
@ -133,6 +135,9 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
var result = QueueConfiguration.Declare(ChannelAccessor.Channel);
Logger.LogDebug($"RabbitMQ Queue '{QueueConfiguration.QueueName}' has {result.MessageCount} messages and {result.ConsumerCount} consumers.");
// Declare delayed queue
QueueConfiguration.DeclareDelayed(ChannelAccessor.Channel);
if (AbpBackgroundJobOptions.IsJobExecutionEnabled)
{
Consumer = new AsyncEventingBasicConsumer(ChannelAccessor.Channel);
@ -154,12 +159,21 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
//TODO: How to handle priority & delay?
//TODO: How to handle priority
var routingKey = QueueConfiguration.QueueName;
var basicProperties = CreateBasicPropertiesToPublish();
if (delay.HasValue)
{
routingKey = QueueConfiguration.DelayedQueueName;
basicProperties.Expiration = delay.Value.TotalMilliseconds.ToString();
}
ChannelAccessor.Channel.BasicPublish(
exchange: "",
routingKey: QueueConfiguration.QueueName,
basicProperties: CreateBasicPropertiesToPublish(),
routingKey: routingKey,
basicProperties: basicProperties,
body: Serializer.Serialize(args)
);

35
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueConfiguration.cs

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using RabbitMQ.Client;
using Volo.Abp.RabbitMQ;
namespace Volo.Abp.BackgroundJobs.RabbitMQ
@ -9,21 +11,42 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
public string ConnectionName { get; set; }
public string DelayedQueueName { get; set; }
public JobQueueConfiguration(
Type jobArgsType,
string queueName,
Type jobArgsType,
string queueName,
string delayedQueueName,
string connectionName = null,
bool durable = true,
bool exclusive = false,
bool autoDelete = false)
: base(
queueName,
durable,
exclusive,
queueName,
durable,
exclusive,
autoDelete)
{
JobArgsType = jobArgsType;
ConnectionName = connectionName;
DelayedQueueName = delayedQueueName;
}
public virtual QueueDeclareOk DeclareDelayed(IModel channel)
{
var delayedArguments = new Dictionary<string, object>(Arguments)
{
["x-dead-letter-routing-key"] = QueueName,
["x-dead-letter-exchange"] = string.Empty
};
return channel.QueueDeclare(
queue: DelayedQueueName,
durable: Durable,
exclusive: Exclusive,
autoDelete: AutoDelete,
arguments: delayedArguments
);
}
}
}
}

42
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs

@ -14,11 +14,25 @@ namespace Volo.Abp.Cli.Commands
{
public class AddModuleCommand : IConsoleCommand, ITransientDependency
{
private AddModuleInfoOutput _lastAddedModuleInfo;
public ILogger<AddModuleCommand> Logger { get; set; }
protected SolutionModuleAdder SolutionModuleAdder { get; }
public SolutionAbpVersionFinder SolutionAbpVersionFinder { get; }
public AddModuleInfoOutput LastAddedModuleInfo
{
get
{
if (_lastAddedModuleInfo == null)
{
throw new Exception("You need to add a module first to get the last added module info!");
}
return _lastAddedModuleInfo;
}
}
public AddModuleCommand(SolutionModuleAdder solutionModuleAdder, SolutionAbpVersionFinder solutionAbpVersionFinder)
{
SolutionModuleAdder = solutionModuleAdder;
@ -51,16 +65,24 @@ namespace Volo.Abp.Cli.Commands
version = SolutionAbpVersionFinder.Find(solutionFile);
}
await SolutionModuleAdder.AddAsync(
solutionFile,
commandLineArgs.Target,
version,
skipDbMigrations,
withSourceCode,
addSourceCodeToSolutionFile,
newTemplate,
newProTemplate
);
var moduleInfo = await SolutionModuleAdder.AddAsync(
solutionFile,
commandLineArgs.Target,
version,
skipDbMigrations,
withSourceCode,
addSourceCodeToSolutionFile,
newTemplate,
newProTemplate
);
_lastAddedModuleInfo = new AddModuleInfoOutput
{
DisplayName = moduleInfo.DisplayName,
Name = moduleInfo.Name,
DocumentationLinks = moduleInfo.DocumentationLinks,
InstallationCompleteMessage = moduleInfo.InstallationCompleteMessage
};
}
public string GetUsageInfo()

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs

@ -19,6 +19,14 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
context.BuildArgs.SolutionName.ProjectName
).Run();
new SolutionRenamer(
context.Files,
"myCompanyName.myProjectName",
"MicroserviceName",
context.BuildArgs.SolutionName.CompanyName,
context.BuildArgs.SolutionName.ProjectName
).Run();
new SolutionRenamer(
context.Files,
null,

17
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/AddModuleInfoOutput.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Cli.ProjectModification
{
public class AddModuleInfoOutput
{
public string Name { get; set; }
public string DisplayName { get; set; }
public string DocumentationLinks { get; set; }
public string InstallationCompleteMessage { get; set; }
}
}

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs

@ -18,6 +18,8 @@ namespace Volo.Abp.Cli.ProjectModification
public List<NpmPackageInfo> NpmPackages { get; set; }
public string InstallationCompleteMessage { get; set; }
public string GetFirstDocumentationLinkOrNull()
{
if (string.IsNullOrWhiteSpace(DocumentationLinks))
@ -26,8 +28,8 @@ namespace Volo.Abp.Cli.ProjectModification
}
var docs = DocumentationLinks.Split(" ", StringSplitOptions.RemoveEmptyEntries);
return docs.Any() ?
docs.First() :
return docs.Any() ?
docs.First() :
null;
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -78,8 +78,7 @@ namespace Volo.Abp.Cli.ProjectModification
Logger = NullLogger<SolutionModuleAdder>.Instance;
}
public virtual async Task AddAsync(
[NotNull] string solutionFile,
public virtual async Task<ModuleWithMastersInfo> AddAsync([NotNull] string solutionFile,
[NotNull] string moduleName,
string version,
bool skipDbMigrations = false,
@ -94,8 +93,7 @@ namespace Volo.Abp.Cli.ProjectModification
var module = await GetModuleInfoAsync(moduleName, newTemplate, newProTemplate);
module = RemoveIncompatiblePackages(module, version);
Logger.LogInformation(
$"Installing module '{module.Name}' to the solution '{Path.GetFileNameWithoutExtension(solutionFile)}'");
Logger.LogInformation($"Installing module '{module.Name}' to the solution '{Path.GetFileNameWithoutExtension(solutionFile)}'");
var projectFiles = ProjectFinder.GetProjectFiles(solutionFile);
@ -137,6 +135,8 @@ namespace Volo.Abp.Cli.ProjectModification
{
CmdHelper.OpenWebPage(documentationLink);
}
return module;
}
private ModuleWithMastersInfo RemoveIncompatiblePackages(ModuleWithMastersInfo module, string version)
@ -146,7 +146,7 @@ namespace Volo.Abp.Cli.ProjectModification
return module;
}
private bool IsPackageInCompatible(string minVersion, string maxVersion, string version)
private static bool IsPackageInCompatible(string minVersion, string maxVersion, string version)
{
try
{

34
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ServiceProxying/CSharp/CSharpServiceProxyGenerator.cs

@ -206,7 +206,7 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
{
var methodBuilder = new StringBuilder();
var returnTypeName = GetRealTypeName(usingNamespaceList, action.ReturnValue.Type);
var returnTypeName = GetRealTypeName(action.ReturnValue.Type, usingNamespaceList);
if(!action.Name.EndsWith("Async"))
{
@ -225,7 +225,7 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
foreach (var parameter in action.Parameters.GroupBy(x => x.Name).Select( x=> x.First()))
{
methodBuilder.Replace("<args>", $"{GetRealTypeName(usingNamespaceList, parameter.Type)} {parameter.Name}, <args>");
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>");
}
methodBuilder.Replace("<args>", string.Empty);
@ -249,7 +249,7 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
foreach (var parameter in action.ParametersOnMethod)
{
methodBuilder.Replace("<args>", $"{GetRealTypeName(usingNamespaceList, parameter.Type)} {parameter.Name}, <args>");
methodBuilder.Replace("<args>", $"{GetRealTypeName(parameter.Type, usingNamespaceList)} {parameter.Name}, <args>");
}
methodBuilder.Replace("<args>", string.Empty);
@ -257,21 +257,27 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
methodBuilder.AppendLine(" {");
var argsTemplate = "new ClientProxyRequestTypeValue" +
$"{Environment.NewLine} {{<args>" +
$"{Environment.NewLine} }}";
var args = action.ParametersOnMethod.Any() ? argsTemplate : string.Empty;
if (returnTypeName == "void")
{
methodBuilder.AppendLine($" await RequestAsync(nameof({action.Name}), <args>);");
methodBuilder.AppendLine($" await RequestAsync(nameof({action.Name}), {args});");
}
else
{
methodBuilder.AppendLine($" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), <args>);");
methodBuilder.AppendLine($" return await RequestAsync<{returnTypeName}>(nameof({action.Name}), {args});");
}
foreach (var parameter in action.ParametersOnMethod)
{
methodBuilder.Replace("<args>", $"{parameter.Name}, <args>");
methodBuilder.Replace("<args>", $"{Environment.NewLine} {{ typeof({GetRealTypeName(parameter.Type)}), {parameter.Name} }},<args>");
}
methodBuilder.Replace("<args>", string.Empty);
methodBuilder.Replace(",<args>", string.Empty);
methodBuilder.Replace(", )", ")");
methodBuilder.AppendLine(" }");
}
@ -297,7 +303,7 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
return typeFullName.Substring(0, typeFullName.LastIndexOf('.'));
}
private string GetRealTypeName(List<string> usingNamespaceList, string typeName)
private string GetRealTypeName(string typeName, List<string> usingNamespaceList = null)
{
var filter = new []{"<", ",", ">"};
var stringBuilder = new StringBuilder();
@ -305,7 +311,11 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
if (typeNames.All(x => !filter.Any(x.Contains)))
{
AddUsingNamespace(usingNamespaceList, typeName);
if (usingNamespaceList != null)
{
AddUsingNamespace(usingNamespaceList, typeName);
}
return NormalizeTypeName(typeNames.Last());
}
@ -315,7 +325,11 @@ namespace Volo.Abp.Cli.ServiceProxying.CSharp
{
if (filter.Any(x => item.Contains(x)))
{
AddUsingNamespace(usingNamespaceList, $"{fullName}.{item}".TrimStart('.'));
if (usingNamespaceList != null)
{
AddUsingNamespace(usingNamespaceList, $"{fullName}.{item}".TrimStart('.'));
}
fullName = string.Empty;
if (item.Contains('<') || item.Contains(','))

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ProjectNameValidator.cs

@ -53,7 +53,7 @@ namespace Volo.Abp.Cli.Utils
{
foreach (var illegalKeyword in IllegalKeywords)
{
if (projectName.Contains(illegalKeyword))
if (projectName.Split(".").Contains(illegalKeyword))
{
throw new CliUsageException("Project name cannot contain the word \"" + illegalKeyword + "\". Specify a different name.");
}

110
framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryExtensions.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
@ -54,16 +55,7 @@ namespace Volo.Abp.Domain.Repositories
)
where TEntity : class, IEntity, ISoftDelete
{
if (!(ProxyHelper.UnProxy(repository) is IUnitOfWorkManagerAccessor unitOfWorkManagerAccessor))
{
throw new AbpException($"The given repository (of type {repository.GetType().AssemblyQualifiedName}) should implement the {typeof(IUnitOfWorkManagerAccessor).AssemblyQualifiedName} interface in order to invoke the {nameof(HardDeleteAsync)} method!");
}
var uowManager = unitOfWorkManagerAccessor.UnitOfWorkManager;
if (uowManager == null)
{
throw new AbpException($"{nameof(unitOfWorkManagerAccessor.UnitOfWorkManager)} property of the given {nameof(repository)} object is null!");
}
var uowManager = repository.GetUnitOfWorkManager();
if (uowManager.Current == null)
{
@ -79,29 +71,27 @@ namespace Volo.Abp.Domain.Repositories
}
}
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IRepository<TEntity> repository,
Expression<Func<TEntity, bool>> predicate,
bool autoSave,
CancellationToken cancellationToken, IUnitOfWork currentUow
public static async Task HardDeleteAsync<TEntity>(
this IBasicRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave = false,
CancellationToken cancellationToken = default
)
where TEntity : class, IEntity, ISoftDelete
{
var hardDeleteEntities = (HashSet<IEntity>) currentUow.Items.GetOrAdd(
UnitOfWorkItemNames.HardDeletedEntities,
() => new HashSet<IEntity>()
);
var uowManager = repository.GetUnitOfWorkManager();
List<TEntity> entities;
using (currentUow.ServiceProvider.GetRequiredService<IDataFilter<ISoftDelete>>().Disable())
if (uowManager.Current == null)
{
entities = await repository.AsyncExecuter.ToListAsync((await repository.GetQueryableAsync()).Where(predicate), cancellationToken);
using (var uow = uowManager.Begin())
{
await HardDeleteWithUnitOfWorkAsync(repository, entities, autoSave, cancellationToken, uowManager.Current);
await uow.CompleteAsync(cancellationToken);
}
}
foreach (var entity in entities)
else
{
hardDeleteEntities.Add(entity);
await repository.DeleteAsync(entity, autoSave, cancellationToken);
await HardDeleteWithUnitOfWorkAsync(repository, entities, autoSave, cancellationToken, uowManager.Current);
}
}
@ -113,16 +103,7 @@ namespace Volo.Abp.Domain.Repositories
)
where TEntity : class, IEntity, ISoftDelete
{
if (!(ProxyHelper.UnProxy(repository) is IUnitOfWorkManagerAccessor unitOfWorkManagerAccessor))
{
throw new AbpException($"The given repository (of type {repository.GetType().AssemblyQualifiedName}) should implement the {typeof(IUnitOfWorkManagerAccessor).AssemblyQualifiedName} interface in order to invoke the {nameof(HardDeleteAsync)} method!");
}
var uowManager = unitOfWorkManagerAccessor.UnitOfWorkManager;
if (uowManager == null)
{
throw new AbpException($"{nameof(unitOfWorkManagerAccessor.UnitOfWorkManager)} property of the given {nameof(repository)} object is null!");
}
var uowManager = repository.GetUnitOfWorkManager();
if (uowManager.Current == null)
{
@ -138,11 +119,66 @@ namespace Volo.Abp.Domain.Repositories
}
}
private static IUnitOfWorkManager GetUnitOfWorkManager<TEntity>(
this IBasicRepository<TEntity> repository,
[CallerMemberName] string callingMethodName = nameof(GetUnitOfWorkManager)
)
where TEntity : class, IEntity
{
if (ProxyHelper.UnProxy(repository) is not IUnitOfWorkManagerAccessor unitOfWorkManagerAccessor)
{
throw new AbpException($"The given repository (of type {repository.GetType().AssemblyQualifiedName}) should implement the " +
$"{typeof(IUnitOfWorkManagerAccessor).AssemblyQualifiedName} interface in order to invoke the {callingMethodName} method!");
}
if (unitOfWorkManagerAccessor.UnitOfWorkManager == null)
{
throw new AbpException($"{nameof(unitOfWorkManagerAccessor.UnitOfWorkManager)} property of the given {nameof(repository)} object is null!");
}
return unitOfWorkManagerAccessor.UnitOfWorkManager;
}
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IRepository<TEntity> repository,
Expression<Func<TEntity, bool>> predicate,
bool autoSave,
CancellationToken cancellationToken,
IUnitOfWork currentUow
)
where TEntity : class, IEntity, ISoftDelete
{
using (currentUow.ServiceProvider.GetRequiredService<IDataFilter<ISoftDelete>>().Disable())
{
var entities = await repository.AsyncExecuter.ToListAsync((await repository.GetQueryableAsync()).Where(predicate), cancellationToken);
await HardDeleteWithUnitOfWorkAsync(repository, entities, autoSave, cancellationToken, currentUow);
}
}
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IBasicRepository<TEntity> repository,
IEnumerable<TEntity> entities,
bool autoSave,
CancellationToken cancellationToken,
IUnitOfWork currentUow
)
where TEntity : class, IEntity, ISoftDelete
{
var hardDeleteEntities = (HashSet<IEntity>)currentUow.Items.GetOrAdd(
UnitOfWorkItemNames.HardDeletedEntities,
() => new HashSet<IEntity>()
);
hardDeleteEntities.UnionWith(entities);
await repository.DeleteManyAsync(entities, autoSave, cancellationToken);
}
private static async Task HardDeleteWithUnitOfWorkAsync<TEntity>(
IBasicRepository<TEntity> repository,
TEntity entity,
bool autoSave,
CancellationToken cancellationToken, IUnitOfWork currentUow
CancellationToken cancellationToken,
IUnitOfWork currentUow
)
where TEntity : class, IEntity, ISoftDelete
{

8
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -155,6 +155,10 @@ namespace Volo.Abp.EntityFrameworkCore
{
try
{
ApplyAbpConcepts();
var eventReport = CreateEventReport();
var auditLog = AuditingManager?.Current?.Log;
List<EntityChangeInfo> entityChangeList = null;
if (auditLog != null)
@ -162,10 +166,6 @@ namespace Volo.Abp.EntityFrameworkCore
entityChangeList = EntityHistoryHelper.CreateChangeList(ChangeTracker.Entries().ToList());
}
ApplyAbpConcepts();
var eventReport = CreateEventReport();
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
PublishEntityEvents(eventReport);

10
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -166,7 +166,7 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
foreach (var property in properties)
{
var propertyEntry = entityEntry.Property(property.Name);
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted))
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry))
{
propertyChanges.Add(new EntityPropertyChangeInfo
{
@ -188,11 +188,11 @@ namespace Volo.Abp.EntityFrameworkCore.EntityHistory
protected virtual bool IsDeleted(EntityEntry entityEntry)
{
if (entityEntry.State == EntityState.Deleted)
{
return true;
}
return entityEntry.State == EntityState.Deleted || IsSoftDeleted(entityEntry);
}
protected virtual bool IsSoftDeleted(EntityEntry entityEntry)
{
var entity = entityEntry.Entity;
return entity is ISoftDelete && entity.As<ISoftDelete>().IsDeleted;
}

4
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs

@ -51,10 +51,6 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter());
// Remove after this PR.
// https://github.com/dotnet/runtime/pull/57525
deserializeOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
var dictionary = JsonSerializer.Deserialize<ExtraPropertyDictionary>(extraPropertiesAsJson, deserializeOptions) ??
new ExtraPropertyDictionary();

3
framework/src/Volo.Abp.Http.Client.Web/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.Http.Client.Web/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

25
framework/src/Volo.Abp.Http.Client.Web/Volo.Abp.Http.Client.Web.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>Volo.Abp.Http.Client.Web</AssemblyName>
<PackageId>Volo.Abp.Http.Client.Web</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<IsPackable>true</IsPackable>
<OutputType>Library</OutputType>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
</ItemGroup>
</Project>

42
framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/AbpHttpClientWebModule.cs

@ -0,0 +1,42 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.Http.Client.Web.Conventions;
using Volo.Abp.Modularity;
namespace Volo.Abp.Http.Client.Web
{
[DependsOn(
typeof(AbpAspNetCoreMvcModule),
typeof(AbpHttpClientModule)
)]
public class AbpHttpClientWebModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Replace(ServiceDescriptor.Transient<IAbpServiceConvention, AbpHttpClientProxyServiceConvention>());
context.Services.AddTransient<AbpHttpClientProxyServiceConvention>();
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
partManager.FeatureProviders.Add(new AbpHttpClientProxyControllerFeatureProvider());
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var partManager = context.ServiceProvider.GetRequiredService<ApplicationPartManager>();
foreach (var moduleAssembly in context
.ServiceProvider
.GetRequiredService<IModuleContainer>()
.Modules
.Select(m => m.Type.Assembly)
.Where(a => a.GetTypes().Any(AbpHttpClientProxyHelper.IsClientProxyService))
.Distinct())
{
partManager.ApplicationParts.AddIfNotContains(moduleAssembly);
}
}
}
}

13
framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyControllerFeatureProvider.cs

@ -0,0 +1,13 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Volo.Abp.Http.Client.Web.Conventions
{
public class AbpHttpClientProxyControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
return AbpHttpClientProxyHelper.IsClientProxyService(typeInfo);
}
}
}

4
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyHelper.cs → framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyHelper.cs

@ -3,9 +3,9 @@ using System.Linq;
using Volo.Abp.Application.Services;
using Volo.Abp.Http.Client.ClientProxying;
namespace Volo.Abp.Swashbuckle.Conventions
namespace Volo.Abp.Http.Client.Web.Conventions
{
public static class AbpSwaggerClientProxyHelper
public static class AbpHttpClientProxyHelper
{
public static bool IsClientProxyService(Type type)
{

12
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyServiceConvention.cs → framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/Conventions/AbpHttpClientProxyServiceConvention.cs

@ -14,15 +14,15 @@ using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Reflection;
namespace Volo.Abp.Swashbuckle.Conventions
namespace Volo.Abp.Http.Client.Web.Conventions
{
[DisableConventionalRegistration]
public class AbpSwaggerServiceConvention : AbpServiceConvention
public class AbpHttpClientProxyServiceConvention : AbpServiceConvention
{
protected readonly IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder;
protected readonly List<ActionModel> ActionWithAttributeRoute;
public AbpSwaggerServiceConvention(
public AbpHttpClientProxyServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options,
IConventionalRouteBuilder conventionalRouteBuilder,
IClientProxyApiDescriptionFinder clientProxyApiDescriptionFinder)
@ -34,12 +34,12 @@ namespace Volo.Abp.Swashbuckle.Conventions
protected override IList<ControllerModel> GetControllers(ApplicationModel application)
{
return application.Controllers.Where(c => !AbpSwaggerClientProxyHelper.IsClientProxyService(c.ControllerType)).ToList();
return application.Controllers.Where(c => !AbpHttpClientProxyHelper.IsClientProxyService(c.ControllerType)).ToList();
}
protected virtual IList<ControllerModel> GetClientProxyControllers(ApplicationModel application)
{
return application.Controllers.Where(c => AbpSwaggerClientProxyHelper.IsClientProxyService(c.ControllerType)).ToList();
return application.Controllers.Where(c => AbpHttpClientProxyHelper.IsClientProxyService(c.ControllerType)).ToList();
}
protected override void ApplyForControllers(ApplicationModel application)
@ -217,7 +217,7 @@ namespace Volo.Abp.Swashbuckle.Conventions
var key =
$"{appServiceType.FullName}." +
$"{action.ActionMethod.Name}." +
$"{string.Join("-", action.Parameters.Select(x => x.ParameterType.FullName))}";
$"{string.Join("-", action.Parameters.Select(x => TypeHelper.GetFullNameHandlingNullableAndGenerics(x.ParameterType)))}";
var actionApiDescriptionModel = ClientProxyApiDescriptionFinder.FindAction(key);
if (actionApiDescriptionModel == null)

3
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs

@ -5,6 +5,7 @@ using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Validation;
using Volo.Abp.ExceptionHandling;
using Volo.Abp.Http.Client.DynamicProxying;
namespace Volo.Abp.Http.Client
{
@ -22,6 +23,8 @@ namespace Volo.Abp.Http.Client
{
var configuration = context.Services.GetConfiguration();
Configure<AbpRemoteServiceOptions>(configuration);
context.Services.AddTransient(typeof(DynamicHttpProxyInterceptorClientProxy<>));
}
}
}

2
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/ApiVersionInfo.cs → framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ApiVersionInfo.cs

@ -1,6 +1,6 @@
using System;
namespace Volo.Abp.Http.Client.Proxying
namespace Volo.Abp.Http.Client.ClientProxying
{
public class ApiVersionInfo //TODO: Rename to not conflict with api versioning apis
{

278
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs

@ -1,9 +1,23 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Tracing;
namespace Volo.Abp.Http.Client.ClientProxying
{
@ -11,42 +25,270 @@ namespace Volo.Abp.Http.Client.ClientProxying
{
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected IHttpProxyExecuter HttpProxyExecuter => LazyServiceProvider.LazyGetRequiredService<IHttpProxyExecuter>();
protected IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder => LazyServiceProvider.LazyGetRequiredService<IClientProxyApiDescriptionFinder>();
protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService<ICancellationTokenProvider>();
protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService<ICorrelationIdProvider>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IOptions<AbpCorrelationIdOptions> AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpCorrelationIdOptions>>();
protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService<IProxyHttpClientFactory>();
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceConfigurationProvider>();
protected IOptions<AbpHttpClientOptions> ClientOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpHttpClientOptions>>();
protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService<IJsonSerializer>();
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService<IRemoteServiceHttpClientAuthenticator>();
protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyRequestPayloadBuilder>();
protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService<ClientProxyUrlBuilder>();
protected virtual async Task RequestAsync(string methodName, params object[] arguments)
protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue arguments = null)
{
await HttpProxyExecuter.MakeRequestAsync(BuildHttpProxyExecuterContext(methodName, arguments));
await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments));
}
protected virtual async Task<T> RequestAsync<T>(string methodName, params object[] arguments)
protected virtual async Task<T> RequestAsync<T>(string methodName, ClientProxyRequestTypeValue arguments = null)
{
return await HttpProxyExecuter.MakeRequestAndGetResultAsync<T>(BuildHttpProxyExecuterContext(methodName, arguments));
return await RequestAsync<T>(BuildHttpProxyClientProxyContext(methodName, arguments));
}
protected virtual HttpProxyExecuterContext BuildHttpProxyExecuterContext(string methodName, params object[] arguments)
protected virtual ClientProxyRequestContext BuildHttpProxyClientProxyContext(string methodName, ClientProxyRequestTypeValue arguments = null)
{
var actionKey = GetActionKey(methodName, arguments);
var action = ClientProxyApiDescriptionFinder.FindAction(actionKey);
return new HttpProxyExecuterContext(action, BuildArguments(action, arguments), typeof(TService));
if (arguments == null)
{
arguments = new ClientProxyRequestTypeValue();
}
var methodUniqueName = $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Values.Select(x => x.Key.FullName))}";
var action = ClientProxyApiDescriptionFinder.FindAction(methodUniqueName);
if (action == null)
{
throw new AbpException($"The API description of the {typeof(TService).FullName}.{methodName} method was not found!");
}
return new ClientProxyRequestContext(
action,
action.Parameters
.GroupBy(x => x.NameOnMethod)
.Select((x, i) => new KeyValuePair<string, object>(x.Key, arguments.Values[i].Value))
.ToDictionary(x => x.Key, x => x.Value),
typeof(TService));
}
protected virtual async Task<T> RequestAsync<T>(ClientProxyRequestContext requestContext)
{
var responseContent = await RequestAsync(requestContext);
if (typeof(T) == typeof(IRemoteStreamContent) ||
typeof(T) == typeof(RemoteStreamContent))
{
/* returning a class that holds a reference to response
* content just to be sure that GC does not dispose of
* it before we finish doing our work with the stream */
return (T)(object)new RemoteStreamContent(
await responseContent.ReadAsStreamAsync(),
responseContent.Headers?.ContentDisposition?.FileNameStar ??
RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(),
responseContent.Headers?.ContentType?.ToString(),
responseContent.Headers?.ContentLength);
}
var stringContent = await responseContent.ReadAsStringAsync();
if (typeof(T) == typeof(string))
{
return (T)(object)stringContent;
}
if (stringContent.IsNullOrWhiteSpace())
{
return default;
}
return JsonSerializer.Deserialize<T>(stringContent);
}
protected virtual async Task<HttpContent> RequestAsync(ClientProxyRequestContext requestContext)
{
var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {requestContext.ServiceType.FullName}.");
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName);
var apiVersion = await GetApiVersionInfoAsync(requestContext);
var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + await GetUrlWithParametersAsync(requestContext, apiVersion);
var requestMessage = new HttpRequestMessage(requestContext.Action.GetHttpMethod(), url)
{
Content = ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion)
};
AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion);
if (requestContext.Action.AllowAnonymous != true)
{
await ClientAuthenticator.Authenticate(
new RemoteServiceHttpClientAuthenticateContext(
client,
requestMessage,
remoteServiceConfig,
clientConfig.RemoteServiceName
)
);
}
var response = await client.SendAsync(
requestMessage,
HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/,
GetCancellationToken(requestContext.Arguments)
);
if (!response.IsSuccessStatusCode)
{
await ThrowExceptionForResponseAsync(response);
}
return response.Content;
}
protected virtual async Task<ApiVersionInfo> GetApiVersionInfoAsync(ClientProxyRequestContext requestContext)
{
var apiVersion = await FindBestApiVersionAsync(requestContext);
//TODO: Make names configurable?
var versionParam = requestContext.Action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ??
requestContext.Action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query);
return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion);
}
protected virtual Task<string> GetUrlWithParametersAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion)
{
return Task.FromResult(ClientProxyUrlBuilder.GenerateUrlWithParameters(requestContext.Action, requestContext.Arguments, apiVersion));
}
protected virtual Task<HttpContent> GetHttpContentAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion)
{
return Task.FromResult(ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion));
}
protected virtual async Task<string> FindBestApiVersionAsync(ClientProxyRequestContext requestContext)
{
var configuredVersion = await GetConfiguredApiVersionAsync(requestContext);
if (requestContext.Action.SupportedVersions.IsNullOrEmpty())
{
return configuredVersion ?? "1.0";
}
if (requestContext.Action.SupportedVersions.Contains(configuredVersion))
{
return configuredVersion;
}
return requestContext.Action.SupportedVersions.Last(); //TODO: Ensure to get the latest version!
}
protected virtual Dictionary<string, object> BuildArguments(ActionApiDescriptionModel action, object[] arguments)
protected virtual async Task<string> GetConfiguredApiVersionAsync(ClientProxyRequestContext requestContext)
{
var parameters = action.Parameters.GroupBy(x => x.NameOnMethod).Select(x => x.Key).ToList();
var dict = new Dictionary<string, object>();
var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType)
?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {requestContext.ServiceType.FullName}.");
return (await RemoteServiceConfigurationProvider
.GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version;
}
for (var i = 0; i < parameters.Count; i++)
protected virtual async Task ThrowExceptionForResponseAsync(HttpResponseMessage response)
{
if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat))
{
dict[parameters[i]] = arguments[i];
var errorResponse = JsonSerializer.Deserialize<RemoteServiceErrorResponse>(
await response.Content.ReadAsStringAsync()
);
throw new AbpRemoteCallException(errorResponse.Error)
{
HttpStatusCode = (int) response.StatusCode
};
}
return dict;
throw new AbpRemoteCallException(
new RemoteServiceErrorInfo
{
Message = response.ReasonPhrase,
Code = response.StatusCode.ToString()
}
)
{
HttpStatusCode = (int) response.StatusCode
};
}
private static string GetActionKey(string methodName, params object[] arguments)
protected virtual void AddHeaders(
IReadOnlyDictionary<string, object> argumentsDictionary,
ActionApiDescriptionModel action,
HttpRequestMessage requestMessage,
ApiVersionInfo apiVersion)
{
return $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Select(x => x.GetType().FullName))}";
//API Version
if (!apiVersion.Version.IsNullOrEmpty())
{
//TODO: What about other media types?
requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}");
requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}");
requestMessage.Headers.Add("api-version", apiVersion.Version);
}
//Header parameters
var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray();
foreach (var headerParameter in headers)
{
var value = HttpActionParameterHelper.FindParameterValue(argumentsDictionary, headerParameter);
if (value != null)
{
requestMessage.Headers.Add(headerParameter.Name, value.ToString());
}
}
//CorrelationId
requestMessage.Headers.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, CorrelationIdProvider.Get());
//TenantId
if (CurrentTenant.Id.HasValue)
{
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
//Culture
//TODO: Is that the way we want? Couldn't send the culture (not ui culture)
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
if (!currentCulture.IsNullOrEmpty())
{
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture));
}
//X-Requested-With
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest");
}
protected virtual StringSegment RemoveQuotes(StringSegment input)
{
if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"')
{
input = input.Subsegment(1, input.Length - 2);
}
return input;
}
protected virtual CancellationToken GetCancellationToken(IReadOnlyDictionary<string, object> arguments)
{
var cancellationTokenArg = arguments.LastOrDefault();
if (cancellationTokenArg.Value is CancellationToken cancellationToken)
{
if (cancellationToken != default)
{
return cancellationToken;
}
}
return CancellationTokenProvider.Token;
}
}
}

6
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuterContext.cs → framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestContext.cs

@ -3,9 +3,9 @@ using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Http.Modeling;
namespace Volo.Abp.Http.Client.Proxying
namespace Volo.Abp.Http.Client.ClientProxying
{
public class HttpProxyExecuterContext
public class ClientProxyRequestContext
{
[NotNull]
public ActionApiDescriptionModel Action { get; }
@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Client.Proxying
[NotNull]
public Type ServiceType { get; }
public HttpProxyExecuterContext(
public ClientProxyRequestContext(
[NotNull] ActionApiDescriptionModel action,
[NotNull] IReadOnlyDictionary<string, object> arguments,
[NotNull] Type serviceType)

13
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/RequestPayloadBuilder.cs → framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs

@ -6,17 +6,18 @@ using System.Net.Http.Headers;
using System.Text;
using JetBrains.Annotations;
using Volo.Abp.Content;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
namespace Volo.Abp.Http.Client.Proxying
namespace Volo.Abp.Http.Client.ClientProxying
{
public static class RequestPayloadBuilder
public class ClientProxyRequestPayloadBuilder : ITransientDependency
{
[CanBeNull]
public static HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion)
public virtual HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion)
{
var body = GenerateBody(action, methodArguments, jsonSerializer);
if (body != null)
@ -29,7 +30,7 @@ namespace Volo.Abp.Http.Client.Proxying
return body;
}
private static HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer)
protected virtual HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer)
{
var parameters = action
.Parameters
@ -57,7 +58,7 @@ namespace Volo.Abp.Http.Client.Proxying
return new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json);
}
private static HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments)
protected virtual HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments)
{
var parameters = action
.Parameters

31
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestTypeValue.cs

@ -0,0 +1,31 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Volo.Abp.Http.Client.ClientProxying
{
public class ClientProxyRequestTypeValue : IEnumerable<KeyValuePair<Type, object>>
{
public List<KeyValuePair<Type, object>> Values { get; private set; }
public ClientProxyRequestTypeValue()
{
Values = new List<KeyValuePair<Type, object>>();
}
public void Add(Type type, object value)
{
Values.Add(new KeyValuePair<Type, object>(type, value));
}
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
return Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

17
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/UrlBuilder.cs → framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs

@ -5,16 +5,17 @@ using System.Globalization;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Localization;
namespace Volo.Abp.Http.Client.Proxying
namespace Volo.Abp.Http.Client.ClientProxying
{
internal static class UrlBuilder
public class ClientProxyUrlBuilder : ITransientDependency
{
public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
public string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{
// The ASP.NET Core route value provider and query string value provider:
// Treat values as invariant culture.
@ -30,7 +31,7 @@ namespace Volo.Abp.Http.Client.Proxying
}
}
private static void ReplacePathVariables(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
protected virtual void ReplacePathVariables(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{
var pathParameters = actionParameters
.Where(p => p.BindingSourceId == ParameterBindingSources.Path)
@ -72,7 +73,7 @@ namespace Volo.Abp.Http.Client.Proxying
}
}
private static void AddQueryStringParameters(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
protected virtual void AddQueryStringParameters(StringBuilder urlBuilder, IList<ParameterApiDescriptionModel> actionParameters, IReadOnlyDictionary<string, object> methodArguments, ApiVersionInfo apiVersion)
{
var queryStringParameters = actionParameters
.Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query))
@ -100,7 +101,7 @@ namespace Volo.Abp.Http.Client.Proxying
}
}
private static bool AddQueryStringParameter(
protected virtual bool AddQueryStringParameter(
StringBuilder urlBuilder,
bool isFirstParam,
string name,
@ -133,7 +134,7 @@ namespace Volo.Abp.Http.Client.Proxying
return true;
}
private static string ConvertValueToString([CanBeNull] object value)
protected virtual string ConvertValueToString([CanBeNull] object value)
{
if (value is DateTime dateTimeValue)
{

63
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs

@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Http.Client.ClientProxying;
using Volo.Abp.Http.Client.Proxying;
using Volo.Abp.Http.Modeling;
@ -15,32 +16,32 @@ namespace Volo.Abp.Http.Client.DynamicProxying
{
public class DynamicHttpProxyInterceptor<TService> : AbpInterceptor, ITransientDependency
{
// ReSharper disable once StaticMemberInGenericType
protected static MethodInfo MakeRequestAndGetResultAsyncMethod { get; }
protected static MethodInfo CallRequestAsyncMethod { get; }
static DynamicHttpProxyInterceptor()
{
CallRequestAsyncMethod = typeof(DynamicHttpProxyInterceptor<TService>)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.First(m => m.Name == nameof(CallRequestAsync) && m.IsGenericMethodDefinition);
}
public ILogger<DynamicHttpProxyInterceptor<TService>> Logger { get; set; }
protected DynamicHttpProxyInterceptorClientProxy<TService> InterceptorClientProxy { get; }
protected AbpHttpClientOptions ClientOptions { get; }
protected IHttpProxyExecuter HttpProxyExecuter { get; }
protected IProxyHttpClientFactory HttpClientFactory { get; }
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; }
protected IApiDescriptionFinder ApiDescriptionFinder { get; }
public ILogger<DynamicHttpProxyInterceptor<TService>> Logger { get; set; }
static DynamicHttpProxyInterceptor()
{
MakeRequestAndGetResultAsyncMethod = typeof(HttpProxyExecuter)
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.First(m => m.Name == nameof(IHttpProxyExecuter.MakeRequestAndGetResultAsync) && m.IsGenericMethodDefinition);
}
public DynamicHttpProxyInterceptor(
IHttpProxyExecuter httpProxyExecuter,
DynamicHttpProxyInterceptorClientProxy<TService> interceptorClientProxy,
IOptions<AbpHttpClientOptions> clientOptions,
IProxyHttpClientFactory httpClientFactory,
IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider,
IApiDescriptionFinder apiDescriptionFinder)
{
HttpProxyExecuter = httpProxyExecuter;
InterceptorClientProxy = interceptorClientProxy;
HttpClientFactory = httpClientFactory;
RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider;
ApiDescriptionFinder = apiDescriptionFinder;
@ -49,34 +50,32 @@ namespace Volo.Abp.Http.Client.DynamicProxying
Logger = NullLogger<DynamicHttpProxyInterceptor<TService>>.Instance;
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
var context = new HttpProxyExecuterContext(
var context = new ClientProxyRequestContext(
await GetActionApiDescriptionModel(invocation),
invocation.ArgumentsDictionary,
typeof(TService));
if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
await HttpProxyExecuter.MakeRequestAsync(context);
await InterceptorClientProxy.CallRequestAsync(context);
}
else
{
var result = (Task)MakeRequestAndGetResultAsyncMethod
.MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0])
.Invoke(HttpProxyExecuter, new object[] { context });
var returnType = invocation.Method.ReturnType.GenericTypeArguments[0];
var result = (Task)CallRequestAsyncMethod
.MakeGenericMethod(returnType)
.Invoke(this, new object[] { context });
invocation.ReturnValue = await GetResultAsync(
result,
invocation.Method.ReturnType.GetGenericArguments()[0]
);
invocation.ReturnValue = await GetResultAsync(result, returnType);
}
}
private async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel(IAbpMethodInvocation invocation)
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel(IAbpMethodInvocation invocation)
{
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ??
throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName);
@ -88,13 +87,19 @@ namespace Volo.Abp.Http.Client.DynamicProxying
);
}
private async Task<object> GetResultAsync(Task task, Type resultType)
protected virtual async Task<T> CallRequestAsync<T>(ClientProxyRequestContext context)
{
return await InterceptorClientProxy.CallRequestAsync<T>(context);
}
protected virtual async Task<object> GetResultAsync(Task task, Type resultType)
{
await task;
return typeof(Task<>)
var resultProperty = typeof(Task<>)
.MakeGenericType(resultType)
.GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public)
.GetValue(task);
.GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public);
Check.NotNull(resultProperty, nameof(resultProperty));
return resultProperty.GetValue(task);
}
}
}

19
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs

@ -0,0 +1,19 @@
using System.Net.Http;
using System.Threading.Tasks;
using Volo.Abp.Http.Client.ClientProxying;
namespace Volo.Abp.Http.Client.DynamicProxying
{
public class DynamicHttpProxyInterceptorClientProxy<TService> : ClientProxyBase<TService>
{
public virtual async Task<T> CallRequestAsync<T>(ClientProxyRequestContext requestContext)
{
return await base.RequestAsync<T>(requestContext);
}
public virtual async Task<HttpContent> CallRequestAsync(ClientProxyRequestContext requestContext)
{
return await base.RequestAsync(requestContext);
}
}
}

267
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs

@ -1,267 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Tracing;
namespace Volo.Abp.Http.Client.Proxying
{
public class HttpProxyExecuter : IHttpProxyExecuter, ITransientDependency
{
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected ICorrelationIdProvider CorrelationIdProvider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; }
protected IProxyHttpClientFactory HttpClientFactory { get; }
protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; }
protected AbpHttpClientOptions ClientOptions { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; }
public HttpProxyExecuter(
ICancellationTokenProvider cancellationTokenProvider,
ICorrelationIdProvider correlationIdProvider,
ICurrentTenant currentTenant,
IOptions<AbpCorrelationIdOptions> abpCorrelationIdOptions,
IProxyHttpClientFactory httpClientFactory,
IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider,
IOptions<AbpHttpClientOptions> clientOptions,
IRemoteServiceHttpClientAuthenticator clientAuthenticator,
IJsonSerializer jsonSerializer)
{
CancellationTokenProvider = cancellationTokenProvider;
CorrelationIdProvider = correlationIdProvider;
CurrentTenant = currentTenant;
AbpCorrelationIdOptions = abpCorrelationIdOptions.Value;
HttpClientFactory = httpClientFactory;
RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider;
ClientOptions = clientOptions.Value;
ClientAuthenticator = clientAuthenticator;
JsonSerializer = jsonSerializer;
}
public virtual async Task<T> MakeRequestAndGetResultAsync<T>(HttpProxyExecuterContext context)
{
var responseContent = await MakeRequestAsync(context);
if (typeof(T) == typeof(IRemoteStreamContent) ||
typeof(T) == typeof(RemoteStreamContent))
{
/* returning a class that holds a reference to response
* content just to be sure that GC does not dispose of
* it before we finish doing our work with the stream */
return (T) (object) new RemoteStreamContent(
await responseContent.ReadAsStreamAsync(),
responseContent.Headers?.ContentDisposition?.FileNameStar ?? RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(),
responseContent.Headers?.ContentType?.ToString(),
responseContent.Headers?.ContentLength);
}
var stringContent = await responseContent.ReadAsStringAsync();
if (typeof(T) == typeof(string))
{
return (T)(object)stringContent;
}
if (stringContent.IsNullOrWhiteSpace())
{
return default;
}
return JsonSerializer.Deserialize<T>(stringContent);
}
public virtual async Task<HttpContent> MakeRequestAsync(HttpProxyExecuterContext context)
{
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {context.ServiceType.FullName}.");
var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName);
var client = HttpClientFactory.Create(clientConfig.RemoteServiceName);
var apiVersion = await GetApiVersionInfoAsync(context);
var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + UrlBuilder.GenerateUrlWithParameters(context.Action, context.Arguments, apiVersion);
var requestMessage = new HttpRequestMessage(context.Action.GetHttpMethod(), url)
{
Content = RequestPayloadBuilder.BuildContent(context.Action, context.Arguments, JsonSerializer, apiVersion)
};
AddHeaders(context.Arguments, context.Action, requestMessage, apiVersion);
if (context.Action.AllowAnonymous != true)
{
await ClientAuthenticator.Authenticate(
new RemoteServiceHttpClientAuthenticateContext(
client,
requestMessage,
remoteServiceConfig,
clientConfig.RemoteServiceName
)
);
}
var response = await client.SendAsync(
requestMessage,
HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/,
GetCancellationToken(context.Arguments)
);
if (!response.IsSuccessStatusCode)
{
await ThrowExceptionForResponseAsync(response);
}
return response.Content;
}
private async Task<ApiVersionInfo> GetApiVersionInfoAsync(HttpProxyExecuterContext context)
{
var apiVersion = await FindBestApiVersionAsync(context);
//TODO: Make names configurable?
var versionParam = context.Action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ??
context.Action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query);
return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion);
}
private async Task<string> FindBestApiVersionAsync(HttpProxyExecuterContext context)
{
var configuredVersion = await GetConfiguredApiVersionAsync(context);
if (context.Action.SupportedVersions.IsNullOrEmpty())
{
return configuredVersion ?? "1.0";
}
if (context.Action.SupportedVersions.Contains(configuredVersion))
{
return configuredVersion;
}
return context.Action.SupportedVersions.Last(); //TODO: Ensure to get the latest version!
}
private async Task<string> GetConfiguredApiVersionAsync(HttpProxyExecuterContext context)
{
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType)
?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {context.ServiceType.FullName}.");
return (await RemoteServiceConfigurationProvider
.GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version;
}
private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response)
{
if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat))
{
var errorResponse = JsonSerializer.Deserialize<RemoteServiceErrorResponse>(
await response.Content.ReadAsStringAsync()
);
throw new AbpRemoteCallException(errorResponse.Error)
{
HttpStatusCode = (int) response.StatusCode
};
}
throw new AbpRemoteCallException(
new RemoteServiceErrorInfo
{
Message = response.ReasonPhrase,
Code = response.StatusCode.ToString()
}
)
{
HttpStatusCode = (int) response.StatusCode
};
}
protected virtual void AddHeaders(
IReadOnlyDictionary<string, object> argumentsDictionary,
ActionApiDescriptionModel action,
HttpRequestMessage requestMessage,
ApiVersionInfo apiVersion)
{
//API Version
if (!apiVersion.Version.IsNullOrEmpty())
{
//TODO: What about other media types?
requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}");
requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}");
requestMessage.Headers.Add("api-version", apiVersion.Version);
}
//Header parameters
var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray();
foreach (var headerParameter in headers)
{
var value = HttpActionParameterHelper.FindParameterValue(argumentsDictionary, headerParameter);
if (value != null)
{
requestMessage.Headers.Add(headerParameter.Name, value.ToString());
}
}
//CorrelationId
requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get());
//TenantId
if (CurrentTenant.Id.HasValue)
{
//TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
//Culture
//TODO: Is that the way we want? Couldn't send the culture (not ui culture)
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
if (!currentCulture.IsNullOrEmpty())
{
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture));
}
//X-Requested-With
requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest");
}
protected virtual StringSegment RemoveQuotes(StringSegment input)
{
if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"')
{
input = input.Subsegment(1, input.Length - 2);
}
return input;
}
protected virtual CancellationToken GetCancellationToken(IReadOnlyDictionary<string, object> arguments)
{
var cancellationTokenArg = arguments.LastOrDefault();
if (cancellationTokenArg.Value is CancellationToken cancellationToken)
{
if (cancellationToken != default)
{
return cancellationToken;
}
}
return CancellationTokenProvider.Token;
}
}
}

12
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs

@ -1,12 +0,0 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Volo.Abp.Http.Client.Proxying
{
public interface IHttpProxyExecuter
{
Task<HttpContent> MakeRequestAsync(HttpProxyExecuterContext context);
Task<T> MakeRequestAndGetResultAsync<T>(HttpProxyExecuterContext context);
}
}

34
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/AbpIdentityClientOptions.cs

@ -1,4 +1,9 @@
namespace Volo.Abp.IdentityModel
using System;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.IdentityModel
{
public class AbpIdentityClientOptions
{
@ -8,5 +13,30 @@
{
IdentityClients = new IdentityClientConfigurationDictionary();
}
public IdentityClientConfiguration GetClientConfiguration(ICurrentTenant currentTenant, string identityClientName = null)
{
if (identityClientName.IsNullOrWhiteSpace())
{
identityClientName = IdentityClientConfigurationDictionary.DefaultName;
}
if (currentTenant.Id.HasValue)
{
var tenantConfiguration = IdentityClients.FirstOrDefault(x => x.Key == $"{identityClientName}.{currentTenant.Id}");
if (tenantConfiguration.Key == null && !currentTenant.Name.IsNullOrWhiteSpace())
{
tenantConfiguration = IdentityClients.FirstOrDefault(x => x.Key == $"{identityClientName}.{currentTenant.Name}");
}
if (tenantConfiguration.Key != null)
{
return tenantConfiguration.Value;
}
}
return IdentityClients.GetOrDefault(identityClientName) ??
IdentityClients.Default;
}
}
}
}

14
framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs

@ -66,7 +66,7 @@ namespace Volo.Abp.IdentityModel
protected virtual async Task<string> GetAccessTokenOrNullAsync(string identityClientName)
{
var configuration = GetClientConfiguration(identityClientName);
var configuration = ClientOptions.GetClientConfiguration(CurrentTenant, identityClientName);
if (configuration == null)
{
Logger.LogWarning($"Could not find {nameof(IdentityClientConfiguration)} for {identityClientName}. Either define a configuration for {identityClientName} or set a default configuration.");
@ -114,17 +114,6 @@ namespace Volo.Abp.IdentityModel
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
private IdentityClientConfiguration GetClientConfiguration(string identityClientName = null)
{
if (identityClientName.IsNullOrEmpty())
{
return ClientOptions.IdentityClients.Default;
}
return ClientOptions.IdentityClients.GetOrDefault(identityClientName) ??
ClientOptions.IdentityClients.Default;
}
protected virtual async Task<string> GetTokenEndpoint(IdentityClientConfiguration configuration)
{
//TODO: Can use (configuration.Authority + /connect/token) directly?
@ -205,6 +194,7 @@ namespace Volo.Abp.IdentityModel
UserName = configuration.UserName,
Password = configuration.UserPassword
};
IdentityModelHttpRequestMessageOptions.ConfigureHttpRequestMessage?.Invoke(request);
AddParametersToRequestAsync(configuration, request);

4
framework/src/Volo.Abp.Json/Volo/Abp/Json/SystemTextJson/AbpSystemTextJsonSerializerOptionsSetup.cs

@ -29,10 +29,6 @@ namespace Volo.Abp.Json.SystemTextJson
// If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
options.JsonSerializerOptions.Encoder ??= JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
// Remove after this PR.
// https://github.com/dotnet/runtime/pull/57525
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
}
}

1
framework/src/Volo.Abp.Swashbuckle/Volo.Abp.Swashbuckle.csproj

@ -20,7 +20,6 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
<ProjectReference Include="..\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
</ItemGroup>

42
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleModule.cs

@ -1,21 +1,12 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.Http.Client;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle.Conventions;
using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Swashbuckle
{
[DependsOn(
typeof(AbpVirtualFileSystemModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpHttpClientModule))]
typeof(AbpAspNetCoreMvcModule))]
public class AbpSwashbuckleModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
@ -24,35 +15,6 @@ namespace Volo.Abp.Swashbuckle
{
options.FileSets.AddEmbedded<AbpSwashbuckleModule>();
});
var swaggerConventionOptions = context.Services.ExecutePreConfiguredActions<AbpSwaggerClientProxyOptions>();
if (swaggerConventionOptions.IsEnabled)
{
context.Services.Replace(ServiceDescriptor.Transient<IAbpServiceConvention, AbpSwaggerServiceConvention>());
context.Services.AddTransient<AbpSwaggerServiceConvention>();
var partManager = context.Services.GetSingletonInstance<ApplicationPartManager>();
partManager.FeatureProviders.Add(new AbpSwaggerClientProxyControllerFeatureProvider());
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var swaggerConventionOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpSwaggerClientProxyOptions>>().Value;
if (swaggerConventionOptions.IsEnabled)
{
var partManager = context.ServiceProvider.GetRequiredService<ApplicationPartManager>();
foreach (var moduleAssembly in context
.ServiceProvider
.GetRequiredService<IModuleContainer>()
.Modules
.Select(m => m.Type.Assembly)
.Where(a => a.GetTypes().Any(AbpSwaggerClientProxyHelper.IsClientProxyService))
.Distinct())
{
partManager.ApplicationParts.AddIfNotContains(moduleAssembly);
}
}
}
}
}

13
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyControllerFeatureProvider.cs

@ -1,13 +0,0 @@
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Volo.Abp.Swashbuckle.Conventions
{
public class AbpSwaggerClientProxyControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
return AbpSwaggerClientProxyHelper.IsClientProxyService(typeInfo);
}
}
}

12
framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/Conventions/AbpSwaggerClientProxyOptions.cs

@ -1,12 +0,0 @@
namespace Volo.Abp.Swashbuckle.Conventions
{
public class AbpSwaggerClientProxyOptions
{
public bool IsEnabled { get; set; }
public AbpSwaggerClientProxyOptions()
{
IsEnabled = true;
}
}
}

11
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs

@ -11,6 +11,7 @@ using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization.Resource;
using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Authorization;
using Volo.Abp.Autofac;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
@ -65,6 +66,16 @@ namespace Volo.Abp.AspNetCore.Mvc
{
policy.RequireClaim("MyCustomClaimType", "42");
});
options.AddPolicy("TestPermission1_And_TestPermission2", policy =>
{
policy.Requirements.Add(new PermissionsRequirement(new []{"TestPermission1", "TestPermission2"}, requiresAll: true));
});
options.AddPolicy("TestPermission1_Or_TestPermission2", policy =>
{
policy.Requirements.Add(new PermissionsRequirement(new []{"TestPermission1", "TestPermission2"}, requiresAll: false));
});
});
Configure<AbpAspNetCoreMvcOptions>(options =>

14
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs

@ -38,5 +38,19 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization
CurrentUser.Id.ShouldBe(FakeUserId);
return Content("OK");
}
[Authorize("TestPermission1_And_TestPermission2")]
public ActionResult Custom_And_PolicyTest()
{
CurrentUser.Id.ShouldBe(FakeUserId);
return Content("OK");
}
[Authorize("TestPermission1_Or_TestPermission2")]
public ActionResult Custom_Or_PolicyTest()
{
CurrentUser.Id.ShouldBe(FakeUserId);
return Content("OK");
}
}
}

27
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs

@ -1,5 +1,4 @@
using System;
using System.Net;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using Shouldly;
@ -72,5 +71,29 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization
var result = await GetResponseAsStringAsync("/AuthTest/PermissionTest");
result.ShouldBe("OK");
}
[Fact]
public async Task Custom_And_Policy_Should_Not_Work_When_Permissions_Not_Granted()
{
_fakeRequiredService.Claims.AddRange(new[]
{
new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString())
});
var response = await GetResponseAsync("/AuthTest/Custom_And_PolicyTest", HttpStatusCode.Forbidden, xmlHttpRequest: true);
response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
}
[Fact]
public async Task Custom_Or_Policy_Should_Work_When_Permissions_Are_Granted()
{
_fakeRequiredService.Claims.AddRange(new[]
{
new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString())
});
var result = await GetResponseAsStringAsync("/AuthTest/Custom_Or_PolicyTest");
result.ShouldBe("OK");
}
}
}

1
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs

@ -9,6 +9,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization
var testGroup = context.AddGroup("TestGroup");
testGroup.AddPermission("TestPermission1");
testGroup.AddPermission("TestPermission2");
}
}
}

7
framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs

@ -3,6 +3,7 @@ using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Shouldly;
using Volo.Abp.AspNetCore.TestBase;
@ -30,11 +31,15 @@ namespace Volo.Abp.AspNetCore
}
}
protected virtual async Task<HttpResponseMessage> GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
protected virtual async Task<HttpResponseMessage> GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, bool xmlHttpRequest = false)
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url))
{
requestMessage.Headers.Add("Accept-Language", CultureInfo.CurrentUICulture.Name);
if (xmlHttpRequest)
{
requestMessage.Headers.Add(HeaderNames.XRequestedWith, "XMLHttpRequest");
}
var response = await Client.SendAsync(requestMessage);
response.StatusCode.ShouldBe(expectedStatusCode);
return response;

8
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs

@ -43,6 +43,12 @@ namespace Volo.Abp.Auditing
"AppEntityWithSelector",
type => type == typeof(AppEntityWithSelector))
);
options.EntityHistorySelectors.Add(
new NamedTypeSelector(
"AppEntityWithSoftDelete",
type => type == typeof(AppEntityWithSoftDelete))
);
});
context.Services.AddType<Auditing_Tests.MyAuditedObject1>();
@ -62,4 +68,4 @@ namespace Volo.Abp.Auditing
return connection;
}
}
}
}

20
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithSoftDelete.cs

@ -0,0 +1,20 @@
using System;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Auditing.App.Entities
{
public class AppEntityWithSoftDelete : AggregateRoot<Guid>, IHasDeletionTime
{
public AppEntityWithSoftDelete(Guid id, string name)
: base(id)
{
Name = name;
}
public string Name { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletionTime { get; set; }
}
}

8
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs

@ -17,15 +17,17 @@ namespace Volo.Abp.Auditing.App.EntityFrameworkCore
public DbSet<AppEntityWithPropertyHasAudited> AppEntityWithPropertyHasAudited { get; set; }
public DbSet<AppEntityWithSelector> AppEntityWithSelector { get; set; }
public DbSet<AppFullAuditedEntityWithAudited> AppFullAuditedEntityWithAudited { get; set; }
public DbSet<AppEntityWithAuditedAndHasCustomAuditingProperties> AppEntityWithAuditedAndHasCustomAuditingProperties { get; set; }
public DbSet<AppEntityWithSoftDelete> AppEntityWithSoftDelete { get; set; }
public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options)
: base(options)
{
}
}
}
}

22
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -314,5 +314,27 @@ namespace Volo.Abp.Auditing
&& x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithAudited.Name)));
#pragma warning restore 4014
}
[Fact]
public virtual async Task Should_Write_AuditLog_For_Soft_Deleted_Entity()
{
var entity = new AppEntityWithSoftDelete(Guid.NewGuid(), "test name");
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithSoftDelete, Guid>>();
await repository.InsertAsync(entity);
using (var scope = _auditingManager.BeginScope())
{
await repository.DeleteAsync(entity.Id);
await scope.SaveAsync();
}
#pragma warning disable 4014
_auditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Deleted &&
x.EntityChanges[0].PropertyChanges.Count == 0));
#pragma warning restore 4014
}
}
}

5
framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectNameValidation_Tests.cs

@ -51,13 +51,16 @@ namespace Volo.Abp.Cli
{
var illegalKeywords = new[]
{
"Blazor"
"Blazor"
};
foreach (var illegalKeyword in illegalKeywords)
{
var args = new CommandLineArgs("new", illegalKeyword);
await _newCommand.ExecuteAsync(args).ShouldThrowAsync<CliUsageException>();
args = new CommandLineArgs("new", "Acme." + illegalKeyword);
await _newCommand.ExecuteAsync(args).ShouldThrowAsync<CliUsageException>();
}
}

24
framework/test/Volo.Abp.IdentityModel.Tests/Volo.Abp.IdentityModel.Tests.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.IdentityModel\Volo.Abp.IdentityModel.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
<ItemGroup>
<None Remove="appsettings.json" />
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

51
framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityClientOptions_Tests.cs

@ -0,0 +1,51 @@
using System;
using Microsoft.Extensions.Options;
using Shouldly;
using Volo.Abp.MultiTenancy;
using Xunit;
namespace Volo.Abp.IdentityModel
{
public class AbpIdentityClientOptions_Tests : AbpIdentityModelTestBase
{
private readonly ICurrentTenant _currentTenant;
private readonly AbpIdentityClientOptions _identityClientOptions;
public AbpIdentityClientOptions_Tests()
{
_currentTenant = GetRequiredService<ICurrentTenant>();
_identityClientOptions = GetRequiredService<IOptions<AbpIdentityClientOptions>>().Value;
}
[Fact]
public void GetClientConfiguration_Test()
{
var hostDefaultConfiguration = _identityClientOptions.GetClientConfiguration(_currentTenant);
hostDefaultConfiguration.UserName.ShouldBe("host_default_admin");
var hostIdentityConfiguration = _identityClientOptions.GetClientConfiguration(_currentTenant, "Identity");
hostIdentityConfiguration.UserName.ShouldBe("host_identity_admin");
using (_currentTenant.Change(Guid.Parse("f72a344f-651e-49f0-85f6-be260a10e4df"), "Test_Tenant1"))
{
var tenantDefaultConfiguration = _identityClientOptions.GetClientConfiguration(_currentTenant);
tenantDefaultConfiguration.UserName.ShouldBe("tenant_default_admin");
}
using (_currentTenant.Change(Guid.Parse("f72a344f-651e-49f0-85f6-be260a10e4df")))
{
var tenantIdentityConfiguration = _identityClientOptions.GetClientConfiguration(_currentTenant, "Identity");
tenantIdentityConfiguration.UserName.ShouldBe("tenant_identity_admin");
}
using (_currentTenant.Change(Guid.NewGuid()))
{
var configuration = _identityClientOptions.GetClientConfiguration(_currentTenant);
configuration.UserName.ShouldBe("host_default_admin");
configuration = _identityClientOptions.GetClientConfiguration(_currentTenant, "Identity");
configuration.UserName.ShouldBe("host_identity_admin");
}
}
}
}

12
framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityModelTestBase.cs

@ -0,0 +1,12 @@
using Volo.Abp.Testing;
namespace Volo.Abp.IdentityModel
{
public abstract class AbpIdentityModelTestBase : AbpIntegratedTest<AbpIdentityModelTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

10
framework/test/Volo.Abp.IdentityModel.Tests/Volo/Abp/IdentityModel/AbpIdentityModelTestModule.cs

@ -0,0 +1,10 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.IdentityModel
{
[DependsOn(typeof(AbpIdentityModelModule))]
public class AbpIdentityModelTestModule : AbpModule
{
}
}

40
framework/test/Volo.Abp.IdentityModel.Tests/appsettings.json

@ -0,0 +1,40 @@
{
"IdentityClients": {
"Default": {
"GrantType": "password",
"ClientId": "Test_App",
"ClientSecret": "1q2w3e*",
"UserName": "host_default_admin",
"UserPassword": "1q2w3E*",
"Authority": "https://localhost:44395",
"Scope": "Test_Scope"
},
"Default.Test_Tenant1": {
"GrantType": "password",
"ClientId": "Test_App",
"ClientSecret": "1q2w3e*",
"UserName": "tenant_default_admin",
"UserPassword": "1q2w3E*",
"Authority": "https://localhost:44395",
"Scope": "Test_Scope"
},
"Identity": {
"GrantType": "password",
"ClientId": "Test_App",
"ClientSecret": "1q2w3e*",
"UserName": "host_identity_admin",
"UserPassword": "1q2w3E*",
"Authority": "https://localhost:44395",
"Scope": "Test_Scope"
},
"Identity.f72a344f-651e-49f0-85f6-be260a10e4df": {
"GrantType": "password",
"ClientId": "Test_App",
"ClientSecret": "1q2w3e*",
"UserName": "tenant_identity_admin",
"UserPassword": "1q2w3E*",
"Authority": "https://localhost:44395",
"Scope": "Test_Scope"
}
}
}

13
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/HardDelete_Tests.cs

@ -54,6 +54,19 @@ namespace Volo.Abp.TestApp.Testing
}
}
[Fact]
public async Task Should_HardDelete_Entities_With_IEnumerable()
{
using (DataFilter.Disable<ISoftDelete>())
{
var persons = await PersonRepository.GetListAsync();
await PersonRepository.HardDeleteAsync(persons);
var personCount = await PersonRepository.GetCountAsync();
personCount.ShouldBe(0);
}
}
[Fact]
public async Task Should_HardDelete_Soft_Deleted_Entities()
{

15
modules/account/src/Volo.Abp.Account.HttpApi.Client/ClientProxies/AccountClientProxy.Generated.cs

@ -18,17 +18,26 @@ namespace Volo.Abp.Account.ClientProxies
{
public virtual async Task<IdentityUserDto> RegisterAsync(RegisterDto input)
{
return await RequestAsync<IdentityUserDto>(nameof(RegisterAsync), input);
return await RequestAsync<IdentityUserDto>(nameof(RegisterAsync), new ClientProxyRequestTypeValue
{
{ typeof(RegisterDto), input }
});
}
public virtual async Task SendPasswordResetCodeAsync(SendPasswordResetCodeDto input)
{
await RequestAsync(nameof(SendPasswordResetCodeAsync), input);
await RequestAsync(nameof(SendPasswordResetCodeAsync), new ClientProxyRequestTypeValue
{
{ typeof(SendPasswordResetCodeDto), input }
});
}
public virtual async Task ResetPasswordAsync(ResetPasswordDto input)
{
await RequestAsync(nameof(ResetPasswordAsync), input);
await RequestAsync(nameof(ResetPasswordAsync), new ClientProxyRequestTypeValue
{
{ typeof(ResetPasswordDto), input }
});
}
}
}

1
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Validation;

1
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Widgets;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Identity;
using Volo.Abp.Validation;

22
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs

@ -1,29 +1,19 @@
using System;
using JetBrains.Annotations;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.Identity.EntityFrameworkCore;
namespace Volo.Abp.AuditLogging.EntityFrameworkCore
{
public static class AbpAuditLoggingDbContextModelBuilderExtensions
{
public static void ConfigureAuditLogging(
[NotNull] this ModelBuilder builder,
Action<AbpAuditLoggingModelBuilderConfigurationOptions> optionsAction = null)
[NotNull] this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
var options = new AbpAuditLoggingModelBuilderConfigurationOptions(
AbpAuditLoggingDbProperties.DbTablePrefix,
AbpAuditLoggingDbProperties.DbSchema
);
optionsAction?.Invoke(options);
builder.Entity<AuditLog>(b =>
{
b.ToTable(options.TablePrefix + "AuditLogs", options.Schema);
b.ToTable(AbpAuditLoggingDbProperties.DbTablePrefix + "AuditLogs", AbpAuditLoggingDbProperties.DbSchema);
b.ConfigureByConvention();
@ -56,7 +46,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
builder.Entity<AuditLogAction>(b =>
{
b.ToTable(options.TablePrefix + "AuditLogActions", options.Schema);
b.ToTable(AbpAuditLoggingDbProperties.DbTablePrefix + "AuditLogActions", AbpAuditLoggingDbProperties.DbSchema);
b.ConfigureByConvention();
@ -75,7 +65,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
builder.Entity<EntityChange>(b =>
{
b.ToTable(options.TablePrefix + "EntityChanges", options.Schema);
b.ToTable(AbpAuditLoggingDbProperties.DbTablePrefix + "EntityChanges", AbpAuditLoggingDbProperties.DbSchema);
b.ConfigureByConvention();
@ -96,7 +86,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore
builder.Entity<EntityPropertyChange>(b =>
{
b.ToTable(options.TablePrefix + "EntityPropertyChanges", options.Schema);
b.ToTable(AbpAuditLoggingDbProperties.DbTablePrefix + "EntityPropertyChanges", AbpAuditLoggingDbProperties.DbSchema);
b.ConfigureByConvention();

18
modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingModelBuilderConfigurationOptions.cs

@ -1,18 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
public class AbpAuditLoggingModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public AbpAuditLoggingModelBuilderConfigurationOptions(
[NotNull] string tablePrefix,
[CanBeNull] string schema)
: base(
tablePrefix,
schema)
{
}
}
}

14
modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbContextExtensions.cs

@ -1,25 +1,17 @@
using System;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB;
namespace Volo.Abp.AuditLogging.MongoDB
{
public static class AbpAuditLoggingMongoDbContextExtensions
{
public static void ConfigureAuditLogging(
this IMongoModelBuilder builder,
Action<AuditLoggingMongoModelBuilderConfigurationOptions> optionsAction = null)
this IMongoModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
var options = new AuditLoggingMongoModelBuilderConfigurationOptions(
AbpAuditLoggingDbProperties.DbTablePrefix
);
optionsAction?.Invoke(options);
builder.Entity<AuditLog>(b =>
{
b.CollectionName = options.CollectionPrefix + "AuditLogs";
b.CollectionName = AbpAuditLoggingDbProperties.DbTablePrefix + "AuditLogs";
});
}
}

14
modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/AuditLoggingMongoModelBuilderConfigurationOptions.cs

@ -1,14 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.MongoDB;
namespace Volo.Abp.AuditLogging.MongoDB
{
public class AuditLoggingMongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions
{
public AuditLoggingMongoModelBuilderConfigurationOptions(
[NotNull] string collectionPrefix = "")
: base(collectionPrefix)
{
}
}
}

12
modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs

@ -7,8 +7,7 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore
public static class BackgroundJobsDbContextModelCreatingExtensions
{
public static void ConfigureBackgroundJobs(
this ModelBuilder builder,
Action<BackgroundJobsModelBuilderConfigurationOptions> optionsAction = null)
this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
@ -17,16 +16,9 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore
return;
}
var options = new BackgroundJobsModelBuilderConfigurationOptions(
BackgroundJobsDbProperties.DbTablePrefix,
BackgroundJobsDbProperties.DbSchema
);
optionsAction?.Invoke(options);
builder.Entity<BackgroundJobRecord>(b =>
{
b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema);
b.ToTable(BackgroundJobsDbProperties.DbTablePrefix + "BackgroundJobs", BackgroundJobsDbProperties.DbSchema);
b.ConfigureByConvention();

18
modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsModelBuilderConfigurationOptions.cs

@ -1,18 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore
{
public class BackgroundJobsModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public BackgroundJobsModelBuilderConfigurationOptions(
[NotNull] string tablePrefix = "",
[CanBeNull] string schema = null)
: base(
tablePrefix,
schema)
{
}
}
}

16
modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobsMongoDbContextExtensions.cs

@ -1,26 +1,18 @@
using System;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB;
namespace Volo.Abp.BackgroundJobs.MongoDB
{
public static class BackgroundJobsMongoDbContextExtensions
{
public static void ConfigureBackgroundJobs(
this IMongoModelBuilder builder,
Action<AbpMongoModelBuilderConfigurationOptions> optionsAction = null)
this IMongoModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
var options = new BackgroundJobsMongoModelBuilderConfigurationOptions(
BackgroundJobsDbProperties.DbTablePrefix
);
optionsAction?.Invoke(options);
builder.Entity<BackgroundJobRecord>(b =>
{
b.CollectionName = options.CollectionPrefix + "BackgroundJobs";
b.CollectionName = BackgroundJobsDbProperties.DbTablePrefix + "BackgroundJobs";
});
}
}
}
}

14
modules/background-jobs/src/Volo.Abp.BackgroundJobs.MongoDB/Volo/Abp/BackgroundJobs/MongoDB/BackgroundJobsMongoModelBuilderConfigurationOptions.cs

@ -1,14 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.MongoDB;
namespace Volo.Abp.BackgroundJobs.MongoDB
{
public class BackgroundJobsMongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions
{
public BackgroundJobsMongoModelBuilderConfigurationOptions(
[NotNull] string collectionPrefix = "")
: base(collectionPrefix)
{
}
}
}

2
modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
FROM mcr.microsoft.com/dotnet/aspnet:6.0.0-rc.1-bullseye-slim AS base
WORKDIR /app
EXPOSE 80
COPY bin/Release/publish .

15
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringDbContextModelCreatingExtensions.cs

@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using System;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
@ -7,21 +6,13 @@ namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
public static class BlobStoringDbContextModelCreatingExtensions
{
public static void ConfigureBlobStoring(
this ModelBuilder builder,
Action<BlobStoringModelBuilderConfigurationOptions> optionsAction = null)
this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
var options = new BlobStoringModelBuilderConfigurationOptions(
BlobStoringDatabaseDbProperties.DbTablePrefix,
BlobStoringDatabaseDbProperties.DbSchema
);
optionsAction?.Invoke(options);
builder.Entity<DatabaseBlobContainer>(b =>
{
b.ToTable(options.TablePrefix + "BlobContainers", options.Schema);
b.ToTable(BlobStoringDatabaseDbProperties.DbTablePrefix + "BlobContainers", BlobStoringDatabaseDbProperties.DbSchema);
b.ConfigureByConvention();
@ -36,7 +27,7 @@ namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
builder.Entity<DatabaseBlob>(b =>
{
b.ToTable(options.TablePrefix + "Blobs", options.Schema);
b.ToTable(BlobStoringDatabaseDbProperties.DbTablePrefix + "Blobs", BlobStoringDatabaseDbProperties.DbSchema);
b.ConfigureByConvention();

18
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.EntityFrameworkCore/Volo/Abp/BlobStoring/Database/EntityFrameworkCore/BlobStoringModelBuilderConfigurationOptions.cs

@ -1,18 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore
{
public class BlobStoringModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public BlobStoringModelBuilderConfigurationOptions(
[NotNull] string tablePrefix = "",
[CanBeNull] string schema = null)
: base(
tablePrefix,
schema)
{
}
}
}

18
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringMongoDbContextExtensions.cs

@ -1,31 +1,23 @@
using System;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB;
namespace Volo.Abp.BlobStoring.Database.MongoDB
{
public static class BlobStoringMongoDbContextExtensions
{
public static void ConfigureBlobStoring(
this IMongoModelBuilder builder,
Action<AbpMongoModelBuilderConfigurationOptions> optionsAction = null)
this IMongoModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
var options = new BlobStoringMongoModelBuilderConfigurationOptions(
BlobStoringDatabaseDbProperties.DbTablePrefix
);
optionsAction?.Invoke(options);
builder.Entity<DatabaseBlobContainer>(b =>
{
b.CollectionName = options.CollectionPrefix + "BlobContainers";
b.CollectionName = BlobStoringDatabaseDbProperties.DbTablePrefix + "BlobContainers";
});
builder.Entity<DatabaseBlob>(b =>
{
b.CollectionName = options.CollectionPrefix + "Blobs";
b.CollectionName = BlobStoringDatabaseDbProperties.DbTablePrefix + "Blobs";
});
}
}
}
}

14
modules/blob-storing-database/src/Volo.Abp.BlobStoring.Database.MongoDB/Volo/Abp/BlobStoring/Database/MongoDB/BlobStoringMongoModelBuilderConfigurationOptions.cs

@ -1,14 +0,0 @@
using JetBrains.Annotations;
using Volo.Abp.MongoDB;
namespace Volo.Abp.BlobStoring.Database.MongoDB
{
public class BlobStoringMongoModelBuilderConfigurationOptions : AbpMongoModelBuilderConfigurationOptions
{
public BlobStoringMongoModelBuilderConfigurationOptions(
[NotNull] string collectionPrefix = "")
: base(collectionPrefix)
{
}
}
}

6
modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs

@ -31,6 +31,7 @@ using Volo.Abp.Identity;
using Volo.Abp.Identity.Web;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.PermissionManagement.HttpApi;
using Volo.Abp.PermissionManagement.Identity;
using Volo.Abp.Threading;
using Volo.Abp.UI;
@ -45,7 +46,9 @@ namespace Volo.BloggingTestApp
[DependsOn(
typeof(BloggingWebModule),
typeof(BloggingApplicationModule),
typeof(BloggingHttpApiModule),
typeof(BloggingAdminWebModule),
typeof(BloggingAdminHttpApiModule),
typeof(BloggingAdminApplicationModule),
#if MONGODB
typeof(BloggingTestAppMongoDbModule),
@ -53,11 +56,14 @@ namespace Volo.BloggingTestApp
typeof(BloggingTestAppEntityFrameworkCoreModule),
#endif
typeof(AbpAccountWebModule),
typeof(AbpAccountHttpApiModule),
typeof(AbpAccountApplicationModule),
typeof(AbpIdentityWebModule),
typeof(AbpIdentityHttpApiModule),
typeof(AbpIdentityApplicationModule),
typeof(AbpPermissionManagementDomainIdentityModule),
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpPermissionManagementHttpApiModule),
typeof(BlobStoringDatabaseDomainModule),
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcUiBasicThemeModule)

5
modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj

@ -21,6 +21,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Blogging.Admin.HttpApi\Volo.Blogging.Admin.HttpApi.csproj" />
<ProjectReference Include="..\..\src\Volo.Blogging.HttpApi\Volo.Blogging.HttpApi.csproj" />
<ProjectReference Include="..\Volo.BloggingTestApp.EntityFrameworkCore\Volo.BloggingTestApp.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\src\Volo.Blogging.Application\Volo.Blogging.Application.csproj" />
<ProjectReference Include="..\..\src\Volo.Blogging.Web\Volo.Blogging.Web.csproj" />
@ -30,9 +32,12 @@
<ProjectReference Include="..\..\..\..\modules\basic-theme\src\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj" />
<ProjectReference Include="..\..\..\..\modules\identity\src\Volo.Abp.Identity.Application\Volo.Abp.Identity.Application.csproj" />
<ProjectReference Include="..\..\..\..\modules\identity\src\Volo.Abp.Identity.Web\Volo.Abp.Identity.Web.csproj" />
<ProjectReference Include="..\..\..\..\modules\identity\src\Volo.Abp.Identity.HttpApi\Volo.Abp.Identity.HttpApi.csproj" />
<ProjectReference Include="..\..\..\..\modules\identity\src\Volo.Abp.PermissionManagement.Domain.Identity\Volo.Abp.PermissionManagement.Domain.Identity.csproj" />
<ProjectReference Include="..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.Application\Volo.Abp.PermissionManagement.Application.csproj" />
<ProjectReference Include="..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.HttpApi\Volo.Abp.PermissionManagement.HttpApi.csproj" />
<ProjectReference Include="..\..\..\..\modules\account\src\Volo.Abp.Account.Web\Volo.Abp.Account.Web.csproj" />
<ProjectReference Include="..\..\..\..\modules\account\src\Volo.Abp.Account.HttpApi\Volo.Abp.Account.HttpApi.csproj" />
<ProjectReference Include="..\..\..\..\modules\account\src\Volo.Abp.Account.Application\Volo.Abp.Account.Application.csproj" />
<ProjectReference Include="..\..\..\..\modules\blob-storing-database\src\Volo.Abp.BlobStoring.Database.Domain\Volo.Abp.BlobStoring.Database.Domain.csproj" />
</ItemGroup>

3123
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.all.js

File diff suppressed because one or more lines are too long

2
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.all.min.js

File diff suppressed because one or more lines are too long

1319
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.css

File diff suppressed because it is too large

3121
modules/blogging/app/Volo.BloggingTestApp/wwwroot/libs/sweetalert2/sweetalert2.js

File diff suppressed because it is too large

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

Loading…
Cancel
Save