Browse Source

Merge remote-tracking branch 'abpframework/dev' into Translate

pull/2498/head
liangshiw 7 years ago
parent
commit
41b8f5fc0a
  1. 11
      .github/labeler.yml
  2. 21
      .github/workflows/labeler.yml
  3. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  4. 2
      common.props
  5. 35
      docs/cs/Best-Practices/PostgreSQL-Integration.md
  6. 4
      docs/cs/CLI.md
  7. 74
      docs/cs/EntityFrameworkCore-PostgreSQL-Integration.md
  8. 2
      docs/cs/Getting-Started-Angular-Template.md
  9. 113
      docs/cs/Getting-Started-AspNetCore-Application.md
  10. 10
      docs/cs/Index.md
  11. 23
      docs/cs/docs-nav.json
  12. BIN
      docs/cs/images/authorization-new-permission-ui-hierarcy.png
  13. BIN
      docs/cs/images/authorization-new-permission-ui-localized.png
  14. BIN
      docs/cs/images/authorization-new-permission-ui.png
  15. BIN
      docs/cs/images/create-aspnet-core-application.png
  16. BIN
      docs/cs/images/create-new-aspnet-core-application-v2.png
  17. BIN
      docs/cs/images/select-empty-web-application-v2.png
  18. 7
      docs/en/Best-Practices/Entity-Framework-Core-Integration.md
  19. 18
      docs/en/CLI.md
  20. 11
      docs/en/Connection-Strings.md
  21. 0
      docs/en/EntityFrameworkCore-PostgreSQL-Integration.md
  22. 93
      docs/en/Modules/Docs.md
  23. 53
      docs/en/Settings.md
  24. 8
      docs/en/Tutorials/Angular/Part-I.md
  25. 12
      docs/en/Tutorials/Angular/Part-II.md
  26. 2
      docs/en/docs-nav.json
  27. BIN
      docs/en/images/docs-section-ui.png
  28. 8
      docs/pt-BR/Tutorials/Angular/Part-I.md
  29. 12
      docs/pt-BR/Tutorials/Angular/Part-II.md
  30. 2
      docs/pt-BR/docs-nav.json
  31. 7
      docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md
  32. 2
      docs/zh-Hans/Entities.md
  33. 0
      docs/zh-Hans/EntityFrameworkCore-PostgreSQL-Integration.md
  34. 10
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md
  35. 2
      docs/zh-Hans/docs-nav.json
  36. 4
      framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj
  37. 2
      framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj
  38. 6
      framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs
  39. 2
      framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo.Abp.AspNetCore.MultiTenancy.csproj
  40. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj
  41. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs
  42. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs
  43. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs
  44. 50
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteTenantStore.cs
  45. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo.Abp.AspNetCore.Mvc.Contracts.csproj
  46. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj
  47. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj
  48. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj
  49. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/cs.json
  50. 1
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pt-BR.json
  51. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj
  52. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj
  53. 4
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Views/Error/Default.cshtml
  54. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj
  55. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj
  56. 3
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo.Abp.AspNetCore.Mvc.UI.csproj
  57. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
  58. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  59. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs
  60. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  61. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs
  62. 23
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs
  63. 13
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs
  64. 20
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs
  65. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs
  66. 4
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
  67. 4
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs
  68. 79
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
  69. 3
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/IAbpRequestLocalizationOptionsProvider.cs
  70. 2
      framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
  71. 27
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs
  72. 15
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs
  73. 2
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs
  74. 2
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs
  75. 7
      framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs
  76. 28
      framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs
  77. 2
      framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj
  78. 7
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptor.cs
  79. 4
      framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs
  80. 2
      framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs
  81. 2
      framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj
  82. 3
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs
  83. 20
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs
  84. 7
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobArgsHelper.cs
  85. 17
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs
  86. 16
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs
  87. 5
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs
  88. 31
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs
  89. 7
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs
  90. 6
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs
  91. 34
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/IBackgroundJobStore.cs
  92. 35
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs
  93. 2
      framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj
  94. 1
      framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj
  95. 2
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/AbpCastleCoreModule.cs
  96. 15
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/AbpAsyncDeterminationInterceptor.cs
  97. 89
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs
  98. 67
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs
  99. 48
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapterBase.cs
  100. 27
      framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapterWithReturnValue.cs

11
.github/labeler.yml

@ -1,4 +1,15 @@
ui-angular:
- npm/ng-packs/*
- npm/ng-packs/**/*
- npm/ng-packs/**/**/*
- npm/ng-packs/**/**/**/*
- npm/ng-packs/**/**/**/**/*
- npm/ng-packs/**/**/**/**/**/*
- templates/app/angular/*
- templates/app/angular/**/*
- templates/app/angular/**/**/*
- templates/app/angular/**/**/**/*
- templates/module/angular/*
- templates/module/angular/**/*
- templates/module/angular/**/**/*
- templates/module/angular/**/**/**/*

21
.github/workflows/labeler.yml

@ -1,17 +1,12 @@
name: "Pull Request Labeler"
name: Pull request labeler
on:
pull_request:
paths:
- npm/ng-packs/**/*
- templates/app/angular/**/*
- templates/module/angular/**/*
branches:
- master
- dev
schedule:
- cron: '0 12 */1 * *'
jobs:
labeler:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
- uses: paulfantom/periodic-labeler@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}

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

@ -25,6 +25,7 @@
"Volo.AbpIo.Commercial:010004": "Can not find the specified user! The user must have already registered.",
"MyOrganizations": "My organizations",
"ApiKey": "API key",
"UserNameNotFound": "There is no user with username {0}"
"UserNameNotFound": "There is no user with username {0}",
"SuccessfullyAddedToNewsletter": "Thanks you for subscribing to our newsletter!"
}
}

2
common.props

@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>1.0.2</Version>
<Version>2.0.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io</PackageProjectUrl>

35
docs/cs/Best-Practices/PostgreSQL-Integration.md

@ -1,35 +0,0 @@
## Entity Framework Core PostgreSQL integrace
> Podívejte se na [Entity Framework Core integrační dokument](../Entity-Framework-Core.md) pro základy integrace EF Core.
### Aktualizace projektu EntityFrameworkCore
- V projektu `Acme.BookStore.EntityFrameworkCore` nahraďte balík `Volo.Abp.EntityFrameworkCore.SqlServer` za `Volo.Abp.EntityFrameworkCore.PostgreSql`
- Aktualizace pro použití PostgreSQL v `BookStoreEntityFrameworkCoreModule`
- Nahraďte `AbpEntityFrameworkCoreSqlServerModule` za `AbpEntityFrameworkCorePostgreSqlModule`
- Nahraďte `options.UseSqlServer()` za `options.UsePostgreSql()`
- V jiných projektech aktualizujte PostgreSQL connection string v nezbytných `appsettings.json` souborech
#### Odstranění stávajících migrací
Smažte všechny stavající migrační soubory (včetně `DbContextModelSnapshot`)
![postgresql-delete-initial-migrations](images/postgresql-delete-initial-migrations.png)
#### Znovu vygenerujte počáteční migraci & aktualizujte databázi
Nastavte správný spouštěcí projekt (obvykle web projekt),
Otevřete **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), zvolte `Acme.BookStore.EntityFrameworkCore.DbMigrations` jako **Default project** a proveďte následující příkaz:
Proveďte příkaz `Add-Migration`:
````
PM> Add-Migration Initial
````
Poté proveďte příkaz `Update-Database` k aktualizaci schématu databáze:
````
PM> Update-Database
````
![postgresql-update-database](images/postgresql-update-database.png)

4
docs/cs/CLI.md

@ -46,11 +46,13 @@ abp new Acme.BookStore
* `--tiered`: Vytvoří stupňovité řešení, kde jsou vrstvy Web a Http API fyzicky odděleny. Pokud není uvedeno, tak vytvoří vrstvené řešení, které je méně složité a vhodné pro většinu scénářů.
* `angular`: Angular. Pro tuto šablonu jsou dostupné dodatečné možnosti:
* `--separate-identity-server`: Oddělí identity server aplikaci od API host aplikace. Pokud není uvedeno, bude na straně serveru jediný koncový bod.
* `none`: Bez UI. Pro tuto šablonu jsou dostupné dodatečné možnosti:
* `--separate-identity-server`: Oddělí identity server aplikaci od API host aplikace. Pokud není uvedeno, bude na straně serveru jediný koncový bod.
* `--database-provider` nebo `-d`: Určuje poskytovatele databáze. Výchozí poskytovatel je `ef`. Dostupní poskytovatelé:
* `ef`: Entity Framework Core.
* `mongodb`: MongoDB.
* `module`: [Šablona modulu](Startup-Templates/Module.md). Dodatečné možnosti:
* `--no-ui`: Určuje nezahrnutí uživatelského rozhraní. Umožňuje vytvořit moduly pouze pro služby (a.k.a. mikroslužby - bez uživatelského rozhraní).
* `--no-ui`: Určuje nezahrnutí uživatelského rozhraní. Umožňuje vytvořit moduly pouze pro služby (a.k.a. mikroslužby - bez uživatelského rozhraní).
* `--output-folder` nebo `-o`: Určuje výstupní složku. Výchozí hodnota je aktuální adresář.
* `--version` nebo `-v`: Určuje verzi ABP & šablony. Může to být [štítek vydání](https://github.com/abpframework/abp/releases) nebo [název větve](https://github.com/abpframework/abp/branches). Pokud není uvedeno, používá nejnovější vydání. Většinou budete chtít použít nejnovější verzi.

74
docs/cs/EntityFrameworkCore-PostgreSQL-Integration.md

@ -0,0 +1,74 @@
## Entity Framework Core PostgreSQL integrace
> Podívejte se na [Entity Framework Core integrační dokument](../Entity-Framework-Core.md) pro základy integrace EF Core.
### Aktualizace projektu EntityFrameworkCore
- V projektu `Acme.BookStore.EntityFrameworkCore` nahraďte balík `Volo.Abp.EntityFrameworkCore.SqlServer` za `Volo.Abp.EntityFrameworkCore.PostgreSql`
- Aktualizace pro použití PostgreSQL v `BookStoreEntityFrameworkCoreModule`
- Nahraďte `AbpEntityFrameworkCoreSqlServerModule` za `AbpEntityFrameworkCorePostgreSqlModule`
- Nahraďte `options.UseSqlServer()` za `options.UsePostgreSql()`
- V jiných projektech aktualizujte PostgreSQL connection string v nezbytných `appsettings.json` souborech
- Více informací v [PostgreSQL connection strings](https://www.connectionstrings.com/postgresql/), v tomto dokumentu věnujte pozornost sekci `Npgsql`
### Aktualizace projektu EntityFrameworkCore.DbMigrations
- Aktualizace pro použití PostgreSQL v `XXXMigrationsDbContextFactory`
- Nahraďte `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseSqlServer()` za `new DbContextOptionsBuilder<XXXMigrationsDbContext>().UseNpgsql()`
### Odstranění stávajících migrací
Smažte všechny stavající migrační soubory (včetně `DbContextModelSnapshot`)
![postgresql-delete-initial-migrations](images/postgresql-delete-initial-migrations.png)
### Znovu vygenerujte počáteční migraci
Nastavte správný spouštěcí projekt (obvykle web projekt)
![set-as-startup-project](../images/set-as-startup-project.png)
Otevřete **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), zvolte `.EntityFrameworkCore.DbMigrations` jako **Default project** a proveďte následující příkaz:
Proveďte příkaz `Add-Migration`:
````
PM> Add-Migration Initial
````
### Aktualizace databáze
K vytvoření databáze máte dvě možnosti.
#### Použití DbMigrator aplikace
Řešení obsahuje konzolovou aplikaci (v tomto příkladu nazvanou `Acme.BookStore.DbMigrator`), která může vytvářet databáze, aplikovat migrace a vkládat seed data. Je užitečná jak pro vývojové, tak pro produkční prostředí.
> Projekt `.DbMigrator` má vlastní `appsettings.json`. Takže pokud jste změnili connection string uvedený výše, musíte změnit také tento.
Klikněte pravým na projekt `.DbMigrator` a vyberte **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)
Zmáčkněte F5 (nebo Ctrl+F5) ke spuštění aplikace. Výstup bude vypadat následovně:
![set-as-startup-project](images/db-migrator-app.png)
#### Použití EF Core Update-Database příkazu
Ef Core má `Update-Database` příkaz, který v případě potřeby vytvoří databázi a aplikuje čekající migrace.
Nastavte správný spouštěcí projekt (obvykle web projekt)
![set-as-startup-project](../images/set-as-startup-project.png)
Otevřete **Package Manager Console** (Tools -> Nuget Package Manager -> Package Manager Console), vyberte projekt `.EntityFrameworkCore.DbMigrations` jako **Default Project** and spusťte následující příkaz:
````
PM> Update-Database
````
Dojde k vytvoření nové databáze na základě nakonfigurovaného connection stringu.
![postgresql-update-database](images/postgresql-update-database.png)
> Použití nástroje `.DbMigrator` je doporučený způsob, jelikož zároveň vloží seed data nutné k správnému běhu webové aplikace.

2
docs/cs/Getting-Started-Angular-Template.md

@ -75,7 +75,7 @@ Zmáčkněte F5 (nebo Ctrl+F5) ke spuštění aplikace. Výstup by měl být pod
#### Použití příkazu EF Core Update-Database
Ef Core máš příkaz `Update-Database`, který v případě potřeby vytvoří databázi a aplikuje čekající migrace. Klikněte pravým na projekt `.Web` a zvolte **Set as StartUp Project**:
Ef Core máš příkaz `Update-Database`, který v případě potřeby vytvoří databázi a aplikuje čekající migrace. Klikněte pravým na projekt `.HttpApi.Host` a zvolte **Set as StartUp Project**:
![set-as-startup-project](images/set-as-startup-project.png)

113
docs/cs/Getting-Started-AspNetCore-Application.md

@ -4,15 +4,17 @@ Tento tutoriál vysvětluje jak začít s ABP z ničeho s minimem závislostí.
## Tvorba nového projektu
1. Vytvořte novou prázdnou AspNet Core Web aplikaci ve Visual Studio:
1. Vytvořte novou AspNet Core Web aplikaci ve Visual Studio 2019 (16.3.0+):
![](images/create-new-aspnet-core-application.png)
![](images/create-new-aspnet-core-application-v2.png)
2. Zvolte prázdnou šablonu
2. Nakonfigurujte váš nový projekt:
![](images/select-empty-web-application.png)
![](images/select-empty-web-application-v2.png)
Můžete zvolit i jinou šablonu, ale pro demonstraci je lepší čístý projekt.
3. Potvrďte kliknutím na tlačítko vytvořit
![create-aspnet-core-application](images/create-aspnet-core-application.png)
## Instalace Volo.Abp.AspNetCore.Mvc balíku
@ -28,10 +30,8 @@ ABP je modulární framework a proto vyžaduje **spouštěcí (kořenový) modul
````C#
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Volo.Abp;
using Volo.Abp.AspNetCore.Modularity;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
@ -40,7 +40,8 @@ namespace BasicAspNetCoreApplication
[DependsOn(typeof(AbpAspNetCoreMvcModule))]
public class AppModule : AbpModule
{
public override void OnApplicationInitialization(ApplicationInitializationContext context)
public override void OnApplicationInitialization(
ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
@ -49,8 +50,14 @@ namespace BasicAspNetCoreApplication
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
app.UseRouting();
app.UseMvcWithDefaultRouteAndArea();
}
}
}
@ -58,7 +65,7 @@ namespace BasicAspNetCoreApplication
``AppModule`` je dobrý název pro spouštěcí modul aplikace.
ABP balíky definují modulové třídy a modul může mít závislost na jiný modul. V kódu výše, náš ``AppModule`` má závislost na ``AbpAspNetCoreMvcModule`` (definován v balíku Volo.Abp.AspNetCore.Mvc). Je běžné přidat ``DependsOn`` atribute po instalaci nového ABP NuGet balíku.
ABP balíky definují modulové třídy a modul může mít závislost na jiném. V kódu výše, ``AppModule`` má závislost na ``AbpAspNetCoreMvcModule`` (definován v balíku [Volo.Abp.AspNetCore.Mvc](https://www.nuget.org/packages/Volo.Abp.AspNetCore.Mvc)). Je běžné přidat ``DependsOn`` atribute po instalaci nového ABP NuGet balíku.
Místo třídy Startup, konfigurujeme ASP.NET Core pipeline v této modulové třídě.
@ -67,7 +74,6 @@ Místo třídy Startup, konfigurujeme ASP.NET Core pipeline v této modulové t
V dalším kroku upravíme Startup třídu k integraci ABP modulového systému:
````C#
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
@ -75,11 +81,9 @@ namespace BasicAspNetCoreApplication
{
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
{
services.AddApplication<AppModule>();
return services.BuildServiceProviderFromFactory();
}
public void Configure(IApplicationBuilder app)
@ -88,41 +92,19 @@ namespace BasicAspNetCoreApplication
}
}
}
````
Změnili jsme metodu ``ConfigureServices`` aby vracela ``IServiceProvider`` místo ``void``. Tato změna nám dovoluje nahradit AspNet Core vkládání závislostí za jiný framework (více v sekci Autofac integrace níže). ``services.AddApplication<AppModule>()`` přidává všechny služby definované ve všech modulech počínaje ``AppModule``.
``services.AddApplication<AppModule>()`` přidává všechny služby definované ve všech modulech počínaje od ``AppModule``.
Volání ``app.InitializeApplication()`` v metodě ``Configure`` inicializuje a spustí aplikaci.
``app.InitializeApplication()`` v metodě ``Configure`` inicializuje a spustí aplikaci.
## Ahoj světe!
## Spusťte aplikaci!
Aplikace výše zatím nic nedělá. Pojďme proto vytvořit MVC controller, který už něco dělá:
````C#
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace BasicAspNetCoreApplication.Controllers
{
public class HomeController : AbpController
{
public IActionResult Index()
{
return Content("Hello World!");
}
}
}
````
Jakmile spustíte aplikaci, uvidíte na stránce zprávu "Hello World!".
Odvození ``HomeController`` od ``AbpController`` místo standardní třídy ``Controller``. Toto není vyžadováno, ale třída ``AbpController`` má užitečné základní vlastnosti a metody, které usnadňují vývoj.
To je vše! Spusťte aplikaci, bude fungovat podle očekávání.
## Použití Autofac jako frameworku pro vkládání závislostí
Ačkoliv je AspNet Core systém pro vkládání závíslostí (DI) skvělý pro základní požadavky, Autofac poskytuje pokročilé funkce jako injekce vlastností nebo záchyt metod, které jsou v ABP užity k provádění pokročilých funkcí frameworku.
Ačkoliv je AspNet Core systém pro vkládání závíslostí (DI) dostatečný pro základní požadavky, [Autofac](https://autofac.org/) poskytuje pokročilé funkce jako injekce vlastností nebo záchyt metod, které jsou v ABP užity k provádění pokročilých funkcí frameworku.
Nahrazení AspNet Core DI systému za Autofac a integrace s ABP je snadná.
@ -143,40 +125,29 @@ public class AppModule : AbpModule
}
````
3. Změníme řádek ``services.AddApplication<AppModule>();`` v třídě ``Startup`` následovně:
````C#
services.AddApplication<AppModule>(options =>
{
options.UseAutofac(); // Integrace s Autofac
});
````
4. Upravíme `Program.cs` aby nepoužíval metodu `WebHost.CreateDefaultBuilder()` jelikož ta používá výchozí DI kontejner:
3. Upravíme `Program.cs` aby používal Autofac:
````csharp
public class Program
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace BasicAspNetCoreApplication
{
public static void Main(string[] args)
public class Program
{
/*
https://github.com/aspnet/AspNetCore/issues/4206#issuecomment-445612167
CurrentDirectoryHelpers exists in: \framework\src\Volo.Abp.AspNetCore.Mvc\Microsoft\AspNetCore\InProcess\CurrentDirectoryHelpers.cs
Will remove CurrentDirectoryHelpers.cs when upgrade to ASP.NET Core 3.0.
*/
CurrentDirectoryHelpers.SetCurrentDirectory();
BuildWebHostInternal(args).Run();
}
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebHost BuildWebHostInternal(string[] args) =>
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIIS()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseAutofac(); // Přidejte tento řádek
}
}
````

10
docs/cs/Index.md

@ -4,18 +4,12 @@ ABP je **open source aplikační framework** se zaměřením na vývoj webových
K procházení dokumentace využijte navigační nabídky vlevo.
## Stav projektu
ABP je **novou generací** open source frameworku [ASP.NET Boilerplate](https://aspnetboilerplate.com/). V současné době je v náhledové fázi a není připraven k ostrému nasazení. Na dokumentaci se stále pracuje a ještě zdaleka není dokončena.
Pro aplikace na krátkodobé a ostré nasazení je doporučeno použít [ASP.NET Boilerplate](https://aspnetboilerplate.com/) framework, který je bohatý na funkce, vyspělý, aktivně udržovaný a aktuální.
## Začínáme
Nejsnazší cestou jak začít nový projekt s ABP je užití startovací šablony:
* [ASP.NET Core MVC (Razor Pages) UI Šablona](Getting-Started-AspNetCore-MVC-Template.md)
* [Angular UI Šablona](Getting-Started-Angular-Template.md)
* [ASP.NET Core MVC (Razor Pages) UI Počáteční Šablona](Getting-Started-AspNetCore-MVC-Template.md)
* [Angular UI Počáteční Šablona](Getting-Started-Angular-Template.md)
Pokud chcete začít od nuly (s prázdným projektem) tak manuálně nainstalujte ABP Framework s pomocí následujících tutoriálů:

23
docs/cs/docs-nav.json

@ -56,6 +56,10 @@
{
"text": "Základy",
"items": [
{
"text": "Konfigurace",
"path": "Configuration.md"
},
{
"text": "Vkládání závislostí",
"path": "Dependency-Injection.md",
@ -82,16 +86,19 @@
"text": "Validace"
},
{
"text": "Autorizace"
"text": "Autorizace",
"path": "Authorization.md"
},
{
"text": "Ukládání do mezipaměti"
"text": "Ukládání do mezipaměti",
"path": "Caching.md"
},
{
"text": "Audit"
},
{
"text": "Správa nastavení"
"text": "Nastavení",
"path": "Settings.md"
}
]
},
@ -114,6 +121,14 @@
{
"text": "Služby",
"items": [
{
"text": "Současný uživatel",
"path": "CurrentUser.md"
},
{
"text": "Mapování objekt na objekt",
"path": "Object-To-Object-Mapping.md"
},
{
"text": "Serializace objektu"
},
@ -249,7 +264,7 @@
"items": [
{
"text": "PostgreSQL integrace",
"path": "Best-Practices/PostgreSQL-Integration.md"
"path": "EntityFrameworkCore-PostgreSQL-Integration.md"
}
]
},

BIN
docs/cs/images/authorization-new-permission-ui-hierarcy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
docs/cs/images/authorization-new-permission-ui-localized.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/cs/images/authorization-new-permission-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/cs/images/create-aspnet-core-application.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/cs/images/create-new-aspnet-core-application-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
docs/cs/images/select-empty-web-application-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

7
docs/en/Best-Practices/Entity-Framework-Core-Integration.md

@ -174,12 +174,12 @@ public static IQueryable<IdentityUser> IncludeDetails(
* **Do** use the `IncludeDetails` extension method in the repository methods just like used in the example code above (see FindByNormalizedUserNameAsync).
- **Do** override `IncludeDetails` method of the repository for aggregates root which have **sub collections**. Example:
- **Do** override `WithDetails` method of the repository for aggregates root which have **sub collections**. Example:
````C#
protected override IQueryable<IdentityUser> IncludeDetails(IQueryable<IdentityUser> queryable)
public override IQueryable<IdentityUser> WithDetails()
{
return queryable.IncludeDetails(); //uses the extension method defined above
return GetQueryable().IncludeDetails(); // Uses the extension method defined above
}
````
@ -206,4 +206,3 @@ public class AbpIdentityEntityFrameworkCoreModule : AbpModule
}
}
````

18
docs/en/CLI.md

@ -128,6 +128,24 @@ abp update [options]
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
### login
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write
```bash
abp login <username>
```
Notice that, a new login with an already active session, will kill the previous session and creates a new one.
### logout
Logs you out by removing the session token from your computer.
```
abp logout
```
### help
Writes basic usage information of the CLI.

11
docs/en/Connection-Strings.md

@ -0,0 +1,11 @@
# Data Access
ABP framework was designed as database agnostic, it can work any type of data source by the help of the [repository](Repositories.md) and [unit of work](Unit-Of-Work.md) abstractions.
However, currently the following providers are implements:
* [Entity Framework Core](Entity-Framework-Core.md) (works with [various DBMS and providers](https://docs.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli).)
* [MongoDB](MongoDB.md)
* [Dapper](Dapper.md)
More providers might be added in the next releases.

0
docs/en/Best-Practices/PostgreSQL-Integration.md → docs/en/EntityFrameworkCore-PostgreSQL-Integration.md

93
docs/en/Modules/Docs.md

@ -408,12 +408,101 @@ public class Person
```
~~~
As an example you can see ABP Framework documentation:
[https://github.com/abpframework/abp/blob/master/docs/en/](https://github.com/abpframework/abp/blob/master/docs/en/)
#### Conditional sections feature (Using Scriban)
Docs module uses [Scriban](<https://github.com/lunet-io/scriban/tree/master/doc> ) for conditionally show or hide some parts of a document. In order to use that feature, you have to create a JSON file as **Parameter document** per every language. It will contain all the key-values, as well as their display names.
For example, [en/docs-params.json](https://github.com/abpio/abp-commercial-docs/blob/master/en/docs-params.json):
```json
{
"parameters": [{
"name": "UI",
"displayName": "UI",
"values": {
"MVC": "MVC / Razor Pages",
"NG": "Angular"
}
},
{
"name": "DB",
"displayName": "Database",
"values": {
"EF": "Entity Framework Core",
"Mongo": "MongoDB"
}
},
{
"name": "Tiered",
"displayName": "Tiered",
"values": {
"No": "Not Tiered",
"Yes": "Tiered"
}
}]
}
```
Since not every single document in your projects may not have sections or may not need all of those parameters, you have to declare which of those parameters will be used for sectioning the document, as a JSON block anywhere on the document.
For example [Getting-Started.md](https://github.com/abpio/abp-commercial-docs/blob/master/en/Getting-Started.md):
```
.....
​````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
​````
........
```
This section will be automatically deleted during render. And f course, those key values must match with the ones in **Parameter document**.
![Interface](..\images\docs-section-ui.png)
Now you can use **Scriban** syntax to create sections in your document.
For example:
````
{{ if UI == "NG" }}
* `-u` argument specifies the UI framework, `angular` in this case.
{{ end }}
{{ if DB == "Mongo" }}
* `-d` argument specifies the database provider, `mongodb` in this case.
{{ end }}
{{ if Tiered == "Yes" }}
* `--tiered` argument is used to create N-tiered solution where authentication server, UI and API layers are physically separated.
{{ end }}
````
You can also use variables in a text, adding **_Value** postfix to its key:
````
This document assumes that you prefer to use **{{ UI_Value }}** as the UI framework and **{{ DB_Value }}** as the database provider.
````
**IMPORTANT NOTICE**: Scriban uses "{{" and "}}" for syntax. Therefore, you must use escape blocks if you are going to use those in your document (an Angular document, for example). See [Scriban docs](<https://github.com/lunet-io/scriban/blob/master/doc/language.md#13-escape-block> ) for more information.
### 8- Creating the Navigation Document
Navigation document is the main menu of the documents page. It is located on the left side of the page. It is a `JSON` file. Take a look at the below sample navigation document to understand the structure.

53
docs/en/Settings.md

@ -40,6 +40,33 @@ ABP automatically discovers this class and registers the setting definitions.
* **Providers**: Can be used to restrict providers available for a particular setting (see the setting value providers section for more).
* **Properties**: A name/value collection to set custom properties about this setting those can be used later in the application code.
### Change Setting Definitions of a Depended Module
In some cases, you may want to change some properties of a settings defined in some other module that your application/module depends on. A setting definition provider can query and update setting definitions.
The following example gets a setting defined by the [Volo.Abp.Emailing](Emailing.md) package and changes its properties:
````csharp
public class MySettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
var smtpHost = context.GetOrNull("Abp.Mailing.Smtp.Host");
if (smtpHost != null)
{
smtpHost.DefaultValue = "mail.mydomain.com";
smtpHost.DisplayName =
new LocalizableString(
typeof(MyLocalizationResource),
"SmtpServer_DisplayName"
);
}
}
}
````
> Using constants for the setting names is a good practice and ABP packages do it. `Abp.Mailing.Smtp.Host` setting name is a constant defined by the `EmailSettingNames` class (in the `Volo.Abp.Emailing` namespace).
## Reading Setting Values
### ISettingProvider
@ -118,6 +145,30 @@ There are 5 pre-built setting value providers registered by the order below:
Global, Tenant and User setting value providers use the `ISettingStore` to read the value from the data source (see the section below).
### Setting Values in the Application Configuration
As mentioned in the previous section, `ConfigurationSettingValueProvider` reads the settings from the `IConfiguration` service, which can read values from the `appsettings.json` by default. So, the easiest way to configure setting values to define them in the `appsettings.json` file.
For example, you can configure [IEmailSender](Emailing.md) settings as shown below:
````json
{
"Settings": {
"Abp.Mailing.DefaultFromAddress": "noreply@mydomain.com",
"Abp.Mailing.DefaultFromDisplayName": "My Application",
"Abp.Mailing.Smtp.Host": "mail.mydomain.com",
"Abp.Mailing.Smtp.Port": "547",
"Abp.Mailing.Smtp.UserName": "myusername",
"Abp.Mailing.Smtp.Password": "mySecretPassW00rd",
"Abp.Mailing.Smtp.EnableSsl": "True"
}
}
````
Setting values should be configured under the `Settings` section as like in this example.
> `IConfiguration` is an .NET Core service and it can read values not only from the `appsettings.json`, but also from the environment, user secrets... etc. See [Microsoft's documentation]( https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/ ) for more.
### Custom Setting Value Providers
If you need to extend the setting system, you can define a class derived from the `SettingValueProvider` class. Example:
@ -165,7 +216,7 @@ This example adds it as the last item, so it will be the first value provider us
### ISettingStore
While a setting value provider is free to use any source to get the setting value, the`ISettingStore` service is the default source of the setting values. Global, Tenant and User setting value providers use it.
While a setting value provider is free to use any source to get the setting value, the `ISettingStore` service is the default source of the setting values. Global, Tenant and User setting value providers use it.
## ISettingEncryptionService

8
docs/en/Tutorials/Angular/Part-I.md

@ -631,10 +631,10 @@ Replace `book-list.component.html` content as shown below:
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>{{ data.name }}</td>
<td>{{ booksType[data.type] }}</td>
<td>{{ data.publishDate | date }}</td>
<td>{{ data.price }}</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>

12
docs/en/Tutorials/Angular/Part-II.md

@ -195,7 +195,7 @@ Open `book-list.component.html` and add the form in the body template of the mod
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {{ type }}</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
</select>
</div>
@ -461,10 +461,10 @@ Open the `book-list.component.html` and add modify the `p-table` as shown belo
</div>
</div>
</td>
<td>{{ data.name }}</td>
<td>{{ booksType[data.type] }}</td>
<td>{{ data.publishDate | date }}</td>
<td>{{ data.price }}</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
@ -483,7 +483,7 @@ Update the modal header to change the title based on the current operation:
```html
<ng-template #abpHeader>
<h3>{{ selectedBook.id ? 'Edit' : 'New Book' }}</h3>
<h3>{%{{{ selectedBook.id ? 'Edit' : 'New Book' }}}%}</h3>
</ng-template>
```

2
docs/en/docs-nav.json

@ -265,7 +265,7 @@
"items": [
{
"text": "PostgreSQL Integration",
"path": "Best-Practices/PostgreSQL-Integration.md"
"path": "EntityFrameworkCore-PostgreSQL-Integration.md"
}
]
},

BIN
docs/en/images/docs-section-ui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

8
docs/pt-BR/Tutorials/Angular/Part-I.md

@ -630,10 +630,10 @@ Substitua o `book-list.component.html`conteúdo conforme mostrado abaixo:
</ng-template>
<ng-template pTemplate="body" let-data>
<tr>
<td>{{ data.name }}</td>
<td>{{ booksType[data.type] }}</td>
<td>{{ data.publishDate | date }}</td>
<td>{{ data.price }}</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>

12
docs/pt-BR/Tutorials/Angular/Part-II.md

@ -195,7 +195,7 @@ Abra `book-list.component.html`e adicione o formulário no modelo de corpo do mo
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {{ type }}</option>
<option [ngValue]="booksType[type]" *ngFor="let type of bookTypeArr"> {%{{{ type }}}%}</option>
</select>
</div>
@ -455,10 +455,10 @@ Abra o `book-list.component.html` e adicione modifique o `p-table` como mostrado
</div>
</div>
</td>
<td>{{ data.name }}</td>
<td>{{ booksType[data.type] }}</td>
<td>{{ data.publishDate | date }}</td>
<td>{{ data.price }}</td>
<td>{%{{{ data.name }}}%}</td>
<td>{%{{{ booksType[data.type] }}}%}</td>
<td>{%{{{ data.publishDate | date }}}%}</td>
<td>{%{{{ data.price }}}%}</td>
</tr>
</ng-template>
</p-table>
@ -477,7 +477,7 @@ Atualize o cabeçalho modal para alterar o título com base na operação atual:
```html
<ng-template #abpHeader>
<h3>{{ selectedBook.id ? 'Edit' : 'New Book' }}</h3>
<h3>{%{{{ selectedBook.id ? 'Edit' : 'New Book' }}}%}</h3>
</ng-template>
```

2
docs/pt-BR/docs-nav.json

@ -249,7 +249,7 @@
"items": [
{
"text": "Integração do PostgreSQL",
"path": "Best-Practices/PostgreSQL-Integration.md"
"path": "EntityFrameworkCore-PostgreSQL-Integration.md"
}
]
},

7
docs/zh-Hans/Best-Practices/Entity-Framework-Core-Integration.md

@ -173,12 +173,12 @@ public static IQueryable<IdentityUser> IncludeDetails(
* **推荐** 推荐在仓储其他方法中使用 `IncludeDetails` 扩展方法, 就像上面的示例代码一样(参阅 FindByNormalizedUserNameAsync).
- **推荐** 覆盖具有 **子集合** 的聚合根仓储中的`IncludeDetails` 方法. 例如:
- **推荐** 覆盖具有 **子集合** 的聚合根仓储中的 `WithDetails` 方法. 例如:
````C#
protected override IQueryable<IdentityUser> IncludeDetails(IQueryable<IdentityUser> queryable)
public override IQueryable<IdentityUser> WithDetails()
{
return queryable.IncludeDetails(); //uses the extension method defined above
return GetQueryable().IncludeDetails(); // Uses the extension method defined above
}
````
@ -205,4 +205,3 @@ public class AbpIdentityEntityFrameworkCoreModule : AbpModule
}
}
````

2
docs/zh-Hans/Entities.md

@ -41,7 +41,7 @@ public class UserRole : Entity
public DateTime CreationTime { get; set; }
public Phone()
public UserRole()
{
}

0
docs/zh-Hans/Best-Practices/PostgreSQL-Integration.md → docs/zh-Hans/EntityFrameworkCore-PostgreSQL-Integration.md

10
docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md

@ -225,7 +225,7 @@ using Volo.Abp.Application.Services;
namespace Acme.BookStore
{
public interface IBookAppService :
IAsyncCrudAppService< //定义了CRUD方法
ICrudAppService< //定义了CRUD方法
BookDto, //用来展示书籍
Guid, //Book实体的主键
PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序
@ -238,8 +238,8 @@ namespace Acme.BookStore
````
* 框架定义应用程序服务的接口<u>不是必需的</u>. 但是,它被建议作为最佳实践.
* `IAsyncCrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法.
* `IAsyncCrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定.
* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法.
* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定.
#### BookAppService
@ -255,7 +255,7 @@ using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class BookAppService :
AsyncCrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
@ -268,7 +268,7 @@ namespace Acme.BookStore
}
````
* `BookAppService`继承了`AsyncCrudAppService<...>`.`AsyncCrudAppService<...>`实现了上面定义的CRUD方法.
* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法.
* `BookAppService`注入`IRepository <Book,Guid>`,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](../../Repositories.md)
* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作.

2
docs/zh-Hans/docs-nav.json

@ -254,7 +254,7 @@
"items": [
{
"text": "PostgreSQL 集成",
"path": "Best-Practices/PostgreSQL-Integration.md"
"path": "EntityFrameworkCore-PostgreSQL-Integration.md"
}
]
},

4
framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.Authentication.JwtBearer</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Authentication.JwtBearer</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
</ItemGroup>
</Project>

2
framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.Authentication.OAuth</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Authentication.OAuth</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

6
framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs

@ -14,7 +14,11 @@ namespace Volo.Abp.AspNetCore.Authentication.OAuth.Claims
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
var prop = userData.GetProperty(ValueType);
JsonElement prop;
if (!userData.TryGetProperty(ValueType, out prop))
return;
if (prop.ValueKind == JsonValueKind.Null)
{
return;

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

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.MultiTenancy</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.MultiTenancy</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

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

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.Client</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.Client</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

1
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs

@ -7,6 +7,7 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.DynamicProxying;
using Volo.Abp.Threading;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.Client

4
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs

@ -55,9 +55,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
private Dictionary<string, string> GetResourceOrNull()
{
var applicationConfigurationDto = AsyncHelper.RunSync(
() => _applicationConfigurationClient.GetAsync()
);
var applicationConfigurationDto = AsyncHelper.RunSync(() => _applicationConfigurationClient.GetAsync());
var resource = applicationConfigurationDto
.Localization.Values

4
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs

@ -21,10 +21,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
return configuration.Auth.GrantedPolicies.ContainsKey(name);
}
public Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
public async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)
{
/* This provider always works for the current principal. */
return IsGrantedAsync(name);
return await IsGrantedAsync(name);
}
}
}

50
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteTenantStore.cs

@ -85,12 +85,58 @@ namespace Volo.Abp.AspNetCore.Mvc.Client
public TenantConfiguration Find(string name)
{
return AsyncHelper.RunSync(() => FindAsync(name));
var cacheKey = CreateCacheKey(name);
var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext != null && httpContext.Items[cacheKey] is TenantConfiguration tenantConfiguration)
{
return tenantConfiguration;
}
tenantConfiguration = Cache.GetOrAdd(
cacheKey,
() => AsyncHelper.RunSync(async () => CreateTenantConfiguration(await Proxy.Service.FindTenantByNameAsync(name))),
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(5) //TODO: Should be configurable.
}
);
if (httpContext != null)
{
httpContext.Items[cacheKey] = tenantConfiguration;
}
return tenantConfiguration;
}
public TenantConfiguration Find(Guid id)
{
return AsyncHelper.RunSync(() => FindAsync(id));
var cacheKey = CreateCacheKey(id);
var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext != null && httpContext.Items[cacheKey] is TenantConfiguration tenantConfiguration)
{
return tenantConfiguration;
}
tenantConfiguration = Cache.GetOrAdd(
cacheKey,
() => AsyncHelper.RunSync(async () => CreateTenantConfiguration(await Proxy.Service.FindTenantByIdAsync(id))),
() => new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(5) //TODO: Should be configurable.
}
);
if (httpContext != null)
{
httpContext.Items[cacheKey] = tenantConfiguration;
}
return tenantConfiguration;
}
protected virtual TenantConfiguration CreateTenantConfiguration(FindTenantResultDto tenantResultDto)

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

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.Contracts</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.Contracts</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Bootstrap</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Bootstrap</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo.Abp.AspNetCore.Mvc.UI.Bundling.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Bundling</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Bundling</PackageId>
<IsPackable>true</IsPackable>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/cs.json

@ -6,6 +6,7 @@
"Switch": "změnit",
"Name": "Název",
"SwitchTenantHint": "Nechte pole prázdné ke změně na hostitele.",
"SwitchTenant": "Změnit tenant",
"NotSelected": "Nevybrán"
}
}

1
framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/pt-BR.json

@ -6,6 +6,7 @@
"Switch": "trocar",
"Name": "Nome",
"SwitchTenantHint": "Deixe o campo de nome em branco para acessar o host.",
"SwitchTenant": "Alterar Inquilino",
"NotSelected": "Não selecionado"
}
}

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo.Abp.AspNetCore.Mvc.UI.Packages.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Packages</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Packages</PackageId>
<IsPackable>true</IsPackable>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

4
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Views/Error/Default.cshtml

@ -2,8 +2,8 @@
@using System.Collections.Generic
@using Localization.Resources.AbpUi
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Error
@model AbpErrorPageModel
@using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Views.Error
@model AbpErrorViewModel
@inject IHtmlLocalizer<AbpUiResource> L
@{
var errorMessage = Model.ErrorInfo.Message;

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI.Widgets</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI.Widgets</PackageId>
<IsPackable>true</IsPackable>

3
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo.Abp.AspNetCore.Mvc.UI.csproj

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc.UI</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc.UI</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

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

@ -3,7 +3,8 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<AssemblyName>Volo.Abp.AspNetCore.Mvc</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.Mvc</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
@ -24,9 +25,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.0.0-preview8.19405.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
</ItemGroup>
</Project>

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

@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Hosting;
using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DependencyInjection;
@ -64,6 +65,14 @@ namespace Volo.Abp.AspNetCore.Mvc
options.IgnoredInterfaces.AddIfNotContains(typeof(IActionFilter));
});
context.Services.PostConfigure<AbpAspNetCoreMvcOptions>(options =>
{
if (options.MinifyGeneratedScript == null)
{
options.MinifyGeneratedScript = context.Services.GetHostingEnvironment().IsProduction();
}
});
var mvcCoreBuilder = context.Services.AddMvcCore();
context.Services.ExecutePreConfiguredActions(mvcCoreBuilder);

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs

@ -4,6 +4,8 @@ namespace Volo.Abp.AspNetCore.Mvc
{
public class AbpAspNetCoreMvcOptions
{
public bool? MinifyGeneratedScript { get; set; }
public AbpConventionalControllerOptions ConventionalControllers { get; }
public AbpAspNetCoreMvcOptions()

29
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization;
using Volo.Abp.Features;
@ -34,7 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
IAuthorizationService authorizationService,
ICurrentUser currentUser,
ISettingProvider settingProvider,
SettingDefinitionManager settingDefinitionManager,
ISettingDefinitionManager settingDefinitionManager,
IFeatureDefinitionManager featureDefinitionManager,
ILanguageProvider languageProvider)
{
@ -76,23 +77,35 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
protected virtual async Task<ApplicationAuthConfigurationDto> GetAuthConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetAuthConfigAsync()");
var authConfig = new ApplicationAuthConfigurationDto();
foreach (var policyName in await _abpAuthorizationPolicyProvider.GetPoliciesNamesAsync())
var policyNames = await _abpAuthorizationPolicyProvider.GetPoliciesNamesAsync();
Logger.LogDebug($"GetPoliciesNamesAsync returns {policyNames.Count} items.");
foreach (var policyName in policyNames)
{
authConfig.Policies[policyName] = true;
Logger.LogDebug($"_authorizationService.IsGrantedAsync? {policyName}");
if (await _authorizationService.IsGrantedAsync(policyName))
{
authConfig.GrantedPolicies[policyName] = true;
}
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetAuthConfigAsync()");
return authConfig;
}
protected virtual async Task<ApplicationLocalizationConfigurationDto> GetLocalizationConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()");
var localizationConfig = new ApplicationLocalizationConfigurationDto();
localizationConfig.Languages.AddRange(await _languageProvider.GetLanguagesAsync());
@ -115,12 +128,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
localizationConfig.CurrentCulture = GetCurrentCultureInfo();
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetLocalizationConfigAsync()");
return localizationConfig;
}
private static CurrentCultureDto GetCurrentCultureInfo()
{
return new CurrentCultureDto
return new CurrentCultureDto
{
Name = CultureInfo.CurrentUICulture.Name,
DisplayName = CultureInfo.CurrentUICulture.DisplayName,
@ -145,6 +160,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
private async Task<ApplicationSettingConfigurationDto> GetSettingConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetSettingConfigAsync()");
var result = new ApplicationSettingConfigurationDto
{
Values = new Dictionary<string, string>()
@ -160,11 +177,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
result.Values[settingDefinition.Name] = await _settingProvider.GetOrNullAsync(settingDefinition.Name);
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetSettingConfigAsync()");
return result;
}
protected virtual async Task<ApplicationFeatureConfigurationDto> GetFeaturesConfigAsync()
{
Logger.LogDebug("Executing AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()");
var result = new ApplicationFeatureConfigurationDto();
foreach (var featureDefinition in _featureDefinitionManager.GetAll())
@ -177,6 +198,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
result.Values[featureDefinition.Name] = await FeatureChecker.GetOrNullAsync(featureDefinition.Name);
}
Logger.LogDebug("Executed AbpApplicationConfigurationAppService.GetFeaturesConfigAsync()");
return result;
}
}

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationController.cs

@ -15,9 +15,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
}
[HttpGet]
public Task<ApplicationConfigurationDto> GetAsync()
public async Task<ApplicationConfigurationDto> GetAsync()
{
return _applicationConfigurationAppService.GetAsync();
return await _applicationConfigurationAppService.GetAsync();
}
}
}

23
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationScriptController.cs

@ -1,10 +1,11 @@
using System.Diagnostics;
using System.Text;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Http;
using Volo.Abp.Json;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
@ -15,23 +16,31 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
private readonly IAbpApplicationConfigurationAppService _configurationAppService;
private readonly IJsonSerializer _jsonSerializer;
private readonly AbpAspNetCoreMvcOptions _options;
private readonly IJavascriptMinifier _javascriptMinifier;
public AbpApplicationConfigurationScriptController(
IAbpApplicationConfigurationAppService configurationAppService,
IJsonSerializer jsonSerializer)
IJsonSerializer jsonSerializer,
IOptions<AbpAspNetCoreMvcOptions> options,
IJavascriptMinifier javascriptMinifier)
{
_configurationAppService = configurationAppService;
_jsonSerializer = jsonSerializer;
_options = options.Value;
_javascriptMinifier = javascriptMinifier;
}
[HttpGet]
[Produces(MimeTypes.Application.Javascript, MimeTypes.Text.Plain)]
public async Task<ActionResult> Get()
{
var script = CreateAbpExtendScript(await _configurationAppService.GetAsync());
return Content(
CreateAbpExtendScript(
await _configurationAppService.GetAsync()
),
_options.MinifyGeneratedScript == true
? _javascriptMinifier.Minify(script)
: script,
MimeTypes.Application.Javascript
);
}
@ -42,7 +51,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
script.AppendLine("(function(){");
script.AppendLine();
script.AppendLine($"$.extend(true, abp, {_jsonSerializer.Serialize(config, indented: Debugger.IsAttached)})");
script.AppendLine($"$.extend(true, abp, {_jsonSerializer.Serialize(config, indented: true)})");
script.AppendLine();
script.AppendLine("abp.event.trigger('abp.configurationInitialized');");
script.AppendLine();

13
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs

@ -171,6 +171,13 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
{
RemoveEmptySelectors(controller.Selectors);
var controllerType = controller.ControllerType.AsType();
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(controllerType.GetTypeInfo());
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(controllerType))
{
return;
}
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
{
return;
@ -188,6 +195,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions
{
RemoveEmptySelectors(action.Selectors);
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod))
{
return;
}
if (!action.Selectors.Any())
{
AddAbpServiceSelector(rootPath, controllerName, action, configuration);

20
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxyScriptController.cs

@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Volo.Abp.Auditing;
using Volo.Abp.Http;
using Volo.Abp.Http.ProxyScripting;
using Volo.Abp.Minify.Scripts;
namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
{
@ -11,10 +13,16 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
public class AbpServiceProxyScriptController : AbpController
{
private readonly IProxyScriptManager _proxyScriptManager;
private readonly AbpAspNetCoreMvcOptions _options;
private readonly IJavascriptMinifier _javascriptMinifier;
public AbpServiceProxyScriptController(IProxyScriptManager proxyScriptManager)
public AbpServiceProxyScriptController(IProxyScriptManager proxyScriptManager,
IOptions<AbpAspNetCoreMvcOptions> options,
IJavascriptMinifier javascriptMinifier)
{
_proxyScriptManager = proxyScriptManager;
_options = options.Value;
_javascriptMinifier = javascriptMinifier;
}
[HttpGet]
@ -22,7 +30,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
public ActionResult GetAll(ServiceProxyGenerationModel model)
{
model.Normalize();
return Content(_proxyScriptManager.GetScript(model.CreateOptions()), MimeTypes.Application.Javascript);
var script = _proxyScriptManager.GetScript(model.CreateOptions());
return Content(
_options.MinifyGeneratedScript == true
? _javascriptMinifier.Minify(script)
: script,
MimeTypes.Application.Javascript
);
}
}
}

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs

@ -11,8 +11,6 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
public bool UseCache { get; set; }
public bool Minify { get; set; }
public string Modules { get; set; }
public string Controllers { get; set; }
@ -34,7 +32,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting
public ProxyScriptingModel CreateOptions()
{
var options = new ProxyScriptingModel(Type, UseCache, Minify);
var options = new ProxyScriptingModel(Type, UseCache);
if (!Modules.IsNullOrEmpty())
{

4
framework/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore.TestBase</AssemblyName>
<PackageId>Volo.Abp.AspNetCore.TestBase</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.0" />
</ItemGroup>
</Project>

4
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs

@ -25,7 +25,9 @@ namespace Microsoft.AspNetCore.RequestLocalization
{
var middleware = new RequestLocalizationMiddleware(
next,
new OptionsWrapper<RequestLocalizationOptions>(_requestLocalizationOptionsProvider.GetLocalizationOptions()),
new OptionsWrapper<RequestLocalizationOptions>(
await _requestLocalizationOptionsProvider.GetLocalizationOptionsAsync()
),
_loggerFactory
);

79
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs

@ -2,67 +2,76 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
using Nito.AsyncEx;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
namespace Microsoft.AspNetCore.RequestLocalization
{
public class DefaultAbpRequestLocalizationOptionsProvider : IAbpRequestLocalizationOptionsProvider, ISingletonDependency
{
private readonly IServiceProvider _serviceProvider;
private Lazy<RequestLocalizationOptions> _lazyRequestLocalizationOptions;
private readonly IServiceScopeFactory _serviceProviderFactory;
private readonly SemaphoreSlim _syncSemaphore;
private Action<RequestLocalizationOptions> _optionsAction;
private RequestLocalizationOptions _requestLocalizationOptions;
public DefaultAbpRequestLocalizationOptionsProvider(IServiceProvider serviceProvider)
public DefaultAbpRequestLocalizationOptionsProvider(IServiceScopeFactory serviceProviderFactory)
{
_serviceProvider = serviceProvider;
_serviceProviderFactory = serviceProviderFactory;
_syncSemaphore = new SemaphoreSlim(1, 1);
}
public void InitLocalizationOptions(Action<RequestLocalizationOptions> optionsAction = null)
{
_lazyRequestLocalizationOptions = new Lazy<RequestLocalizationOptions>(() =>
_optionsAction = optionsAction;
}
public async Task<RequestLocalizationOptions> GetLocalizationOptionsAsync()
{
if (_requestLocalizationOptions == null)
{
using (var serviceScope = _serviceProvider.CreateScope())
using (await _syncSemaphore.LockAsync())
{
var languageProvider = serviceScope.ServiceProvider.GetRequiredService<ILanguageProvider>();
var settingProvider = serviceScope.ServiceProvider.GetRequiredService<ISettingProvider>();
using (var serviceScope = _serviceProviderFactory.CreateScope())
{
var languageProvider = serviceScope.ServiceProvider.GetRequiredService<ILanguageProvider>();
var settingProvider = serviceScope.ServiceProvider.GetRequiredService<ISettingProvider>();
var languages = AsyncHelper.RunSync(languageProvider.GetLanguagesAsync);
var defaultLanguage = AsyncHelper.RunSync(() =>
settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage));
var languages = await languageProvider.GetLanguagesAsync();
var defaultLanguage = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
var options = !languages.Any()
? new RequestLocalizationOptions()
: new RequestLocalizationOptions
{
DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages),
var options = !languages.Any()
? new RequestLocalizationOptions()
: new RequestLocalizationOptions
{
DefaultRequestCulture = DefaultGetRequestCulture(defaultLanguage, languages),
SupportedCultures = languages
.Select(l => l.CultureName)
.Distinct()
.Select(c => new CultureInfo(c))
.ToArray(),
SupportedCultures = languages
.Select(l => l.CultureName)
.Distinct()
.Select(c => new CultureInfo(c))
.ToArray(),
SupportedUICultures = languages
.Select(l => l.UiCultureName)
.Distinct()
.Select(c => new CultureInfo(c))
.ToArray()
};
SupportedUICultures = languages
.Select(l => l.UiCultureName)
.Distinct()
.Select(c => new CultureInfo(c))
.ToArray()
};
optionsAction?.Invoke(options);
return options;
_optionsAction?.Invoke(options);
_requestLocalizationOptions = options;
}
}
}, true);
}
}
public RequestLocalizationOptions GetLocalizationOptions()
{
return _lazyRequestLocalizationOptions.Value;
return _requestLocalizationOptions;
}
private static RequestCulture DefaultGetRequestCulture(string defaultLanguage, IReadOnlyList<LanguageInfo> languages)

3
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/IAbpRequestLocalizationOptionsProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
namespace Microsoft.AspNetCore.RequestLocalization
@ -7,6 +8,6 @@ namespace Microsoft.AspNetCore.RequestLocalization
{
void InitLocalizationOptions(Action<RequestLocalizationOptions> optionsAction = null);
RequestLocalizationOptions GetLocalizationOptions();
Task<RequestLocalizationOptions> GetLocalizationOptionsAsync();
}
}

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

@ -3,7 +3,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Volo.Abp.AspNetCore</AssemblyName>
<PackageId>Volo.Abp.AspNetCore</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>

27
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs

@ -18,33 +18,6 @@ namespace Volo.Abp.Auditing
_auditingManager = auditingManager;
}
public override void Intercept(IAbpMethodInvocation invocation)
{
if (!ShouldIntercept(invocation, out var auditLog, out var auditLogAction))
{
invocation.Proceed();
return;
}
var stopwatch = Stopwatch.StartNew();
try
{
invocation.Proceed();
}
catch (Exception ex)
{
auditLog.Exceptions.Add(ex);
throw;
}
finally
{
stopwatch.Stop();
auditLogAction.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);
auditLog.Actions.Add(auditLogAction);
}
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
if (!ShouldIntercept(invocation, out var auditLog, out var auditLogAction))

15
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingManager.cs

@ -120,16 +120,6 @@ namespace Volo.Abp.Auditing
}
}
protected virtual void Save(DisposableSaveHandle saveHandle)
{
BeforeSave(saveHandle);
if (ShouldSave(saveHandle.AuditLog))
{
_auditingStore.Save(saveHandle.AuditLog);
}
}
protected bool ShouldSave(AuditLogInfo auditLog)
{
if (!auditLog.Actions.Any() && !auditLog.EntityChanges.Any())
@ -165,11 +155,6 @@ namespace Volo.Abp.Auditing
await _auditingManager.SaveAsync(this);
}
public void Save()
{
_auditingManager.Save(this);
}
public void Dispose()
{
_scope.Dispose();

2
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditLogSaveHandle.cs

@ -5,8 +5,6 @@ namespace Volo.Abp.Auditing
{
public interface IAuditLogSaveHandle : IDisposable
{
void Save();
Task SaveAsync();
}
}

2
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs

@ -4,8 +4,6 @@ namespace Volo.Abp.Auditing
{
public interface IAuditingStore
{
void Save(AuditLogInfo auditInfo);
Task SaveAsync(AuditLogInfo auditInfo);
}
}

7
framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs

@ -15,14 +15,9 @@ namespace Volo.Abp.Auditing
Logger = NullLogger<SimpleLogAuditingStore>.Instance;
}
public void Save(AuditLogInfo auditInfo)
{
Logger.LogInformation(auditInfo.ToString());
}
public Task SaveAsync(AuditLogInfo auditInfo)
{
Save(auditInfo);
Logger.LogInformation(auditInfo.ToString());
return Task.FromResult(0);
}
}

28
framework/src/Volo.Abp.Authorization/Microsoft/AspNetCore/Authorization/AbpAuthorizationServiceExtensions.cs

@ -7,52 +7,54 @@ namespace Microsoft.AspNetCore.Authorization
{
public static class AbpAuthorizationServiceExtensions
{
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, string policyName)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, string policyName)
{
return AuthorizeAsync(
return await AuthorizeAsync(
authorizationService,
authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
null,
policyName
);
}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, IAuthorizationRequirement requirement)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, IAuthorizationRequirement requirement)
{
return authorizationService.AuthorizeAsync(
return await authorizationService.AuthorizeAsync(
authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
resource,
requirement
);
}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, AuthorizationPolicy policy)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, AuthorizationPolicy policy)
{
return authorizationService.AuthorizeAsync(
return await authorizationService.AuthorizeAsync(
authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
resource,
policy
);
}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, AuthorizationPolicy policy)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, AuthorizationPolicy policy)
{
return AuthorizeAsync(authorizationService, authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
return await AuthorizeAsync(
authorizationService,
null,
policy
);
}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, IEnumerable<IAuthorizationRequirement> requirements)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
return authorizationService.AuthorizeAsync(
return await authorizationService.AuthorizeAsync(
authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
resource,
requirements
);
}
public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, string policyName)
public static async Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService authorizationService, object resource, string policyName)
{
return authorizationService.AuthorizeAsync(
return await authorizationService.AuthorizeAsync(
authorizationService.AsAbpAuthorizationService().CurrentPrincipal,
resource,
policyName

2
framework/src/Volo.Abp.Authorization/Volo.Abp.Authorization.csproj

@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

7
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AuthorizationInterceptor.cs

@ -1,7 +1,6 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Threading;
namespace Volo.Abp.Authorization
{
@ -14,12 +13,6 @@ namespace Volo.Abp.Authorization
_methodInvocationAuthorizationService = methodInvocationAuthorizationService;
}
public override void Intercept(IAbpMethodInvocation invocation)
{
AsyncHelper.RunSync(() => AuthorizeAsync(invocation));
invocation.Proceed();
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
await AuthorizeAsync(invocation);

4
framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionChecker.cs

@ -27,9 +27,9 @@ namespace Volo.Abp.Authorization.Permissions
PermissionValueProviderManager = permissionValueProviderManager;
}
public virtual Task<bool> IsGrantedAsync(string name)
public virtual async Task<bool> IsGrantedAsync(string name)
{
return IsGrantedAsync(PrincipalAccessor.Principal, name);
return await IsGrantedAsync(PrincipalAccessor.Principal, name);
}
public virtual async Task<bool> IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name)

2
framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs

@ -89,7 +89,7 @@ namespace Autofac.Builder
foreach (var interceptor in interceptors)
{
registrationBuilder.InterceptedBy(
typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptor)
typeof(AbpAsyncDeterminationInterceptor<>).MakeGenericType(interceptor)
);
}

2
framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj

@ -16,7 +16,7 @@
<ItemGroup>
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" />
<PackageReference Include="Autofac" Version="4.9.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

3
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobsAbstractionsModule.cs

@ -23,7 +23,8 @@ namespace Volo.Abp.BackgroundJobs
services.OnRegistred(context =>
{
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)))
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)) ||
ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IAsyncBackgroundJob<>)))
{
jobTypes.Add(context.ImplementationType);
}

20
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AsyncBackgroundJob.cs

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Volo.Abp.BackgroundJobs
{
public abstract class AsyncBackgroundJob<TArgs> : IAsyncBackgroundJob<TArgs>
{
//TODO: Add UOW, Localization and other useful properties..?
public ILogger<AsyncBackgroundJob<TArgs>> Logger { get; set; }
protected AsyncBackgroundJob()
{
Logger = NullLogger<AsyncBackgroundJob<TArgs>>.Instance;
}
public abstract Task ExecuteAsync(TArgs args);
}
}

7
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobArgsHelper.cs

@ -13,7 +13,8 @@ namespace Volo.Abp.BackgroundJobs
continue;
}
if (@interface.GetGenericTypeDefinition() != typeof(IBackgroundJob<>))
if (@interface.GetGenericTypeDefinition() != typeof(IBackgroundJob<>) &&
@interface.GetGenericTypeDefinition() != typeof(IAsyncBackgroundJob<>))
{
continue;
}
@ -27,7 +28,9 @@ namespace Volo.Abp.BackgroundJobs
return genericArgs[0];
}
throw new AbpException($"Could not find type of the job args. Ensure that given type implements the {typeof(IBackgroundJob<>).AssemblyQualifiedName} interface. Given job type: {jobType.AssemblyQualifiedName}");
throw new AbpException($"Could not find type of the job args. " +
$"Ensure that given type implements the {typeof(IBackgroundJob<>).AssemblyQualifiedName} or {typeof(IAsyncBackgroundJob<>).AssemblyQualifiedName} interface. " +
$"Given job type: {jobType.AssemblyQualifiedName}");
}
}
}

17
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs

@ -2,7 +2,9 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs
{
@ -27,15 +29,24 @@ namespace Volo.Abp.BackgroundJobs
throw new AbpException("The job type is not registered to DI: " + context.JobType);
}
var jobExecuteMethod = context.JobType.GetMethod(nameof(IBackgroundJob<object>.Execute));
var jobExecuteMethod = context.JobType.GetMethod(nameof(IBackgroundJob<object>.Execute)) ??
context.JobType.GetMethod(nameof(IAsyncBackgroundJob<object>.ExecuteAsync));
if (jobExecuteMethod == null)
{
throw new AbpException($"Given job type does not implement {typeof(IBackgroundJob<>).Name}. The job type was: " + context.JobType);
throw new AbpException($"Given job type does not implement {typeof(IBackgroundJob<>).Name} or {typeof(IAsyncBackgroundJob<>).Name}. " +
"The job type was: " + context.JobType);
}
try
{
jobExecuteMethod.Invoke(job, new[] { context.JobArgs });
if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob<object>.ExecuteAsync))
{
AsyncHelper.RunSync(() => (Task) jobExecuteMethod.Invoke(job, new[] {context.JobArgs}));
}
else
{
jobExecuteMethod.Invoke(job, new[] { context.JobArgs });
}
}
catch (Exception ex)
{

16
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/IAsyncBackgroundJob.cs

@ -0,0 +1,16 @@
using System.Threading.Tasks;
namespace Volo.Abp.BackgroundJobs
{
/// <summary>
/// Defines interface of a background job.
/// </summary>
public interface IAsyncBackgroundJob<in TArgs>
{
/// <summary>
/// Executes the job with the <see cref="args"/>.
/// </summary>
/// <param name="args">Job arguments.</param>
Task ExecuteAsync(TArgs args);
}
}

5
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/IJobQueueManager.cs

@ -1,9 +1,10 @@
using Volo.Abp.Threading;
using System.Threading.Tasks;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundJobs.RabbitMQ
{
public interface IJobQueueManager : IRunnable
{
IJobQueue<TArgs> Get<TArgs>();
Task<IJobQueue<TArgs>> GetAsync<TArgs>();
}
}

31
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueueManager.cs

@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
@ -17,6 +18,8 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
protected AbpBackgroundJobOptions Options { get; }
protected SemaphoreSlim SyncSemaphore { get; }
public JobQueueManager(
IOptions<AbpBackgroundJobOptions> options,
IServiceProvider serviceProvider)
@ -24,6 +27,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
ServiceProvider = serviceProvider;
Options = options.Value;
JobQueues = new ConcurrentDictionary<string, IRunnable>();
SyncSemaphore = new SemaphoreSlim(1, 1);
}
public async Task StartAsync(CancellationToken cancellationToken = default)
@ -51,20 +55,31 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
JobQueues.Clear();
}
public IJobQueue<TArgs> Get<TArgs>()
public async Task<IJobQueue<TArgs>> GetAsync<TArgs>()
{
var jobConfiguration = Options.GetJob(typeof(TArgs));
return (IJobQueue<TArgs>)JobQueues.GetOrAdd(jobConfiguration.JobName, _ =>
if (JobQueues.TryGetValue(jobConfiguration.JobName, out var jobQueue))
{
return (IJobQueue<TArgs>)jobQueue;
}
using (await SyncSemaphore.LockAsync())
{
var jobQueue = (IRunnable) ServiceProvider
.GetRequiredService(typeof(IJobQueue<>)
.MakeGenericType(typeof(TArgs)));
if (JobQueues.TryGetValue(jobConfiguration.JobName, out jobQueue))
{
return (IJobQueue<TArgs>)jobQueue;
}
AsyncHelper.RunSync(() => jobQueue.StartAsync());
jobQueue = (IJobQueue<TArgs>)ServiceProvider
.GetRequiredService(typeof(IJobQueue<>).MakeGenericType(typeof(TArgs)));
return jobQueue;
});
await jobQueue.StartAsync();
JobQueues.TryAdd(jobConfiguration.JobName, jobQueue);
return (IJobQueue<TArgs>)jobQueue;
}
}
}
}

7
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/RabbitMqBackgroundJobManager.cs

@ -14,14 +14,13 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ
_jobQueueManager = jobQueueManager;
}
public Task<string> EnqueueAsync<TArgs>(
public async Task<string> EnqueueAsync<TArgs>(
TArgs args,
BackgroundJobPriority priority = BackgroundJobPriority.Normal,
TimeSpan? delay = null)
{
return _jobQueueManager
.Get<TArgs>()
.EnqueueAsync(args, priority, delay);
var jobQueue = await _jobQueueManager.GetAsync<TArgs>();
return await jobQueue.EnqueueAsync(args, priority, delay);
}
}
}

6
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobWorker.cs

@ -36,7 +36,7 @@ namespace Volo.Abp.BackgroundJobs
{
var store = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var waitingJobs = store.GetWaitingJobs(WorkerOptions.MaxJobFetchCount);
var waitingJobs = AsyncHelper.RunSync(() => store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount));
if (!waitingJobs.Any())
{
@ -62,7 +62,7 @@ namespace Volo.Abp.BackgroundJobs
{
jobExecuter.Execute(context);
store.Delete(jobInfo.Id);
AsyncHelper.RunSync(() => store.DeleteAsync(jobInfo.Id));
}
catch (BackgroundJobExecutionException)
{
@ -94,7 +94,7 @@ namespace Volo.Abp.BackgroundJobs
{
try
{
store.Update(jobInfo);
AsyncHelper.RunSync(() => store.UpdateAsync(jobInfo));
}
catch (Exception updateEx)
{

34
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/IBackgroundJobStore.cs

@ -9,13 +9,6 @@ namespace Volo.Abp.BackgroundJobs
/// </summary>
public interface IBackgroundJobStore
{
/// <summary>
/// Gets a BackgroundJobInfo based on the given jobId.
/// </summary>
/// <param name="jobId">The Job Unique Identifier.</param>
/// <returns>The BackgroundJobInfo object.</returns>
BackgroundJobInfo Find(Guid jobId);
/// <summary>
/// Gets a BackgroundJobInfo based on the given jobId.
/// </summary>
@ -23,27 +16,12 @@ namespace Volo.Abp.BackgroundJobs
/// <returns>The BackgroundJobInfo object.</returns>
Task<BackgroundJobInfo> FindAsync(Guid jobId);
/// <summary>
/// Inserts a background job.
/// </summary>
/// <param name="jobInfo">Job information.</param>
void Insert(BackgroundJobInfo jobInfo);
/// <summary>
/// Inserts a background job.
/// </summary>
/// <param name="jobInfo">Job information.</param>
Task InsertAsync(BackgroundJobInfo jobInfo);
/// <summary>
/// Gets waiting jobs. It should get jobs based on these:
/// Conditions: !IsAbandoned And NextTryTime &lt;= Clock.Now.
/// Order by: Priority DESC, TryCount ASC, NextTryTime ASC.
/// Maximum result: <paramref name="maxResultCount"/>.
/// </summary>
/// <param name="maxResultCount">Maximum result count.</param>
List<BackgroundJobInfo> GetWaitingJobs(int maxResultCount);
/// <summary>
/// Gets waiting jobs. It should get jobs based on these:
/// Conditions: !IsAbandoned And NextTryTime &lt;= Clock.Now.
@ -53,24 +31,12 @@ namespace Volo.Abp.BackgroundJobs
/// <param name="maxResultCount">Maximum result count.</param>
Task<List<BackgroundJobInfo>> GetWaitingJobsAsync(int maxResultCount);
/// <summary>
/// Deletes a job.
/// </summary>
/// <param name="jobId">The Job Unique Identifier.</param>
void Delete(Guid jobId);
/// <summary>
/// Deletes a job.
/// </summary>
/// <param name="jobId">The Job Unique Identifier.</param>
Task DeleteAsync(Guid jobId);
/// <summary>
/// Updates a job.
/// </summary>
/// <param name="jobInfo">Job information.</param>
void Update(BackgroundJobInfo jobInfo);
/// <summary>
/// Updates a job.
/// </summary>

35
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs

@ -23,21 +23,11 @@ namespace Volo.Abp.BackgroundJobs
_jobs = new ConcurrentDictionary<Guid, BackgroundJobInfo>();
}
public BackgroundJobInfo Find(Guid jobId)
{
return _jobs.GetOrDefault(jobId);
}
public virtual Task<BackgroundJobInfo> FindAsync(Guid jobId)
{
return Task.FromResult(_jobs.GetOrDefault(jobId));
}
public void Insert(BackgroundJobInfo jobInfo)
{
_jobs[jobInfo.Id] = jobInfo;
}
public virtual Task InsertAsync(BackgroundJobInfo jobInfo)
{
_jobs[jobInfo.Id] = jobInfo;
@ -45,17 +35,6 @@ namespace Volo.Abp.BackgroundJobs
return Task.FromResult(0);
}
public List<BackgroundJobInfo> GetWaitingJobs(int maxResultCount)
{
return _jobs.Values
.Where(t => !t.IsAbandoned && t.NextTryTime <= Clock.Now)
.OrderByDescending(t => t.Priority)
.ThenBy(t => t.TryCount)
.ThenBy(t => t.NextTryTime)
.Take(maxResultCount)
.ToList();
}
public virtual Task<List<BackgroundJobInfo>> GetWaitingJobsAsync(int maxResultCount)
{
var waitingJobs = _jobs.Values
@ -69,10 +48,6 @@ namespace Volo.Abp.BackgroundJobs
return Task.FromResult(waitingJobs);
}
public void Delete(Guid jobId)
{
_jobs.TryRemove(jobId, out _);
}
public virtual Task DeleteAsync(Guid jobId)
{
@ -80,15 +55,7 @@ namespace Volo.Abp.BackgroundJobs
return Task.FromResult(0);
}
public void Update(BackgroundJobInfo jobInfo)
{
if (jobInfo.IsAbandoned)
{
DeleteAsync(jobInfo.Id);
}
}
public virtual Task UpdateAsync(BackgroundJobInfo jobInfo)
{
if (jobInfo.IsAbandoned)

2
framework/src/Volo.Abp.Caching/Volo.Abp.Caching.csproj

@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
</ItemGroup>
<ItemGroup>

1
framework/src/Volo.Abp.Castle.Core/Volo.Abp.Castle.Core.csproj

@ -15,6 +15,7 @@
<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.4.0" />
<PackageReference Include="Castle.Core.AsyncInterceptor" Version="2.0.21-alpha" />
</ItemGroup>
<ItemGroup>

2
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/AbpCastleCoreModule.cs

@ -8,7 +8,7 @@ namespace Volo.Abp.Castle
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(typeof(CastleAbpInterceptorAdapter<>));
context.Services.AddTransient(typeof(AbpAsyncDeterminationInterceptor<>));
}
}
}

15
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/AbpAsyncDeterminationInterceptor.cs

@ -0,0 +1,15 @@
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Castle.DynamicProxy
{
public class AbpAsyncDeterminationInterceptor<TInterceptor> : AsyncDeterminationInterceptor
where TInterceptor : IAbpInterceptor
{
public AbpAsyncDeterminationInterceptor(TInterceptor abpInterceptor)
: base(new CastleAsyncAbpInterceptorAdapter<TInterceptor>(abpInterceptor))
{
}
}
}

89
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs

@ -1,89 +0,0 @@
using System.Reflection;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Threading;
namespace Volo.Abp.Castle.DynamicProxy
{
public class CastleAbpInterceptorAdapter<TInterceptor> : IInterceptor
where TInterceptor : IAbpInterceptor
{
private static readonly MethodInfo MethodExecuteWithoutReturnValueAsync =
typeof(CastleAbpInterceptorAdapter<TInterceptor>)
.GetMethod(
nameof(ExecuteWithoutReturnValueAsync),
BindingFlags.NonPublic | BindingFlags.Instance
);
private static readonly MethodInfo MethodExecuteWithReturnValueAsync =
typeof(CastleAbpInterceptorAdapter<TInterceptor>)
.GetMethod(
nameof(ExecuteWithReturnValueAsync),
BindingFlags.NonPublic | BindingFlags.Instance
);
private readonly TInterceptor _abpInterceptor;
public CastleAbpInterceptorAdapter(TInterceptor abpInterceptor)
{
_abpInterceptor = abpInterceptor;
}
public void Intercept(IInvocation invocation)
{
var proceedInfo = invocation.CaptureProceedInfo();
var method = invocation.MethodInvocationTarget ?? invocation.Method;
if (method.IsAsync())
{
InterceptAsyncMethod(invocation, proceedInfo);
}
else
{
InterceptSyncMethod(invocation, proceedInfo);
}
}
private void InterceptSyncMethod(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
_abpInterceptor.Intercept(new CastleAbpMethodInvocationAdapter(invocation, proceedInfo));
}
private void InterceptAsyncMethod(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = MethodExecuteWithoutReturnValueAsync
.Invoke(this, new object[] { invocation, proceedInfo });
}
else
{
invocation.ReturnValue = MethodExecuteWithReturnValueAsync
.MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0])
.Invoke(this, new object[] {invocation, proceedInfo});
}
}
private async Task ExecuteWithoutReturnValueAsync(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
await Task.Yield();
await _abpInterceptor.InterceptAsync(
new CastleAbpMethodInvocationAdapter(invocation, proceedInfo)
);
}
private async Task<T> ExecuteWithReturnValueAsync<T>(IInvocation invocation, IInvocationProceedInfo proceedInfo)
{
await Task.Yield();
await _abpInterceptor.InterceptAsync(
new CastleAbpMethodInvocationAdapter(invocation, proceedInfo)
);
return await (Task<T>)invocation.ReturnValue;
}
}
}

67
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs

@ -1,77 +1,26 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Threading;
namespace Volo.Abp.Castle.DynamicProxy
{
public class CastleAbpMethodInvocationAdapter : IAbpMethodInvocation
public class CastleAbpMethodInvocationAdapter : CastleAbpMethodInvocationAdapterBase, IAbpMethodInvocation
{
public object[] Arguments => Invocation.Arguments;
public IReadOnlyDictionary<string, object> ArgumentsDictionary => _lazyArgumentsDictionary.Value;
private readonly Lazy<IReadOnlyDictionary<string, object>> _lazyArgumentsDictionary;
public Type[] GenericArguments => Invocation.GenericArguments;
public object TargetObject => Invocation.InvocationTarget ?? Invocation.MethodInvocationTarget;
public MethodInfo Method => Invocation.MethodInvocationTarget ?? Invocation.Method;
public object ReturnValue
{
get => _actualReturnValue ?? Invocation.ReturnValue;
set => Invocation.ReturnValue = value;
}
private object _actualReturnValue;
protected IInvocation Invocation { get; }
protected IInvocationProceedInfo ProceedInfo { get; }
protected Func<IInvocation, IInvocationProceedInfo, Task> Proceed { get; }
public CastleAbpMethodInvocationAdapter(IInvocation invocation, IInvocationProceedInfo proceedInfo)
public CastleAbpMethodInvocationAdapter(IInvocation invocation, IInvocationProceedInfo proceedInfo,
Func<IInvocation, IInvocationProceedInfo, Task> proceed)
: base(invocation)
{
Invocation = invocation;
ProceedInfo = proceedInfo;
_lazyArgumentsDictionary = new Lazy<IReadOnlyDictionary<string, object>>(GetArgumentsDictionary);
Proceed = proceed;
}
public void Proceed()
public override async Task ProceedAsync()
{
ProceedInfo.Invoke();
if (Invocation.Method.IsAsync())
{
AsyncHelper.RunSync(() => (Task)Invocation.ReturnValue);
}
}
public Task ProceedAsync()
{
ProceedInfo.Invoke();
_actualReturnValue = Invocation.ReturnValue;
return Invocation.Method.IsAsync()
? (Task)_actualReturnValue
: Task.FromResult(_actualReturnValue);
}
private IReadOnlyDictionary<string, object> GetArgumentsDictionary()
{
var dict = new Dictionary<string, object>();
var methodParameters = Method.GetParameters();
for (int i = 0; i < methodParameters.Length; i++)
{
dict[methodParameters[i].Name] = Invocation.Arguments[i];
}
return dict;
await Proceed(Invocation, ProceedInfo);
}
}
}

48
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapterBase.cs

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Castle.DynamicProxy
{
public abstract class CastleAbpMethodInvocationAdapterBase : IAbpMethodInvocation
{
public object[] Arguments => Invocation.Arguments;
public IReadOnlyDictionary<string, object> ArgumentsDictionary => _lazyArgumentsDictionary.Value;
private readonly Lazy<IReadOnlyDictionary<string, object>> _lazyArgumentsDictionary;
public Type[] GenericArguments => Invocation.GenericArguments;
public object TargetObject => Invocation.InvocationTarget ?? Invocation.MethodInvocationTarget;
public MethodInfo Method => Invocation.MethodInvocationTarget ?? Invocation.Method;
public object ReturnValue { get; set; }
protected IInvocation Invocation { get; }
protected CastleAbpMethodInvocationAdapterBase(IInvocation invocation)
{
Invocation = invocation;
_lazyArgumentsDictionary = new Lazy<IReadOnlyDictionary<string, object>>(GetArgumentsDictionary);
}
public abstract Task ProceedAsync();
private IReadOnlyDictionary<string, object> GetArgumentsDictionary()
{
var dict = new Dictionary<string, object>();
var methodParameters = Method.GetParameters();
for (int i = 0; i < methodParameters.Length; i++)
{
dict[methodParameters[i].Name] = Invocation.Arguments[i];
}
return dict;
}
}
}

27
framework/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapterWithReturnValue.cs

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.Castle.DynamicProxy
{
public class CastleAbpMethodInvocationAdapterWithReturnValue<TResult> : CastleAbpMethodInvocationAdapterBase, IAbpMethodInvocation
{
protected IInvocationProceedInfo ProceedInfo { get; }
protected Func<IInvocation, IInvocationProceedInfo, Task<TResult>> Proceed { get; }
public CastleAbpMethodInvocationAdapterWithReturnValue(IInvocation invocation,
IInvocationProceedInfo proceedInfo,
Func<IInvocation, IInvocationProceedInfo, Task<TResult>> proceed)
: base(invocation)
{
ProceedInfo = proceedInfo;
Proceed = proceed;
}
public override async Task ProceedAsync()
{
ReturnValue = await Proceed(Invocation, ProceedInfo);
}
}
}

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

Loading…
Cancel
Save