Browse Source

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

pull/4148/head
liangshiwei 6 years ago
parent
commit
532b90c222
  1. 5
      .gitignore
  2. 1
      build/common.ps1
  3. 6
      docs/cs/Contribution/Index.md
  4. 40
      docs/cs/Contribution/Localization-Text-Files.md
  5. 54
      docs/en/Background-Jobs-Quartz.md
  6. 84
      docs/en/Background-Workers-Quartz.md
  7. 158
      docs/en/CLI.md
  8. 6
      docs/en/Contribution/Index.md
  9. 31
      docs/en/Contribution/Localization-Text-Files.md
  10. 17
      docs/en/Entity-Framework-Core-MySQL.md
  11. 67
      docs/en/Entity-Framework-Core-Oracle.md
  12. 1
      docs/en/Entity-Framework-Core-Other-DBMS.md
  13. 2
      docs/en/Getting-Started.md
  14. 1
      docs/en/Modules/Index.md
  15. 80
      docs/en/Modules/Virtual-File-Explorer.md
  16. 481
      docs/en/UI/Angular/Component-Replacement.md
  17. 62
      docs/en/UI/Angular/Localization.md
  18. BIN
      docs/en/UI/Angular/images/layout-components.png
  19. BIN
      docs/en/UI/Angular/images/logo-component.png
  20. BIN
      docs/en/UI/Angular/images/nav-items-component.png
  21. BIN
      docs/en/UI/Angular/images/replaced-logo-component.png
  22. BIN
      docs/en/UI/Angular/images/replaced-nav-items-component.png
  23. BIN
      docs/en/UI/Angular/images/replaced-routes-component.png
  24. BIN
      docs/en/UI/Angular/images/routes-component.png
  25. 4
      docs/en/docs-nav.json
  26. BIN
      docs/en/images/virtual-file-explorer.png
  27. 54
      docs/zh-Hans/Background-Jobs-Quartz.md
  28. 86
      docs/zh-Hans/Background-Workers-Quartz.md
  29. 7
      docs/zh-Hans/Contribution/Index.md
  30. 31
      docs/zh-Hans/Contribution/Localization-Text-Files.md
  31. 1
      docs/zh-Hans/Modules/Index.md
  32. 80
      docs/zh-Hans/Modules/Virtual-File-Explorer.md
  33. BIN
      docs/zh-Hans/images/virtual-file-explorer.png
  34. 35
      framework/Volo.Abp.sln
  35. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs
  36. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs
  37. 24
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs
  38. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/PageAlerts/Default.cshtml
  39. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml
  40. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/Default.cshtml
  41. 24
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/IToolbarConfigurationContext.cs
  42. 57
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarConfigurationContext.cs
  43. 47
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/dom-event-handlers.js
  44. 8
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  45. 40
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs
  46. 19
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs
  47. 1
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Routing/EndpointRouteBuilderContext.cs
  48. 3
      framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xml
  49. 30
      framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xsd
  50. 21
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj
  51. 13
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemModule.cs
  52. 32
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/DefaultBlobFilePathCalculator.cs
  53. 24
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs
  54. 72
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs
  55. 27
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfiguration.cs
  56. 8
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfigurationNames.cs
  57. 7
      framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/IBlobFilePathCalculator.cs
  58. 3
      framework/src/Volo.Abp.BlobStoring/FodyWeavers.xml
  59. 30
      framework/src/Volo.Abp.BlobStoring/FodyWeavers.xsd
  60. 23
      framework/src/Volo.Abp.BlobStoring/Volo.Abp.BlobStoring.csproj
  61. 28
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringModule.cs
  62. 12
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringOptions.cs
  63. 31
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobAlreadyExistsException.cs
  64. 198
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs
  65. 71
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs
  66. 27
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationExtensions.cs
  67. 11
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationProviderExtensions.cs
  68. 79
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurations.cs
  69. 57
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerExtensions.cs
  70. 43
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs
  71. 26
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs
  72. 41
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerNameAttribute.cs
  73. 36
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs
  74. 16
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs
  75. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderDeleteArgs.cs
  76. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderExistsArgs.cs
  77. 24
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderGetArgs.cs
  78. 34
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSaveArgs.cs
  79. 15
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSelectorExtensions.cs
  80. 20
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobContainerConfigurationProvider.cs
  81. 49
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobProviderSelector.cs
  82. 8
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs
  83. 88
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainer.cs
  84. 7
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs
  85. 20
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs
  86. 16
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProvider.cs
  87. 10
      framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProviderSelector.cs
  88. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  89. 444
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs
  90. 5
      framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs
  91. 45
      framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs
  92. 16
      framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs
  93. 15
      framework/src/Volo.Abp.Core/Volo/Abp/AbpException.cs
  94. 15
      framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs
  95. 31
      framework/src/Volo.Abp.Core/Volo/Abp/AbpShutdownException.cs
  96. 9
      framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/ProxyHelper.cs
  97. 34
      framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs
  98. 9
      framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
  99. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  100. 13
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Microsoft/EntityFrameworkCore/AbpMySqlModelBuilderExtensions.cs

5
.gitignore

@ -298,6 +298,9 @@ samples/MicroserviceDemo/microservices/TenantManagementService.Host/Logs/logs.tx
/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package-lock.json
/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package-lock.json
/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/package-lock.json
modules/virtual-file-explorer/app/Volo.Abp.VirtualFileExplorer.DemoApp/Logs/
/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package-lock.json
/templates/app/angular/package-lock.json
/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package-lock.json
/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package-lock.json

1
build/common.ps1

@ -19,6 +19,7 @@ $solutionPaths = (
"../modules/audit-logging",
"../modules/background-jobs",
"../modules/client-simulation",
"../modules/virtual-file-explorer",
"../templates/module/aspnet-core",
"../templates/app/aspnet-core",
"../samples/MicroserviceDemo",

6
docs/cs/Contribution/Index.md

@ -41,7 +41,9 @@ Nový jazyk je publikován jakmile jsou minimálně tyto překlady dokončeny.
ABP framework má flexibilní [lokalizační systém](../Localization.md). Můžete tak vytvořit lokalizované uživatelské prostředí pro svou vlastní aplikaci.
K tomu mají framework a vestavěné moduly již lokalizované texty. Například [lokalizační texty pro Volo.Abp.UI balík](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). Můžete vytvořit nový soubor ve [stejné složce](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) k přidání překladu.
K tomu mají framework a vestavěné moduly již lokalizované texty. Například [lokalizační texty pro Volo.Abp.UI balík](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
Můžete vytvořit nový soubor ve [stejné složce](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) k přidání překladu.
* Naklonujte [ABP repozitář](https://github.com/abpframework/abp/) z Githubu.
* Vytvořte nový soubor pro cílový jazyk pro lokalizační text v (json) souboru (u souboru en.json).
@ -49,6 +51,8 @@ K tomu mají framework a vestavěné moduly již lokalizované texty. Napříkla
* Přeložte texty.
* Zašlete pull request na Githubu.
K překladu lokalizovaných textů můžete také použít příkaz `abp translate` of [ABP CLI](CLI.md).
ABP je modulářní framework, proto je zde mnoho zdrojů lokalizačních textů, jeden pro každý modul. K najití všech .json souborů, vyhledejte po naklonování repozitáře soubory "en.json". Můžete se taky podívat na [tento seznam](Localization-Text-Files.md) souborů lokalizačních textů.
### Příspevky do blogu a návody

40
docs/cs/Contribution/Localization-Text-Files.md

@ -1,40 +0,0 @@
## Textové soubory pro lokalizaci
Toto je seznam lokalizačních textových souborů pro každého kdo chce přispět k překladu textů ve frameworku. Tento seznam budeme udržovat aktuální:
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Resources/AbpValidation/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json
* https://github.com/abpframework/abp/tree/master/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Web/Localization/Resources/Docs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Localization/Resources/FeatureManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Localization/Resources/AbpPermissionManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Localization/Resources/AbpTenantManagement/Web/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Application.Contracts/Localization/MyProjectName/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/DomainShared/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc-module/src/MyCompanyName.MyProjectName.Web/Localization/MyProjectName/Web/en.json
* https://github.com/abpframework/abp/tree/master/templates/mvc/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json

54
docs/en/Background-Jobs-Quartz.md

@ -40,7 +40,7 @@ public class YourModule : AbpModule
## Configuration
Quartz is a very configurable library,and the ABP framework provides `AbpQuartzPreOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example:
Quartz is a very configurable library,and the ABP framework provides `AbpQuartzOptions` for this. You can use the `PreConfigure` method in your module class to pre-configure this option. ABP will use it when initializing the Quartz module. For example:
````csharp
[DependsOn(
@ -53,7 +53,7 @@ public class YourModule : AbpModule
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzPreOptions>(options =>
PreConfigure<AbpQuartzOptions>(options =>
{
options.Properties = new NameValueCollection
{
@ -70,4 +70,52 @@ public class YourModule : AbpModule
}
````
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).
Quartz stores job and scheduling information **in memory by default**. In the example, we use the pre-configuration of [options pattern](Options.md) to change it to the database. For more configuration of Quartz, please refer to the Quartz's [documentation](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).
## Exception handling
### Default exception handling strategy
When an exception occurs in the background job,ABP provide the **default handling strategy** retrying once every 3 seconds, up to 3 times. You can change the retry count and retry interval via `AbpBackgroundJobQuartzOptions` options:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundJobQuartzOptions>(options =>
{
options.RetryCount = 1;
options.RetryIntervalMillisecond = 1000;
});
}
}
```
### Customize exception handling strategy
You can customize the exception handling strategy via `AbpBackgroundJobQuartzOptions` options:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundJobQuartzOptions>(options =>
{
options.RetryStrategy = async (retryIndex, executionContext, exception) =>
{
// customize exception handling
};
});
}
}
```

84
docs/en/Background-Workers-Quartz.md

@ -36,11 +36,11 @@ public class YourModule : AbpModule
}
````
### Configuration
## Configuration
See [Configuration](Background-Jobs-Quartz#Configuration).
### Create a Background Worker
## Create a Background Worker
A background work is a class that derives from the `QuartzBackgroundWorkerBase` base class. for example. A simple worker class is shown below:
@ -49,8 +49,8 @@ public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
JobDetail = JobBuilder.Create<MyLogWorker>().Build();
Trigger = TriggerBuilder.Create().StartNow().Build();
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build();
}
public override Task Execute(IJobExecutionContext context)
@ -63,6 +63,82 @@ public class MyLogWorker : QuartzBackgroundWorkerBase
We simply implemented the Execute method to write a log. The background worker is a **singleton by default**. If you want, you can also implement a [dependency interface](Dependency-Injection#DependencyInterfaces) to register it as another life cycle.
> Tips: Add identity to background workers is a best practice,because quartz distinguishes different jobs based on identity.
## Add to BackgroundWorkerManager
Default background workers are **automatically** added to the BackgroundWorkerManager when the application is **initialized**. You can set `AutoRegister` property value to `false`,if you want to add it manually:
```` csharp
public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
AutoRegister = false;
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build();
}
public override Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
````
If you want to globally disable auto add worker, you can global disable via `AbpBackgroundWorkerQuartzOptions` options:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundWorkerQuartzOptions>(options =>
{
options.IsAutoRegisterEnabled = false;
});
}
}
```
## Advanced topics
### Customize ScheduleJob
Assume you have a worker executes every 10 minutes,but because server is unavailable for 30 minutes, 3 executions are missed. You want to execute all missed times after the server is available. You should define your background worker like this:
```csharp
public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).WithSimpleSchedule(s=>s.WithIntervalInMinutes(1).RepeatForever().WithMisfireHandlingInstructionIgnoreMisfires()).Build();
ScheduleJob = async scheduler =>
{
if (!await scheduler.CheckExists(JobDetail.Key))
{
await scheduler.ScheduleJob(JobDetail, Trigger);
}
};
}
public override Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
```
In the example we defined the worker execution interval to be 10 minutes and set `WithMisfireHandlingInstructionIgnoreMisfires`. we customized `ScheduleJob` and add worker to quartz only when the background worker does not exist.
### More
Please see Quartz's [documentation](https://www.quartz-scheduler.net/documentation/index.html) for more information.

158
docs/en/CLI.md

@ -18,6 +18,37 @@ dotnet tool update -g Volo.Abp.Cli
## Commands
Here, the list of all available commands before explaining their details:
* **`help`**: Shows help on the usage of the ABP CLI.
* **`new`**: Generates a new solution based on the ABP [startup templates](Startup-Templates/Index.md).
* **`update`**: Automatically updates all ABP related NuGet and NPM packages in a solution.
* **`add-package`**: Adds an ABP package to a project.
* **`add-module`**: Adds a [multi-package application module](https://docs.abp.io/en/abp/latest/Modules/Index) to a solution.
* **`generate-proxy`**: Generates client side proxies to use HTTP API endpoints on the server.
* **`switch-to-preview`**: Switches to the latest [nightly builds](Nightly-Builds.md) of the ABP related packages on a solution.
* **`switch-to-stable`**: Switches to the latest stable versions of the ABP related packages on a solution.
* **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository.
* **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password.
* **`logout`**: Logouts from your computer if you've authenticated before.
### help
Shows basic usages of the ABP CLI.
Usage:
````bash
abp help [command-name]
````
Examples:
````bash
abp help # Shows a general help.
abp help new # Shows help about the "new" command.
````
### new
Generates a new solution based on the ABP [startup templates](Startup-Templates/Index.md).
@ -63,6 +94,27 @@ abp new Acme.BookStore
* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
* `--local-framework-ref --abp-path`: keeps local references to projects instead of replacing with NuGet package references.
### update
Updating all ABP related packages can be tedious since there are many packages of the framework and modules. This command automatically updates all ABP related NuGet and NPM packages in a solution or project to the latest versions.
Usage:
````bash
abp update [options]
````
* If you run in a directory with a .sln file, it updates all ABP related packages of the all projects of the solution to the latest versions.
* If you run in a directory with a .csproj file, it updates all ABP related packages of the project to the latest versions.
#### Options
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
### add-package
Adds an ABP package to a project by,
@ -117,26 +169,28 @@ abp add-module Volo.Blogging
* `-sp` or `--startup-project`: Relative path to the project folder of the startup project. Default value is the current folder.
* `--with-source-code`: Add source code of the module instead of NuGet/NPM packages.
### update
### generate-proxy
Updating all ABP related packages can be tedious since there are many packages of the framework and modules. This command automatically updates all ABP related NuGet and NPM packages in a solution or project to the latest versions.
Generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running `generate-proxy` command, your host must be up and running.
Usage:
````bash
abp update [options]
abp generate-proxy [options]
````
* If you run in a directory with a .sln file, it updates all ABP related packages of the all projects of the solution to the latest versions.
* If you run in a directory with a .csproj file, it updates all ABP related packages of the project to the latest versions.
#### Options
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
* `--apiUrl` or `-a`: Specifies the root URL of the HTTP API. The default value is being retrieved from the `environment.ts` file for the Angular application. Make sure your host is up and running before running `abp generate-proxy`.
* `--ui` or `-u`: Specifies the UI framework. Default value is `angular` and it is the only UI framework supported for now. Creates TypeScript code.
* `--module` or `-m`: Specifies the module name. Default module name is `app`, which indicates your own application (you typically want this since every module is responsible to maintain its own client proxies). Set `all` for to generate proxies for all the modules.
Example usage with the options:
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
````
### switch-to-preview
@ -147,6 +201,7 @@ Usage:
````bash
abp switch-to-preview [options]
````
#### Options
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
@ -164,64 +219,71 @@ abp switch-to-stable [options]
`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory.
### login
### translate
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write:
Simplifies to translate [localization](Localization.md) files when you have multiple JSON [localization](Localization.md) files in a source control repository.
```bash
abp login <username>
```
* This command will create a unified json file based on the reference culture.
* It searches all the localization `JSON` files in the current directory and all subdirectories (recursively). Then creates a single file (named `abp-translation.json` by default) that includes all the entries need to be translated.
* Once you translate the entries in this file, you can then apply your changes to the original localization files using the `--apply` command.
```bash
abp login <username> -p <password>
```
> The main purpose of this command is to translate ABP Framework localization files (since the [abp repository](https://github.com/abpframework/abp) has tens of localization files to be translated in different directories).
Notice that, a new login with an already active session, overwrites the previous session.
#### Creating the Translation File
### logout
First step is to create the unified translation file:
Logs you out by removing the session token from your computer.
````bash
abp translate -c <culture> [options]
````
```
abp logout
```
Example:
### generate-proxy
````bash
abp translate -c de-DE
````
Generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running `generate-proxy` command, your host must be up and running.
This command created the unified translation file for the `de-DE` (German) culture.
Usage:
##### Additional Options
* `--reference-culture` or `-r`: Default `en`. Specifies the reference culture.
* `--output` or `-o`: Output file name. Default `abp-translation.json`.
* `--all-values` or `-all`: Include all keys to translate. By default, the unified translation file only includes the missing texts for the target culture. Specify this parameter if you may need to revise the values already translated before.
#### Applying Changes
Once you translate the entries in the unified translation file, you can apply your changes to the original localization files using the `--apply` parameter:
````bash
abp generate-proxy [options]
abp translate --apply # apply all changes
abp translate -a # shortcut for --apply
````
#### Options
Then review changes on your source control system to be sure that it has changed the proper files and send a Pull Request if you've translated ABP Framework resources. Thank you in advance for your contribution.
* `--apiUrl` or `-a`: Specifies the root URL of the HTTP API. The default value is being retrieved from the `environment.ts` file for the Angular application. Make sure your host is up and running before running `abp generate-proxy`.
* `--ui` or `-u`: Specifies the UI framework. Default value is `angular` and it is the only UI framework supported for now. Creates TypeScript code.
* `--module` or `-m`: Specifies the module name. Default module name is `app`, which indicates your own application (you typically want this since every module is responsible to maintain its own client proxies). Set `all` for to generate proxies for all the modules.
##### Additional Options
Example usage with the options:
* `--file` or `-f`: Default: `abp-translation.json`. The translation file (use only if you've used the `--output` option before).
````bash
abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all
````
### login
Some features of the CLI requires to be logged in to abp.io platform. To login with your username write:
### help
```bash
abp login <username> # Asks password separately
abp login <username> -p <password> # Specify the password as a parameter
```
Writes basic usage information of the CLI.
> Using `-p` parameter might not be safe if someone is watching your screen :) It can be useful for automation purposes.
Usage:
A new login with an already active session overwrites the previous session.
````bash
abp help [command-name]
````
### logout
Examples:
Logs you out by removing the session token from your computer.
```
abp logout
```
````bash
abp help # Shows a general help.
abp help new # Shows help about the "new" command.
````

6
docs/en/Contribution/Index.md

@ -41,7 +41,9 @@ A new language is published after these minimum translations have been completed
ABP framework has a flexible [localization system](../Localization.md). You can create localized user interfaces for your own application.
In addition to that, the framework and pre-build modules have already localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). You can create a new file in the [same folder](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) to translate it.
In addition to that, the framework and pre-build modules have already localized texts. As an example, see [the localization texts for the Volo.Abp.UI package](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
You can create a new file in the [same folder](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi) to translate it.
* Clone the [ABP repository](https://github.com/abpframework/abp/) from Github.
* Create a new file for the target language for a localization text (json) file (near to the en.json file).
@ -49,6 +51,8 @@ In addition to that, the framework and pre-build modules have already localized
* Translate the texts.
* Send pull request on Github.
You can also use the `abp translate` command of [ABP CLI](CLI.md) to translate localized texts.
ABP is a modular framework. So there are many localization text resource, one per module. To find all .json files, you can search for "en.json" after cloning the repository. You can also check [this list](Localization-Text-Files.md) for a list of localization text files.
### Blog Posts & Tutorials

31
docs/en/Contribution/Localization-Text-Files.md

@ -1,31 +0,0 @@
## Localization Text Files
Here, a list of localization text files for anyone wants to contribute to localization of the texts coming from the framework. We will keep this list up to date:
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json
* https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json
* https://github.com/abpframework/abp/blob/master/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json
* https://github.com/abpframework/abp/tree/master/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json

17
docs/en/Entity-Framework-Core-MySQL.md

@ -25,23 +25,6 @@ MySQL connection strings are different than SQL Server connection strings. So, c
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
## Change the Migrations DbContext
MySQL DBMS has some slight differences than the SQL Server. Some module database mapping configuration (especially the field lengths) causes problems with MySQL. For example, some of the the [IdentityServer module](Modules/IdentityServer.md) tables has such problems and it provides an option to configure the fields based on your DBMS.
The startup template contains a *YourProjectName*MigrationsDbContext which is responsible to maintain and migrate the database schema. This DbContext basically calls extension methods of the depended modules to configure their database tables.
Open the *YourProjectName*MigrationsDbContext and change the `builder.ConfigureIdentityServer();` line as shown below:
````csharp
builder.ConfigureIdentityServer(options =>
{
options.DatabaseProvider = EfCoreDatabaseProvider.MySql;
});
````
Then `ConfigureIdentityServer()` method will set the field lengths to not exceed the MySQL limits. Refer to related module documentation if you have any problem while creating or executing the database migrations.
## Re-Generate the Migrations
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). EF Core Migrations depend on the selected DBMS provider. So, changing the DBMS provider will cause the migration fails.

67
docs/en/Entity-Framework-Core-Oracle.md

@ -0,0 +1,67 @@
# Switch to EF Core Oracle Provider
This document explains how to switch to the **Oracle** database provider for **[the application startup template](Startup-Templates/Application.md)** which comes with SQL Server provider pre-configured.
> This document uses a paid library of [Devart](https://www.devart.com/dotconnect/oracle/) company, because it is the only library for Oracle that supports EF Core 3.x.
## Replace the Volo.Abp.EntityFrameworkCore.SqlServer Package
`.EntityFrameworkCore` project in the solution depends on the [Volo.Abp.EntityFrameworkCore.SqlServer](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.SqlServer) NuGet package. Remove this package and add the same version of the [Volo.Abp.EntityFrameworkCore.Oracle.Devart](https://www.nuget.org/packages/Volo.Abp.EntityFrameworkCore.Oracle.Devart) package.
## Replace the Module Dependency
Find ***YourProjectName*EntityFrameworkCoreModule** class inside the `.EntityFrameworkCore` project, remove `typeof(AbpEntityFrameworkCoreSqlServerModule)` from the `DependsOn` attribute, add `typeof(AbpEntityFrameworkCoreOracleDevartModule)` (also replace `using Volo.Abp.EntityFrameworkCore.SqlServer;` with `using Volo.Abp.EntityFrameworkCore.Oracle.Devart;`).
## UseOracle()
Find `UseSqlServer()` calls in your solution, replace with `UseOracle()`. Check the following files:
* *YourProjectName*EntityFrameworkCoreModule.cs inside the `.EntityFrameworkCore` project.
* *YourProjectName*MigrationsDbContextFactory.cs inside the `.EntityFrameworkCore.DbMigrations` project.
In the `CreateDbContext()` method of the *YourProjectName*MigrationsDbContextFactory.cs, replace the following code block
```
var builder = new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>()
.UseSqlServer(configuration.GetConnectionString("Default"));
```
with this one
```
var builder = (DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>)
new DbContextOptionsBuilder<YourProjectNameMigrationsDbContext>().UseOracle
(
configuration.GetConnectionString("Default")
);
```
> Depending on your solution structure, you may find more code files need to be changed.
## Change the Connection Strings
Oracle connection strings are different than SQL Server connection strings. So, check all `appsettings.json` files in your solution and replace the connection strings inside them. See the [connectionstrings.com]( https://www.connectionstrings.com/mysql/ ) for details of Oracle connection string options.
You typically will change the `appsettings.json` inside the `.DbMigrator` and `.Web` projects, but it depends on your solution structure.
A sample connection string for Oracle:
```
Data Source=localhost;User Id=myuser;Password=mypassword;
```
## Re-Generate the Migrations
The startup template uses [Entity Framework Core's Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) by default.
EF Core Migrations depend on the selected DBMS provider. Changing the DBMS provider, may not work with the existing migrations.
* Delete the `Migrations` folder under the `.EntityFrameworkCore.DbMigrations` project and re-build the solution.
* Run `Add-Migration "Initial"` on the Package Manager Console window (select the `.DbMigrator` (or `.Web`) project as the startup project in the Solution Explorer and select the `.EntityFrameworkCore.DbMigrations` project as the default project in the Package Manager Console).
This will scaffold a new migration for Oracle.
Run the `.DbMigrator` project to create the database, apply the changes and seed the initial data.
## Run the Application
It is ready. Just run the application and enjoy coding.

1
docs/en/Entity-Framework-Core-Other-DBMS.md

@ -6,6 +6,7 @@ ABP framework provides **integration packages** for some common DBMSs to make th
* [MySQL](Entity-Framework-Core-MySQL.md)
* [PostgreSQL](Entity-Framework-Core-PostgreSQL.md)
* [Oracle](Entity-Framework-Core-Oracle.md)
* [SQLite](Entity-Framework-Core-SQLite.md)
However, you can configure your DBMS provider **without** these integration packages. While using the integration package is always recommended (it also makes standard for the depended version across different modules), you can do it manually if there is no integration package for your DBMS provider.

2
docs/en/Getting-Started.md

@ -398,7 +398,7 @@ Wait Expo CLI to start. Expo CLI opens the management interface on the `http://l
In the above management interface, you can start the application with an Android emulator, an iOS simulator or a physical phone by the scan the QR code with the [Expo Client](https://expo.io/tools#client).
> See the [Android Studio Emulator](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/), [iOS Simulator](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/) documents on expo.io.
> See the [Android Studio Emulator](https://docs.expo.io/workflow/android-simulator/), [iOS Simulator](https://docs.expo.io/workflow/ios-simulator/) documents on expo.io.
![React Native login screen on iPhone 11](images/rn-login-iphone.png)

1
docs/en/Modules/Index.md

@ -23,6 +23,7 @@ There are some **free and open source** application modules developed and mainta
* **[Setting Management](Setting-Management.md)**: Used to persist and manage the [settings](../Settings.md).
* **Tenant Management**: Manages tenants for a [multi-tenant](../Multi-Tenancy.md) application.
* **Users**: Abstract users, so other modules can depend on this module instead of the Identity module.
* [**Virtual File Explorer**](Virtual-File-Explorer.md): Provided a simple UI to view files in [virtual file system](../Virtual-File-System.md).
See [the GitHub repository](https://github.com/abpframework/abp/tree/master/modules) for source code of all modules.

80
docs/en/Modules/Virtual-File-Explorer.md

@ -0,0 +1,80 @@
# Virtual File Explorer Module
## What is Virtual File Explorer Module?
Virtual File Explorer Module provided a simple UI to view all files in [virtual file system](../Virtual-File-System.md).
> Virtual File Explorer Module is not installed for [the startup templates](../Startup-Templates/Index.md). So, you need to manually add this module to your application.
### Installation
#### 1- Referencing Virtual File Explorer Module Packages
It is recommended to use the ABP CLI to install the module, open the CMD window in the solution file (`.sln`) directory, and run the following command:
`abp add-module Volo.VirtualFileExplorer`
Or you can also manually install nuget package to `Acme.MyProject.Web` project:
* Install [Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget package to `Acme.MyProject.Web` project.
`Install-Package Volo.Abp.VirtualFileExplorer.Web`
#### 2- Adding Module Dependencies
* Open `MyProjectWebModule.cs`and add `typeof(AbpVirtualFileExplorerWebModule)` as shown below;
```csharp
[DependsOn(
typeof(AbpVirtualFileExplorerWebModule),
typeof(MyProjectApplicationModule),
typeof(MyProjectEntityFrameworkCoreModule),
typeof(AbpAutofacModule),
typeof(AbpIdentityWebModule),
typeof(AbpAccountWebModule),
typeof(AbpAspNetCoreMvcUiBasicThemeModule)
)]
public class MyProjectWebModule : AbpModule
{
//...
}
```
#### 3- Adding NPM Package
* Open `package.json` and add `@abp/virtual-file-explorer": "^2.9.0` as shown below:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/virtual-file-explorer": "^2.9.0"
}
}
```
Then open the command line terminal in the `Acme.MyProject.Web` project folder and run the following command:
1. `yarn`
2. `gulp`
That's all,Now run the application and Navigate to `/VirtualFileExplorer`. You will see virtual file explorer page:
![Virtual-File-Explorer](../images/virtual-file-explorer.png)
### Options
You can disabled virtual file explorer module via `AbpVirtualFileExplorerOptions` options:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpVirtualFileExplorerOptions>(options =>
{
options.IsEnabled = false;
});
}
```

481
docs/en/UI/Angular/Component-Replacement.md

@ -1,10 +1,10 @@
## Component Replacement
# Component Replacement
You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
### How to Replace a Component
## How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
@ -15,16 +15,20 @@ import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddRepl
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
@ -33,13 +37,13 @@ export class AppComponent {
![Example Usage](./images/component-replacement.gif)
### How to Replace a Layout
## How to Replace a Layout
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way.
Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced the same way.
> A layout component template should contain `<router-outlet></router-outlet>` element.
The below example describes how to replace the `ApplicationLayoutComponent`:
The example below describes how to replace the `ApplicationLayoutComponent`:
Run the following command to generate a layout in `angular` folder:
@ -55,7 +59,7 @@ Add the following code in your layout template (`my-layout.component.html`) wher
<router-outlet></router-outlet>
```
Open the `app.component.ts` and add the below content:
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
@ -63,11 +67,13 @@ import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeB
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added below content
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
@ -80,6 +86,463 @@ export class AppComponent {
}
```
### Layout Components
![Layout Components](./images/layout-components.png)
#### How to Replace LogoComponent
![LogoComponent](./images/logo-component.png)
Run the following command in `angular` folder to create a new component called `LogoComponent`.
```bash
yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `logo.component.ts` in `src/app/logo` folder and replace its content with the following:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-logo',
template: `
<a class="navbar-brand" routerLink="/">
<!-- Change the img src -->
<img
src="https://via.placeholder.com/100x50/343a40/FF0000?text=MyLogo"
alt="logo"
width="100%"
height="auto"
/>
</a>
`,
})
export class LogoComponent {}
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
}
}
```
The final UI looks like below:
![New logo](./images/replaced-logo-component.png)
#### How to Replace RoutesComponent
![RoutesComponent](./images/routes-component.png)
Run the following command in `angular` folder to create a new component called `RoutesComponent`.
```bash
yarn ng generate component routes --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import {
Component,
HostBinding,
Inject,
Renderer2,
TrackByFunction,
AfterViewInit,
} from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent implements AfterViewInit {
@HostBinding('class.mx-auto')
marginAuto = true;
smallScreen = window.innerWidth < 992;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
}
```
Open the generated `routes.component.html` in `src/app/routes` folder and replace its content with the following:
```html
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/"
><i class="fas fa-home"></i> {%{{{ '::Menu:Home' | abpLocalization }}}%}</a
>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
</li>
<li
#navbarRootDropdown
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
<i class="fas fa-wrench"></i>
{%{{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}}%}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-id-card-o"></i>
{%{{{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
<a class="dropdown-item" routerLink="/identity/roles">
{%{{{ 'AbpIdentity::Roles' | abpLocalization }}}%}</a
>
</div>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
<a class="dropdown-item" routerLink="/identity/users">
{%{{{ 'AbpIdentity::Users' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpTenantManagement.Tenants'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-users"></i>
{%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
<a class="dropdown-item" routerLink="/tenant-management/tenants">
{%{{{ 'AbpTenantManagement::Tenants' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
</div>
</li>
</ul>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
}
}
```
The final UI looks like below:
![New routes](./images/replaced-routes-component.png)
#### How to Replace NavItemsComponent
![NavItemsComponent](./images/nav-items-component.png)
Run the following command in `angular` folder to create a new component called `NavItemsComponent`.
```bash
yarn ng generate component nav-items --entryComponent
# You don't need the --entryComponent option in Angular 9
```
Open the generated `nav-items.component.ts` in `src/app/nav-items` folder and replace the content with the following:
```js
import {
ApplicationConfiguration,
AuthService,
ConfigState,
SessionState,
SetLanguage,
} from '@abp/ng.core';
import { Component, AfterViewInit } from '@angular/core';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store';
import { Observable, fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'app-nav-items',
templateUrl: 'nav-items.component.html',
})
export class NavItemsComponent implements AfterViewInit {
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
smallScreen = window.innerWidth < 992;
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
constructor(private store: Store, private authService: AuthService) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
}
```
Open the generated `nav-items.component.html` in `src/app/nav-items` folder and replace the content with the following:
```html
<ul class="navbar-nav">
<input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ defaultLanguage$ | async }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{%{{{ lang?.displayName }}}%}</a
>
</div>
</div>
</li>
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{%{{{
'AbpAccount::Login' | abpLocalization
}}}%}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ (currentUser$ | async)?.userName }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{%{{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}}%}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{%{{{ 'AbpUi::Logout' | abpLocalization }}}%}</a
>
</div>
</div>
</li>
</ul>
```
Open `app.component.ts` in `src/app` folder and modify it as shown below:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
}
}
```
The final UI looks like below:
![New nav-items](./images/replaced-nav-items-component.png)
## What's Next?
- [Custom Setting Page](./Custom-Setting-Page.md)

62
docs/en/UI/Angular/Localization.md

@ -130,6 +130,68 @@ this.store.selectSnapshot(
Localization resources are stored in the `localization` property of `ConfigState`.
## RTL Support
As of v2.9 ABP has RTL support. If you are generating a new project with v2.9 and above, everything is set, you do not need to do any changes. If you are migrating your project from an earlier version, please follow the 2 steps below:
#### Step 1. Create Chunks for Bootstrap LTR and RTL
Find [styles configuration in angular.json](https://angular.io/guide/workspace-config#style-script-config) and make sure the chunks in your project has `bootstrap-rtl.min` and `bootstrap-ltr.min` as shown below.
```json
{
"projects": {
"MyProjectName": {
"architect": {
"build": {
"options": {
"styles": [
{
"input": "node_modules/@abp/ng.theme.shared/styles/bootstrap-rtl.min.css",
"inject": false,
"bundleName": "bootstrap-rtl.min"
},
{
"input": "node_modules/bootstrap/dist/css/bootstrap.min.css",
"inject": true,
"bundleName": "bootstrap-ltr.min"
},
{
"input": "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"inject": true,
"bundleName": "fontawesome-all.min"
},
{
"input": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
"inject": true,
"bundleName": "fontawesome-v4-shims.min"
},
"apps/dev-app/src/styles.scss"
],
}
}
}
}
}
}
#### Step 2. Clear Lazy Loaded Fontawesome in AppComponent
If you have created and injected chunks for Fontawesome as seen above, you no longer need the lazy loading in the `AppComponent` which was implemented before v2.9. Simply remove them. The `AppComponent` in the template of the new version looks like this:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<router-outlet></router-outlet>
`,
})
export class AppComponent {}
```
## See Also

BIN
docs/en/UI/Angular/images/layout-components.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/en/UI/Angular/images/logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
docs/en/UI/Angular/images/nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/en/UI/Angular/images/replaced-logo-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
docs/en/UI/Angular/images/replaced-nav-items-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/en/UI/Angular/images/replaced-routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/en/UI/Angular/images/routes-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

4
docs/en/docs-nav.json

@ -440,6 +440,10 @@
"text": "To PostgreSQL",
"path": "Entity-Framework-Core-PostgreSQL.md"
},
{
"text": "To Oracle",
"path": "Entity-Framework-Core-Oracle.md"
},
{
"text": "To SQLite",
"path": "Entity-Framework-Core-SQLite.md"

BIN
docs/en/images/virtual-file-explorer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

54
docs/zh-Hans/Background-Jobs-Quartz.md

@ -40,7 +40,7 @@ public class YourModule : AbpModule
## 配置
Quartz是一个可配置的类库,对此ABP框架提供了 `AbpQuartzPreOptions`. 你可以在模块预配置此选项,ABP在初始化Quartz模块时将使用它. 例:
Quartz是一个可配置的类库,对此ABP框架提供了 `AbpQuartzOptions`. 你可以在模块预配置此选项,ABP在初始化Quartz模块时将使用它. 例:
````csharp
[DependsOn(
@ -53,7 +53,7 @@ public class YourModule : AbpModule
{
var configuration = context.Services.GetConfiguration();
PreConfigure<AbpQuartzPreOptions>(options =>
PreConfigure<AbpQuartzOptions>(options =>
{
options.Properties = new NameValueCollection
{
@ -70,4 +70,52 @@ public class YourModule : AbpModule
}
````
Quartz**默认**将作业与调度信息存储在**内存**中,示例中我们使用[选项模式](Options.md)的预配置将其更改为存储到数据库中. 有关Quartz的更多配置请参阅[Quartz文档](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).
Quartz**默认**将作业与调度信息存储在**内存**中,示例中我们使用[选项模式](Options.md)的预配置将其更改为存储到数据库中. 有关Quartz的更多配置请参阅[Quartz文档](https://www.quartz-scheduler.net/documentation/quartz-3.x/tutorial/index.html).
## 异常处理
### 默认异常处理策略
当后台作业发生异常时ABP提供了**默认**异常处理策略,它会为你的作业重试3次,每次间隔3秒. 你可以通过 `AbpBackgroundJobQuartzOptions` 更改默认重试次数与间隔时间:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundJobQuartzOptions>(options =>
{
options.RetryCount = 1;
options.RetryIntervalMillisecond = 1000;
});
}
}
```
### 自定义异常处理策略
你可以通过 `AbpBackgroundJobQuartzOptions` 选项自定义异常处理策略:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundJobsQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundJobQuartzOptions>(options =>
{
options.RetryStrategy = async (retryIndex, executionContext, exception) =>
{
// customize exception handling
};
});
}
}
```

86
docs/zh-Hans/Background-Workers-Quartz.md

@ -36,11 +36,11 @@ public class YourModule : AbpModule
}
````
### 配置
## 配置
参阅[配置](Background-Jobs-Quartz.md#配置).
### 创建后台工作者
## 创建后台工作者
后台工作者是一个继承自 `QuartzBackgroundWorkerBase` 基类的类. 一个简单的工作者如下所示:
@ -49,8 +49,8 @@ public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
JobDetail = JobBuilder.Create<MyLogWorker>().Build();
Trigger = TriggerBuilder.Create().StartNow().Build();
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build();
}
public override Task Execute(IJobExecutionContext context)
@ -63,6 +63,82 @@ public class MyLogWorker : QuartzBackgroundWorkerBase
示例中我们重写了 `Execute` 方法写入日志. 后台工作者默认是**单例**. 如果你需要,也可以实现[依赖接口](Dependency-Injection.md#依赖接口)将其注册为其他的生命周期.
### 更多
> 提示: 为后台工作者添加标识是最佳实践,Quartz根据标识区分作业. 如果未指定标识会重复添加工作者到Quartz.
## 添加到BackgroundWorkerManager
默认后台工作者会在应用程序启动时**自动**添加到 `BackgroundWorkerManager`,如果你想要手动添加,可以将 `AutoRegister` 属性值设置为 `false`:
```` csharp
public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
AutoRegister = false;
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).StartNow().Build();
}
public override Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
````
尽管你可以使用 `AutoRegister` 跳过自动添加,但如果你想要全局禁用这样会比较繁琐. 你可以通过 `AbpBackgroundWorkerQuartzOptions` 选项全局禁用:
```csharp
[DependsOn(
//...other dependencies
typeof(AbpBackgroundWorkersQuartzModule) //Add the new module dependency
)]
public class YourModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpBackgroundWorkerQuartzOptions>(options =>
{
options.IsAutoRegisterEnabled = false;
});
}
}
```
## 高级主题
### 自定义ScheduleJob
例如你有一个每10分钟执行一次的工作者,但由于服务器不可用30分钟导致工作者错过了3次执行,你想要在服务器恢复正常后执行所有错过的执行. 你应该这样定义你的工作者:
```csharp
public class MyLogWorker : QuartzBackgroundWorkerBase
{
public MyLogWorker()
{
JobDetail = JobBuilder.Create<MyLogWorker>().WithIdentity(nameof(MyLogWorker)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(MyLogWorker)).WithSimpleSchedule(s=>s.WithIntervalInMinutes(1).RepeatForever().WithMisfireHandlingInstructionIgnoreMisfires()).Build();
ScheduleJob = async scheduler =>
{
if (!await scheduler.CheckExists(JobDetail.Key))
{
await scheduler.ScheduleJob(JobDetail, Trigger);
}
};
}
public override Task Execute(IJobExecutionContext context)
{
Logger.LogInformation("Executed MyLogWorker..!");
return Task.CompletedTask;
}
}
```
在示例中我们定义了工作者执行间隔为10分钟,并且设置 `WithMisfireHandlingInstructionIgnoreMisfires` ,另外自定义 `ScheduleJob` 仅当工作者不存在时向quartz添加调度作业.
## 更多
参阅Quartz[文档](https://www.quartz-scheduler.net/documentation/index.html)了解更多信息.

7
docs/zh-Hans/Contribution/Index.md

@ -41,7 +41,9 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南
ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自己的应用程序创建本地化用户界面.
除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).你可以在[相同文件夹](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi)中创建一个新文件进行翻译.
除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json).
你可以在[相同文件夹](https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi)中创建一个新文件进行翻译.
* 从Github克隆[ABP存储库](https://github.com/abpframework/abp/).
* 为本地化文本(json)文件(en.json文件同目录下)创建目标语言的新文件.
@ -49,6 +51,9 @@ ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自
* 翻译文本.
* 在Github上发送拉取请求(Pull request).
你还可以使用[ABP CLI](CLI.md)的`abp translation`命令来翻译本地化文本.
ABP是一个模块化框架. 所以有很多本地化文本资源, 每个模块都有一个. 要查找所有.json文件,可以在克隆存储库后搜索"en.json". 你还可以检查[此列表](Localization-Text-Files.md)以获取本地化文本文件列表.
### 博客文章和教程

31
docs/zh-Hans/Contribution/Localization-Text-Files.md

@ -1,31 +0,0 @@
## 本地化文本文件
这是一个来自框架的本地化文本文件列表, 任何人都可以做出贡献. 我们会将此列表保持最新:
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json
* https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Localization/Resource/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/CountryNames/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Base/Validation/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/en.json
* https://github.com/abpframework/abp/tree/master/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/SourceExt/en.json
* https://github.com/abpframework/abp/blob/master/modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/Resources/Blogging/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/app/VoloDocs.Web/Localization/Resources/VoloDocs/Web/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/Localization/Resources/Docs/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/modules/docs/src/Volo.Docs.Domain/Volo/Docs/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json
* https://github.com/abpframework/abp/tree/master/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain.Shared/Volo/Abp/PermissionManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared/Volo/Abp/SettingManagement/Localization/Resources/AbpSettingManagement/en.json
* https://github.com/abpframework/abp/tree/master/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json
* https://github.com/abpframework/abp-samples/tree/master/BookStore/src/Acme.BookStore.Domain.Shared/Localization/BookStore/en.json
* https://github.com/abpframework/abp-samples/tree/master/DashboardDemo/src/DashboardDemo.Domain.Shared/Localization/DashboardDemo/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application.Contracts/ProductManagement/Localization/ApplicationContracts/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/Localization/Domain/en.json
* https://github.com/abpframework/abp/tree/master/samples/MicroserviceDemo/modules/product/src/ProductManagement.Web/Localization/Resources/ProductManagement/en.json
* https://github.com/abpframework/abp/tree/master/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/Localization/MyProjectName/en.json

1
docs/zh-Hans/Modules/Index.md

@ -23,6 +23,7 @@ ABP是一个 **模块化的应用程序框架** 由十多个 **nuget packages**
* **Setting Management**: 用于保存设置.
* **Tenant Management**: 管理[多租户](../Multi-Tenancy.md)应用程序的租户.
* **Users**: 抽象用户, 因此其他模块可以依赖此模块而不是Identity模块.
* [**Virtual File Explorer**](Virtual-File-Explorer.md): 提供简单的UI查看[虚拟文件系统](../Virtual-File-System.md)中的文件.
模块化文档正在编写中. 请参阅[这个仓库](https://github.com/abpframework/abp/tree/master/modules)获取所有模块的源代码.

80
docs/zh-Hans/Modules/Virtual-File-Explorer.md

@ -0,0 +1,80 @@
# 虚拟文件浏览器模块
## 什么是虚拟文件浏览器模块
虚拟文件浏览器模块提供了一个简单的UI来查看[虚拟文件系统](../Virtual-File-System.md)所有的文件.
> [启动模板](../Startup-Templates/Index.md)默认并没有安装这个模块,所以你需要手动安装到应用程序.
### 安装
#### 1- 引用虚拟文件浏览器模块包
建议使用ABP CLI安装模块,在解决方案文件 (`.sln`) 目录打开 `CMD` 窗口,运行以下命令:
`abp add-module Volo.VirtualFileExplorer`
或者你也可以手动安装nuget包到 `Acme.MyProject.Web` 项目:
* 安装[Volo.Abp.VirtualFileExplorer.Web](https://www.nuget.org/packages/Volo.Abp.VirtualFileExplorer.Web/) nuget包到 `Acme.MyProject.Web` 项目.
`Install-Package Volo.Abp.VirtualFileExplorer.Web`
#### 2- 添加模块依赖
* 打开 `MyProjectWebModule.cs` 并且添加 `typeof(AbpVirtualFileExplorerWebModule)` 如下所示;
```csharp
[DependsOn(
typeof(AbpVirtualFileExplorerWebModule),
typeof(MyProjectApplicationModule),
typeof(MyProjectEntityFrameworkCoreModule),
typeof(AbpAutofacModule),
typeof(AbpIdentityWebModule),
typeof(AbpAccountWebModule),
typeof(AbpAspNetCoreMvcUiBasicThemeModule)
)]
public class MyProjectWebModule : AbpModule
{
//...
}
```
#### 3- 添加NPM包
* 打开 `package.json` 添加 `@abp/virtual-file-explorer": "^2.9.0` 如下所示:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^2.9.0",
"@abp/virtual-file-explorer": "^2.9.0"
}
}
```
然后在 `Acme.MyProject.Web` 项目目录打开命令行终端运行以下命令:
1. `yarn`
2. `gulp`
这就是全部,运行应用程序导航到 `/VirtualFileExplorer`. 你会看到虚拟文件浏览器页面:
![Virtual-File-Explorer](../images/virtual-file-explorer.png)
### 选项
你可以通过 `AbpVirtualFileExplorerOptions` 选项禁用虚拟文件浏览器模块:
```csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpVirtualFileExplorerOptions>(options =>
{
options.IsEnabled = false;
});
}
```

BIN
docs/zh-Hans/images/virtual-file-explorer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

35
framework/Volo.Abp.sln

@ -291,6 +291,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.SignalR
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests\Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Tests.csproj", "{79323211-E658-493E-9863-035AA4C3F913}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring", "src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj", "{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Tests", "test\Volo.Abp.BlobStoring.Tests\Volo.Abp.BlobStoring.Tests.csproj", "{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem", "src\Volo.Abp.BlobStoring.FileSystem\Volo.Abp.BlobStoring.FileSystem.csproj", "{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.FileSystem.Tests", "test\Volo.Abp.BlobStoring.FileSystem.Tests\Volo.Abp.BlobStoring.FileSystem.Tests.csproj", "{68443D4A-1608-4039-B995-7AF4CF82E9F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Oracle.Devart", "src\Volo.Abp.EntityFrameworkCore.Oracle.Devart\Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj", "{75E5C841-5F36-4C44-A532-57CB8E7FFE15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -865,6 +875,26 @@ Global
{79323211-E658-493E-9863-035AA4C3F913}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79323211-E658-493E-9863-035AA4C3F913}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79323211-E658-493E-9863-035AA4C3F913}.Release|Any CPU.Build.0 = Release|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42}.Release|Any CPU.Build.0 = Release|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958}.Release|Any CPU.Build.0 = Release|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B}.Release|Any CPU.Build.0 = Release|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68443D4A-1608-4039-B995-7AF4CF82E9F8}.Release|Any CPU.Build.0 = Release|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75E5C841-5F36-4C44-A532-57CB8E7FFE15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1012,6 +1042,11 @@ Global
{B64FCE08-E9D2-4984-BF12-FE199F257416} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{8B758716-DCC9-4223-8421-5588D1597487} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{79323211-E658-493E-9863-035AA4C3F913} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{A0CFBDD6-A3CB-438C-83F1-5025F12E2D42} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{D53A17BB-4E23-451D-AD9B-E1F6AC3F7958} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{02B1FBE2-850E-4612-ABC6-DD62BCF2DD6B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{68443D4A-1608-4039-B995-7AF4CF82E9F8} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{75E5C841-5F36-4C44-A532-57CB8E7FFE15} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

7
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs

@ -1,6 +1,7 @@
using System;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending;
using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.Timing;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
@ -21,6 +22,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
public CurrentTenantDto CurrentTenant { get; set; }
public TimingDto Timing { get; set; }
public ClockDto Clock { get; set; }
public ObjectExtensionsDto ObjectExtensions { get; set; }
}
}
}

7
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
public class ClockDto
{
public string Kind { get; set; }
}
}

24
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs

@ -0,0 +1,24 @@
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
{
public class TimingDto
{
public TimeZone TimeZone { get; set; }
}
public class TimeZone
{
public IanaTimeZone Iana { get; set; }
public WindowsTimeZone Windows { get; set; }
}
public class WindowsTimeZone
{
public string TimeZoneId { get; set; }
}
public class IanaTimeZone
{
public string TimeZoneName { get; set; }
}
}

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/PageAlerts/Default.cshtml

@ -4,7 +4,7 @@
<div id="AbpPageAlerts">
@foreach (var alertMessage in Model)
{
<abp-alert alert-type="@alertMessage.Type" dismissible="@alertMessage.Dismissible">
<abp-alert alert-type="@(alertMessage.Type)" dismissible="@alertMessage.Dismissible">
@if (alertMessage.Title != null)
{
<h4>@alertMessage.Title</h4>

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml

@ -55,7 +55,7 @@
<div class="@containerClass">
<abp-row>
<abp-column size-md="_5" class="mx-auto">
<abp-column class="col mx-auto" style="max-width: 440px">
@if (MultiTenancyOptions.Value.IsEnabled &&
(TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true))
{

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Shared/Components/AbpPageToolbar/Button/Default.cshtml

@ -5,9 +5,9 @@
icon="@Model.Icon"
id="@Model.Id"
busy-text="@Model.BusyText"
icon-type="@Model.IconType"
button-type="@Model.Type"
size="@Model.Size"
icon-type="@(Model.IconType)"
button-type="@(Model.Type)"
size="@(Model.Size)"
disabled="@Model.Disabled"
class="mx-1"
/>

24
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/IToolbarConfigurationContext.cs

@ -1,4 +1,9 @@
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
@ -8,5 +13,20 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
ITheme Theme { get; }
Toolbar Toolbar { get; }
IAuthorizationService AuthorizationService { get; }
IStringLocalizerFactory StringLocalizerFactory { get; }
Task<bool> IsGrantedAsync(string policyName);
[CanBeNull]
IStringLocalizer GetDefaultLocalizer();
[NotNull]
public IStringLocalizer GetLocalizer<T>();
[NotNull]
public IStringLocalizer GetLocalizer(Type resourceType);
}
}
}

57
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarConfigurationContext.cs

@ -1,21 +1,72 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Theming;
namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars
{
public class ToolbarConfigurationContext : IToolbarConfigurationContext
{
public IServiceProvider ServiceProvider { get; }
private readonly object _serviceProviderLock = new object();
private TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
lock (_serviceProviderLock)
{
if (reference == null)
{
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
return reference;
}
public IAuthorizationService AuthorizationService => LazyGetRequiredService(typeof(IAuthorizationService), ref _authorizationService);
private IAuthorizationService _authorizationService;
private IStringLocalizerFactory _stringLocalizerFactory;
public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(typeof(IStringLocalizerFactory),ref _stringLocalizerFactory);
public ITheme Theme { get; }
public Toolbar Toolbar { get; }
public IServiceProvider ServiceProvider { get; }
public ToolbarConfigurationContext(ITheme currentTheme, Toolbar toolbar, IServiceProvider serviceProvider)
{
Theme = currentTheme;
Toolbar = toolbar;
ServiceProvider = serviceProvider;
}
public Task<bool> IsGrantedAsync(string policyName)
{
return AuthorizationService.IsGrantedAsync(policyName);
}
[CanBeNull]
public IStringLocalizer GetDefaultLocalizer()
{
return StringLocalizerFactory.CreateDefaultOrNull();
}
[NotNull]
public IStringLocalizer GetLocalizer<T>()
{
return StringLocalizerFactory.Create<T>();
}
[NotNull]
public IStringLocalizer GetLocalizer(Type resourceType)
{
return StringLocalizerFactory.Create(resourceType);
}
}
}
}

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

@ -1,6 +1,9 @@
(function ($) {
function enableFormFeatures($forms, validate) {
abp.dom = abp.dom || {};
abp.dom.initializers = abp.dom.initializers || {};
abp.dom.initializers.initializeForms = function ($forms, validate) {
if ($forms.length) {
$forms.each(function () {
var $form = $(this);
@ -30,9 +33,9 @@
}
});
}
}
};
function initializeScript($el) {
abp.dom.initializers.initializeScript = function ($el) {
$el.findWithSelf('[data-script-class]').each(function () {
var scriptClassName = $(this).attr('data-script-class');
if (!scriptClassName) {
@ -51,20 +54,28 @@
});
}
abp.dom.onNodeAdded(function (args) {
args.$el.findWithSelf('[data-toggle="tooltip"]').tooltip({
abp.dom.initializers.initializeToolTips = function ($tooltips) {
$tooltips.tooltip({
container: 'body'
});
}
args.$el.findWithSelf('[data-toggle="popover"]').popover({
abp.dom.initializers.initializePopovers = function ($popovers) {
$popovers.popover({
container: 'body'
});
}
args.$el.findWithSelf('.timeago').timeago();
enableFormFeatures(args.$el.findWithSelf('form'), true);
abp.dom.initializers.initializeTimeAgos = function ($timeagos) {
$timeagos.timeago();
}
initializeScript(args.$el);
abp.dom.onNodeAdded(function (args) {
abp.dom.initializers.initializeToolTips(args.$el.findWithSelf('[data-toggle="tooltip"]'));
abp.dom.initializers.initializePopovers(args.$el.findWithSelf('[data-toggle="popover"]'));
abp.dom.initializers.initializeTimeAgos(args.$el.findWithSelf('.timeago'));
abp.dom.initializers.initializeForms(args.$el.findWithSelf('form'), true);
abp.dom.initializers.initializeScript(args.$el);
});
abp.dom.onNodeRemoved(function (args) {
@ -74,18 +85,10 @@
});
$(function () {
enableFormFeatures($('form'));
$('[data-toggle="tooltip"]').tooltip({
container: 'body'
});
$('[data-toggle="popover"]').popover({
container: 'body'
});
$('.timeago').timeago();
abp.dom.initializers.initializeToolTips($('[data-toggle="tooltip"]'));
abp.dom.initializers.initializePopovers($('[data-toggle="popover"]'));
abp.dom.initializers.initializeTimeAgos($('.timeago'));
abp.dom.initializers.initializeForms($('form'));
$('[data-auto-focus="true"]').first().findWithSelf('input,select').focus();
});

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

@ -170,11 +170,11 @@ namespace Volo.Abp.AspNetCore.Mvc
Configure<AbpEndpointRouterOptions>(options =>
{
options.EndpointConfigureActions.Add(context =>
options.EndpointConfigureActions.Add(endpointContext =>
{
context.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
context.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
context.Endpoints.MapRazorPages();
endpointContext.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}");
endpointContext.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpointContext.Endpoints.MapRazorPages();
});
});
}

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

@ -15,6 +15,7 @@ using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
@ -31,6 +32,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
private readonly ISettingDefinitionManager _settingDefinitionManager;
private readonly IFeatureDefinitionManager _featureDefinitionManager;
private readonly ILanguageProvider _languageProvider;
private readonly ITimezoneProvider _timezoneProvider;
private readonly AbpClockOptions _abpClockOptions;
private readonly ICachedObjectExtensionsDtoService _cachedObjectExtensionsDtoService;
public AbpApplicationConfigurationAppService(
@ -44,6 +47,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
ISettingDefinitionManager settingDefinitionManager,
IFeatureDefinitionManager featureDefinitionManager,
ILanguageProvider languageProvider,
ITimezoneProvider timezoneProvider,
IOptions<AbpClockOptions> abpClockOptions,
ICachedObjectExtensionsDtoService cachedObjectExtensionsDtoService)
{
_serviceProvider = serviceProvider;
@ -54,6 +59,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
_settingDefinitionManager = settingDefinitionManager;
_featureDefinitionManager = featureDefinitionManager;
_languageProvider = languageProvider;
_timezoneProvider = timezoneProvider;
_abpClockOptions = abpClockOptions.Value;
_cachedObjectExtensionsDtoService = cachedObjectExtensionsDtoService;
_localizationOptions = localizationOptions.Value;
_multiTenancyOptions = multiTenancyOptions.Value;
@ -74,6 +81,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
Setting = await GetSettingConfigAsync(),
MultiTenancy = GetMultiTenancy(),
CurrentTenant = GetCurrentTenant(),
Timing = await GetTimingConfigAsync(),
Clock = GetClockConfig(),
ObjectExtensions = _cachedObjectExtensionsDtoService.Get()
};
@ -226,5 +235,36 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations
return result;
}
protected virtual async Task<TimingDto> GetTimingConfigAsync()
{
var result = new TimingDto();
var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone);
if (!windowsTimeZoneId.IsNullOrWhiteSpace())
{
result.TimeZone = new TimeZone
{
Windows = new WindowsTimeZone
{
TimeZoneId = windowsTimeZoneId
},
Iana = new IanaTimeZone()
{
TimeZoneName = _timezoneProvider.WindowsToIana(windowsTimeZoneId)
}
};
}
return result;
}
protected virtual ClockDto GetClockConfig()
{
return new ClockDto
{
Kind = Enum.GetName(typeof(DateTimeKind), _abpClockOptions.Kind)
};
}
}
}

19
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/ValidationAttributeHelper.cs

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
@ -11,8 +8,22 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
private static readonly PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute)
.GetProperty("ErrorMessageString", BindingFlags.Instance | BindingFlags.NonPublic);
private static readonly PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute)
.GetProperty("CustomErrorMessageSet", BindingFlags.Instance | BindingFlags.NonPublic);
public static void SetDefaultErrorMessage(ValidationAttribute validationAttribute)
{
if (validationAttribute is StringLengthAttribute stringLength && stringLength.MinimumLength != 0)
{
var customErrorMessageSet = ValidationAttributeCustomErrorMessageSetProperty.GetValue(validationAttribute) as bool?;
if (customErrorMessageSet != true)
{
validationAttribute.ErrorMessage =
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.";
return;
}
}
validationAttribute.ErrorMessage =
ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string;
}

1
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Routing/EndpointRouteBuilderContext.cs

@ -5,6 +5,7 @@ namespace Microsoft.AspNetCore.Routing
public class EndpointRouteBuilderContext
{
public IEndpointRouteBuilder Endpoints { get; }
public IServiceProvider ScopeServiceProvider { get; }
public EndpointRouteBuilderContext(

3
framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xml

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

30
framework/src/Volo.Abp.BlobStoring.FileSystem/FodyWeavers.xsd

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

21
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo.Abp.BlobStoring.FileSystem.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring.FileSystem</AssemblyName>
<PackageId>Volo.Abp.BlobStoring.FileSystem</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
</ItemGroup>
</Project>

13
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/AbpBlobStoringFileSystemModule.cs

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.FileSystem
{
[DependsOn(
typeof(AbpBlobStoringModule)
)]
public class AbpBlobStoringFileSystemModule : AbpModule
{
}
}

32
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/DefaultBlobFilePathCalculator.cs

@ -0,0 +1,32 @@
using System.IO;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.FileSystem
{
public class DefaultBlobFilePathCalculator : IBlobFilePathCalculator, ITransientDependency
{
public virtual string Calculate(BlobProviderArgs args)
{
var fileSystemConfiguration = args.Configuration.GetFileSystemConfiguration();
var blobPath = fileSystemConfiguration.BasePath;
if (args.TenantId == null)
{
blobPath = Path.Combine(blobPath, "host");
}
else
{
blobPath = Path.Combine(blobPath, "tenants", args.TenantId.Value.ToString("D"));
}
if (fileSystemConfiguration.AppendContainerNameToBasePath)
{
blobPath = Path.Combine(blobPath, args.ContainerName);
}
blobPath = Path.Combine(blobPath, args.BlobName);
return blobPath;
}
}
}

24
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobContainerConfigurationExtensions.cs

@ -0,0 +1,24 @@
using System;
namespace Volo.Abp.BlobStoring.FileSystem
{
public static class FileSystemBlobContainerConfigurationExtensions
{
public static FileSystemBlobProviderConfiguration GetFileSystemConfiguration(
this BlobContainerConfiguration containerConfiguration)
{
return new FileSystemBlobProviderConfiguration(containerConfiguration);
}
public static BlobContainerConfiguration UseFileSystem(
this BlobContainerConfiguration containerConfiguration,
Action<FileSystemBlobProviderConfiguration> fileSystemConfigureAction)
{
containerConfiguration.ProviderType = typeof(FileSystemBlobProvider);
fileSystemConfigureAction(new FileSystemBlobProviderConfiguration(containerConfiguration));
return containerConfiguration;
}
}
}

72
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProvider.cs

@ -0,0 +1,72 @@
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.IO;
namespace Volo.Abp.BlobStoring.FileSystem
{
public class FileSystemBlobProvider : BlobProviderBase, ITransientDependency
{
protected IBlobFilePathCalculator FilePathCalculator { get; }
public FileSystemBlobProvider(IBlobFilePathCalculator filePathCalculator)
{
FilePathCalculator = filePathCalculator;
}
public override async Task SaveAsync(BlobProviderSaveArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
if (!args.OverrideExisting && await ExistsAsync(filePath))
{
throw new BlobAlreadyExistsException($"Saving BLOB '{args.BlobName}' does already exists in the container '{args.ContainerName}'! Set {nameof(args.OverrideExisting)} if it should be overwritten.");
}
DirectoryHelper.CreateIfNotExists(Path.GetDirectoryName(filePath));
var fileMode = args.OverrideExisting
? FileMode.Create
: FileMode.CreateNew;
using (var fileStream = File.Open(filePath, fileMode, FileAccess.Write))
{
await args.BlobStream.CopyToAsync(
fileStream,
args.CancellationToken
);
await fileStream.FlushAsync();
}
}
public override Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
return Task.FromResult(FileHelper.DeleteIfExists(filePath));
}
public override Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
return ExistsAsync(filePath);
}
public override Task<Stream> GetOrNullAsync(BlobProviderGetArgs args)
{
var filePath = FilePathCalculator.Calculate(args);
if (!File.Exists(filePath))
{
return Task.FromResult<Stream>(null);
}
return Task.FromResult<Stream>(File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
protected virtual Task<bool> ExistsAsync(string filePath)
{
return Task.FromResult(File.Exists(filePath));
}
}
}

27
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfiguration.cs

@ -0,0 +1,27 @@
namespace Volo.Abp.BlobStoring.FileSystem
{
public class FileSystemBlobProviderConfiguration
{
public string BasePath
{
get => _containerConfiguration.GetConfiguration<string>(FileSystemBlobProviderConfigurationNames.BasePath);
set => _containerConfiguration.SetConfiguration(FileSystemBlobProviderConfigurationNames.BasePath, Check.NotNullOrWhiteSpace(value, nameof(value)));
}
/// <summary>
/// Default value: true.
/// </summary>
public bool AppendContainerNameToBasePath
{
get => _containerConfiguration.GetConfigurationOrDefault(FileSystemBlobProviderConfigurationNames.AppendContainerNameToBasePath, true);
set => _containerConfiguration.SetConfiguration(FileSystemBlobProviderConfigurationNames.AppendContainerNameToBasePath, value);
}
private readonly BlobContainerConfiguration _containerConfiguration;
public FileSystemBlobProviderConfiguration(BlobContainerConfiguration containerConfiguration)
{
_containerConfiguration = containerConfiguration;
}
}
}

8
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobProviderConfigurationNames.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring.FileSystem
{
public static class FileSystemBlobProviderConfigurationNames
{
public const string BasePath = "FileSystem.BasePath";
public const string AppendContainerNameToBasePath = "FileSystem.AppendContainerNameToBasePath";
}
}

7
framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/IBlobFilePathCalculator.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring.FileSystem
{
public interface IBlobFilePathCalculator
{
string Calculate(BlobProviderArgs args);
}
}

3
framework/src/Volo.Abp.BlobStoring/FodyWeavers.xml

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

30
framework/src/Volo.Abp.BlobStoring/FodyWeavers.xsd

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

23
framework/src/Volo.Abp.BlobStoring/Volo.Abp.BlobStoring.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.BlobStoring</AssemblyName>
<PackageId>Volo.Abp.BlobStoring</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\Volo.Abp.Threading\Volo.Abp.Threading.csproj" />
</ItemGroup>
</Project>

28
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringModule.cs

@ -0,0 +1,28 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
[DependsOn(
typeof(AbpMultiTenancyModule),
typeof(AbpThreadingModule)
)]
public class AbpBlobStoringModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(
typeof(IBlobContainer<>),
typeof(BlobContainer<>)
);
context.Services.AddTransient(
typeof(IBlobContainer),
serviceProvider => serviceProvider
.GetRequiredService<IBlobContainer<DefaultContainer>>()
);
}
}
}

12
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/AbpBlobStoringOptions.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.BlobStoring
{
public class AbpBlobStoringOptions
{
public BlobContainerConfigurations Containers { get; }
public AbpBlobStoringOptions()
{
Containers = new BlobContainerConfigurations();
}
}
}

31
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobAlreadyExistsException.cs

@ -0,0 +1,31 @@
using System;
using System.Runtime.Serialization;
namespace Volo.Abp.BlobStoring
{
public class BlobAlreadyExistsException : AbpException
{
public BlobAlreadyExistsException()
{
}
public BlobAlreadyExistsException(string message)
: base(message)
{
}
public BlobAlreadyExistsException(string message, Exception innerException)
: base(message, innerException)
{
}
public BlobAlreadyExistsException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
}
}

198
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainer.cs

@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
public class BlobContainer<TContainer> : IBlobContainer<TContainer>
where TContainer : class
{
private readonly IBlobContainer _container;
public BlobContainer(IBlobContainerFactory blobContainerFactory)
{
_container = blobContainerFactory.Create<TContainer>();
}
public Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default)
{
return _container.SaveAsync(
name,
stream,
overrideExisting,
cancellationToken
);
}
public Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.DeleteAsync(
name,
cancellationToken
);
}
public Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.ExistsAsync(
name,
cancellationToken
);
}
public Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.GetAsync(
name,
cancellationToken
);
}
public Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default)
{
return _container.GetOrNullAsync(
name,
cancellationToken
);
}
}
public class BlobContainer : IBlobContainer
{
protected string ContainerName { get; }
protected BlobContainerConfiguration Configuration { get; }
protected IBlobProvider Provider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public BlobContainer(
string containerName,
BlobContainerConfiguration configuration,
IBlobProvider provider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider)
{
ContainerName = containerName;
Configuration = configuration;
Provider = provider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
}
public virtual Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default)
{
return Provider.SaveAsync(
new BlobProviderSaveArgs(
ContainerName,
Configuration,
name,
stream,
overrideExisting,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.DeleteAsync(
new BlobProviderDeleteArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.ExistsAsync(
new BlobProviderExistsArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
public virtual async Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default)
{
var stream = await Provider.GetOrNullAsync(
new BlobProviderGetArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
if (stream == null)
{
//TODO: Consider to throw some type of "not found" exception and handle on the HTTP status side
throw new AbpException($"Could not found the requested BLOB '{name}' in the container '{ContainerName}'!");
}
return stream;
}
public virtual Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default)
{
return Provider.GetOrNullAsync(
new BlobProviderGetArgs(
ContainerName,
Configuration,
name,
GetTenantIdOrNull(),
CancellationTokenProvider.FallbackToProvider(cancellationToken)
)
);
}
protected virtual Guid? GetTenantIdOrNull()
{
if (!Configuration.IsMultiTenant)
{
return null;
}
return CurrentTenant.Id;
}
}
}

71
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfiguration.cs

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerConfiguration
{
/// <summary>
/// The provider to be used to store BLOBs of this container.
/// </summary>
public Type ProviderType { get; set; }
/// <summary>
/// Indicates whether this container is multi-tenant or not.
///
/// If this is <code>false</code> and your application is multi-tenant,
/// then the container is shared by all tenants in the system.
///
/// This can be <code>true</code> even if your application is not multi-tenant.
///
/// Default: true.
/// </summary>
public bool IsMultiTenant { get; set; } = true;
[NotNull] private readonly Dictionary<string, object> _properties;
[CanBeNull] private readonly BlobContainerConfiguration _fallbackConfiguration;
public BlobContainerConfiguration(BlobContainerConfiguration fallbackConfiguration = null)
{
_fallbackConfiguration = fallbackConfiguration;
_properties = new Dictionary<string, object>();
}
[CanBeNull]
public T GetConfigurationOrDefault<T>(string name, T defaultValue = default)
{
return (T) GetConfigurationOrNull(name, defaultValue);
}
[CanBeNull]
public object GetConfigurationOrNull(string name, object defaultValue = null)
{
return _properties.GetOrDefault(name) ??
_fallbackConfiguration?.GetConfigurationOrNull(name, defaultValue) ??
defaultValue;
}
[NotNull]
public BlobContainerConfiguration SetConfiguration([NotNull] string name, [CanBeNull] object value)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNull(value, nameof(value));
_properties[name] = value;
return this;
}
[NotNull]
public BlobContainerConfiguration ClearConfiguration([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
_properties.Remove(name);
return this;
}
}
}

27
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationExtensions.cs

@ -0,0 +1,27 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerConfigurationExtensions
{
public static T GetConfiguration<T>(
[NotNull] this BlobContainerConfiguration containerConfiguration,
[NotNull] string name)
{
return (T) containerConfiguration.GetConfiguration(name);
}
public static object GetConfiguration(
[NotNull] this BlobContainerConfiguration containerConfiguration,
[NotNull] string name)
{
var value = containerConfiguration.GetConfigurationOrNull(name);
if (value == null)
{
throw new AbpException($"Could not find the configuration value for '{name}'!");
}
return value;
}
}
}

11
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurationProviderExtensions.cs

@ -0,0 +1,11 @@
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerConfigurationProviderExtensions
{
public static BlobContainerConfiguration Get<TContainer>(
this IBlobContainerConfigurationProvider configurationProvider)
{
return configurationProvider.Get(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
}
}

79
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerConfigurations.cs

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerConfigurations
{
private BlobContainerConfiguration Default => GetConfiguration<DefaultContainer>();
private readonly Dictionary<string, BlobContainerConfiguration> _containers;
public BlobContainerConfigurations()
{
_containers = new Dictionary<string, BlobContainerConfiguration>
{
//Add default container
[BlobContainerNameAttribute.GetContainerName<DefaultContainer>()] = new BlobContainerConfiguration()
};
}
public BlobContainerConfigurations Configure<TContainer>(
Action<BlobContainerConfiguration> configureAction)
{
return Configure(
BlobContainerNameAttribute.GetContainerName<TContainer>(),
configureAction
);
}
public BlobContainerConfigurations Configure(
[NotNull] string name,
[NotNull] Action<BlobContainerConfiguration> configureAction)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNull(configureAction, nameof(configureAction));
configureAction(
_containers.GetOrAdd(
name,
() => new BlobContainerConfiguration(Default)
)
);
return this;
}
public BlobContainerConfigurations ConfigureDefault(Action<BlobContainerConfiguration> configureAction)
{
configureAction(Default);
return this;
}
public BlobContainerConfigurations ConfigureAll(Action<string, BlobContainerConfiguration> configureAction)
{
foreach (var container in _containers)
{
configureAction(container.Key, container.Value);
}
return this;
}
[NotNull]
public BlobContainerConfiguration GetConfiguration<TContainer>()
{
return GetConfiguration(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
[NotNull]
public BlobContainerConfiguration GetConfiguration([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
return _containers.GetOrDefault(name) ??
Default;
}
}
}

57
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerExtensions.cs

@ -0,0 +1,57 @@
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerExtensions
{
public static async Task SaveAsync(
this IBlobContainer container,
string name,
byte[] bytes,
bool overrideExisting = false,
CancellationToken cancellationToken = default
)
{
using (var memoryStream = new MemoryStream(bytes))
{
await container.SaveAsync(
name,
memoryStream,
overrideExisting,
cancellationToken
);
}
}
public static async Task<byte[]> GetAllBytesAsync(
this IBlobContainer container,
string name,
CancellationToken cancellationToken = default)
{
using (var stream = await container.GetAsync(name, cancellationToken))
{
return await stream.GetAllBytesAsync(cancellationToken);
}
}
public static async Task<byte[]> GetAllBytesOrNullAsync(
this IBlobContainer container,
string name,
CancellationToken cancellationToken = default)
{
var stream = await container.GetOrNullAsync(name, cancellationToken);
if (stream == null)
{
return null;
}
using (stream)
{
return await stream.GetAllBytesAsync(cancellationToken);
}
}
}
}

43
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactory.cs

@ -0,0 +1,43 @@
using System.Threading;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerFactory : IBlobContainerFactory, ITransientDependency
{
protected IBlobProviderSelector ProviderSelector { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public BlobContainerFactory(
IBlobContainerConfigurationProvider configurationProvider,
ICurrentTenant currentTenant,
ICancellationTokenProvider cancellationTokenProvider,
IBlobProviderSelector providerSelector)
{
ConfigurationProvider = configurationProvider;
CurrentTenant = currentTenant;
CancellationTokenProvider = cancellationTokenProvider;
ProviderSelector = providerSelector;
}
public virtual IBlobContainer Create(string name, CancellationToken cancellationToken = default)
{
var configuration = ConfigurationProvider.Get(name);
return new BlobContainer(
name,
configuration,
ProviderSelector.Get(name),
CurrentTenant,
CancellationTokenProvider
);
}
}
}

26
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerFactoryExtensions.cs

@ -0,0 +1,26 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
{
public static class BlobContainerFactoryExtensions
{
/// <summary>
/// Gets a named container.
/// </summary>
/// <param name="blobContainerFactory">The blob container manager</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// The container object.
/// </returns>
public static IBlobContainer Create<TContainer>(
this IBlobContainerFactory blobContainerFactory,
CancellationToken cancellationToken = default
)
{
return blobContainerFactory.Create(
BlobContainerNameAttribute.GetContainerName<TContainer>(),
cancellationToken
);
}
}
}

41
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobContainerNameAttribute.cs

@ -0,0 +1,41 @@
using System;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobContainerNameAttribute : Attribute
{
[NotNull]
public string Name { get; }
public BlobContainerNameAttribute([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
Name = name;
}
public virtual string GetName(Type type)
{
return Name;
}
public static string GetContainerName<T>()
{
return GetContainerName(typeof(T));
}
public static string GetContainerName(Type type)
{
var nameAttribute = type.GetCustomAttribute<BlobContainerNameAttribute>();
if (nameAttribute == null)
{
return type.FullName;
}
return nameAttribute.GetName(type);
}
}
}

36
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderArgs.cs

@ -0,0 +1,36 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public abstract class BlobProviderArgs
{
[NotNull]
public string ContainerName { get; }
[NotNull]
public BlobContainerConfiguration Configuration { get; }
[NotNull]
public string BlobName { get; }
public CancellationToken CancellationToken { get; }
public Guid? TenantId { get; }
protected BlobProviderArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
{
ContainerName = Check.NotNullOrWhiteSpace(containerName, nameof(containerName));
Configuration = Check.NotNull(configuration, nameof(configuration));
BlobName = Check.NotNullOrWhiteSpace(blobName, nameof(blobName));
TenantId = tenantId;
CancellationToken = cancellationToken;
}
}
}

16
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderBase.cs

@ -0,0 +1,16 @@
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public abstract class BlobProviderBase : IBlobProvider
{
public abstract Task SaveAsync(BlobProviderSaveArgs args);
public abstract Task<bool> DeleteAsync(BlobProviderDeleteArgs args);
public abstract Task<bool> ExistsAsync(BlobProviderExistsArgs args);
public abstract Task<Stream> GetOrNullAsync(BlobProviderGetArgs args);
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderDeleteArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderDeleteArgs : BlobProviderArgs
{
public BlobProviderDeleteArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderExistsArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderExistsArgs : BlobProviderArgs
{
public BlobProviderExistsArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

24
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderGetArgs.cs

@ -0,0 +1,24 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderGetArgs : BlobProviderArgs
{
public BlobProviderGetArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
}
}
}

34
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSaveArgs.cs

@ -0,0 +1,34 @@
using System;
using System.IO;
using System.Threading;
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public class BlobProviderSaveArgs : BlobProviderArgs
{
[NotNull]
public Stream BlobStream { get; }
public bool OverrideExisting { get; }
public BlobProviderSaveArgs(
[NotNull] string containerName,
[NotNull] BlobContainerConfiguration configuration,
[NotNull] string blobName,
[NotNull] Stream blobStream,
bool overrideExisting = false,
[CanBeNull] Guid? tenantId = null,
CancellationToken cancellationToken = default)
: base(
containerName,
configuration,
blobName,
tenantId,
cancellationToken)
{
BlobStream = Check.NotNull(blobStream, nameof(blobStream));
OverrideExisting = overrideExisting;
}
}
}

15
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobProviderSelectorExtensions.cs

@ -0,0 +1,15 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public static class BlobProviderSelectorExtensions
{
public static IBlobProvider Get<TContainer>(
[NotNull] this IBlobProviderSelector selector)
{
Check.NotNull(selector, nameof(selector));
return selector.Get(BlobContainerNameAttribute.GetContainerName<TContainer>());
}
}
}

20
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobContainerConfigurationProvider.cs

@ -0,0 +1,20 @@
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring
{
public class DefaultBlobContainerConfigurationProvider : IBlobContainerConfigurationProvider, ITransientDependency
{
protected AbpBlobStoringOptions Options { get; }
public DefaultBlobContainerConfigurationProvider(IOptions<AbpBlobStoringOptions> options)
{
Options = options.Value;
}
public virtual BlobContainerConfiguration Get(string name)
{
return Options.Containers.GetConfiguration(name);
}
}
}

49
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultBlobProviderSelector.cs

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.BlobStoring
{
public class DefaultBlobProviderSelector : IBlobProviderSelector, ITransientDependency
{
protected IEnumerable<IBlobProvider> BlobProviders { get; }
protected IBlobContainerConfigurationProvider ConfigurationProvider { get; }
public DefaultBlobProviderSelector(
IBlobContainerConfigurationProvider configurationProvider,
IEnumerable<IBlobProvider> blobProviders)
{
ConfigurationProvider = configurationProvider;
BlobProviders = blobProviders;
}
[NotNull]
public virtual IBlobProvider Get([NotNull] string containerName)
{
Check.NotNull(containerName, nameof(containerName));
var configuration = ConfigurationProvider.Get(containerName);
if (!BlobProviders.Any())
{
throw new AbpException("No BLOB Storage provider was registered! At least one provider must be registered to be able to use the Blog Storing System.");
}
foreach (var provider in BlobProviders)
{
if (ProxyHelper.GetUnProxiedType(provider).IsAssignableTo(configuration.ProviderType))
{
return provider;
}
}
throw new AbpException(
$"Could not find the BLOB Storage provider with the type ({configuration.ProviderType.AssemblyQualifiedName}) configured for the container {containerName} and no default provider was set."
);
}
}
}

8
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/DefaultContainer.cs

@ -0,0 +1,8 @@
namespace Volo.Abp.BlobStoring
{
[BlobContainerName("Default")]
public class DefaultContainer
{
}
}

88
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainer.cs

@ -0,0 +1,88 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainer<TContainer> : IBlobContainer
where TContainer: class
{
}
public interface IBlobContainer
{
/// <summary>
/// Saves a blob <see cref="Stream"/> to the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="stream">A stream for the blob</param>
/// <param name="overrideExisting">
/// Set <code>true</code> to override if there is already a blob in the container with the given name.
/// If set to <code>false</code> (default), throws exception if there is already a blob in the container with the given name.
/// </param>
/// <param name="cancellationToken">Cancellation token</param>
Task SaveAsync(
string name,
Stream stream,
bool overrideExisting = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// Deletes a blob from the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// Returns true if actually deleted the blob.
/// Returns false if the blob with the given <paramref name="name"/> was not exists.
/// </returns>
Task<bool> DeleteAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Checks if a blob does exists in the container.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
Task<bool> ExistsAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Gets a blob from the container.
/// It actually gets a <see cref="Stream"/> to read the blob data.
/// It throws exception if there is no blob with the given <paramref name="name"/>.
/// Use <see cref="GetOrNullAsync"/> if you want to get <code>null</code> if there is no blob with the given <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// A <see cref="Stream"/> to read the blob data.
/// </returns>
Task<Stream> GetAsync(
string name,
CancellationToken cancellationToken = default
);
/// <summary>
/// Gets a blob from the container, or returns null if there is no blob with the given <paramref name="name"/>.
/// It actually gets a <see cref="Stream"/> to read the blob data.
/// </summary>
/// <param name="name">The name of the blob</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// A <see cref="Stream"/> to read the blob data.
/// </returns>
Task<Stream> GetOrNullAsync(
string name,
CancellationToken cancellationToken = default
);
//TODO: Create shortcut extension methods: GetAsArraryAsync, GetAsStringAsync(encoding) (and null versions)
}
}

7
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerConfigurationProvider.cs

@ -0,0 +1,7 @@
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainerConfigurationProvider
{
BlobContainerConfiguration Get(string name);
}
}

20
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobContainerFactory.cs

@ -0,0 +1,20 @@
using System.Threading;
namespace Volo.Abp.BlobStoring
{
public interface IBlobContainerFactory
{
/// <summary>
/// Gets a named container.
/// </summary>
/// <param name="name">The name of the container</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>
/// The container object.
/// </returns>
IBlobContainer Create(
string name,
CancellationToken cancellationToken = default
);
}
}

16
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProvider.cs

@ -0,0 +1,16 @@
using System.IO;
using System.Threading.Tasks;
namespace Volo.Abp.BlobStoring
{
public interface IBlobProvider
{
Task SaveAsync(BlobProviderSaveArgs args);
Task<bool> DeleteAsync(BlobProviderDeleteArgs args);
Task<bool> ExistsAsync(BlobProviderExistsArgs args);
Task<Stream> GetOrNullAsync(BlobProviderGetArgs args);
}
}

10
framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/IBlobProviderSelector.cs

@ -0,0 +1,10 @@
using JetBrains.Annotations;
namespace Volo.Abp.BlobStoring
{
public interface IBlobProviderSelector
{
[NotNull]
IBlobProvider Get([NotNull] string containerName);
}
}

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs

@ -28,11 +28,12 @@ namespace Volo.Abp.Cli
options.Commands["add-package"] = typeof(AddPackageCommand);
options.Commands["add-module"] = typeof(AddModuleCommand);
options.Commands["login"] = typeof(LoginCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands["generate-proxy"] = typeof(GenerateProxyCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands["generate-proxy"] = typeof(GenerateProxyCommand);
options.Commands["suite"] = typeof(SuiteCommand);
options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand);
options.Commands["switch-to-stable"] = typeof(SwitchStableCommand);
options.Commands["switch-to-stable"] = typeof(SwitchStableCommand);
options.Commands["translate"] = typeof(TranslateCommand);
});
}
}

444
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs

@ -0,0 +1,444 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Args;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands
{
public class TranslateCommand: IConsoleCommand, ITransientDependency
{
public ILogger<TranslateCommand> Logger { get; set; }
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var currentDirectory = Directory.GetCurrentDirectory();
var apply = commandLineArgs.Options.ContainsKey(Options.Apply.Short) || commandLineArgs.Options.ContainsKey(Options.Apply.Long);
if (apply)
{
var inputFile = Path.Combine(currentDirectory,
commandLineArgs.Options.GetOrNull(Options.File.Short, Options.File.Long)
?? "abp-translation.json");
Logger.LogInformation("Abp translate apply...");
Logger.LogInformation("Input file: " + inputFile);
ApplyAbpTranslateInfo(currentDirectory, inputFile);
}
else
{
var targetCulture = commandLineArgs.Options.GetOrNull(Options.Culture.Short, Options.Culture.Long);
if (targetCulture == null)
{
throw new CliUsageException(
"Target culture is missing!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var referenceCulture = commandLineArgs.Options.GetOrNull(Options.ReferenceCulture.Short, Options.ReferenceCulture.Long)
?? "en";
var outputFile = Path.Combine(currentDirectory,
commandLineArgs.Options.GetOrNull(Options.Output.Short, Options.Output.Long)
?? "abp-translation.json");
var allValues = commandLineArgs.Options.ContainsKey(Options.AllValues.Short) ||
commandLineArgs.Options.ContainsKey(Options.AllValues.Long);
Logger.LogInformation("Abp translate...");
Logger.LogInformation("Target culture: " + targetCulture);
Logger.LogInformation("Reference culture: " + referenceCulture);
Logger.LogInformation("Output file: " + outputFile);
if (allValues)
{
Logger.LogInformation("Include all keys");
}
var translateInfo = GetAbpTranslateInfo(currentDirectory, targetCulture, referenceCulture, allValues);
File.WriteAllText(outputFile, JsonConvert.SerializeObject(translateInfo, Formatting.Indented));
Logger.LogInformation($"The translation file has been created.");
}
return Task.CompletedTask;
}
private AbpTranslateInfo GetAbpTranslateInfo(string directory, string targetCultureName, string referenceCultureName, bool allValues)
{
var translateInfo = new AbpTranslateInfo
{
ReferenceCulture = referenceCultureName,
TargetCulture = targetCultureName,
Resources = new List<AbpTranslateResource>()
};
var referenceCultureFiles = GetCultureJsonFiles(directory, referenceCultureName);
foreach (var filePath in referenceCultureFiles)
{
var directoryName = Path.GetDirectoryName(filePath) ?? string.Empty;
var referenceLocalizationInfo = GetAbpLocalizationInfoOrNull(filePath);
if (referenceLocalizationInfo == null) // Not abp json file
{
continue;
}
var resource = new AbpTranslateResource
{
ResourcePath = directoryName,
Texts = new List<AbpTranslateResourceText>()
};
foreach (var text in referenceLocalizationInfo.Texts)
{
resource.Texts.Add(new AbpTranslateResourceText
{
LocalizationKey = text.Name,
Reference = text.Value,
Target = string.Empty
});
}
//Use target json file content to fill resource texts
var targetFile = Path.Combine(directoryName, $"{targetCultureName}.json");
if (File.Exists(targetFile))
{
var targetLocalizationInfo = GetAbpLocalizationInfoOrNull(targetFile);
foreach (var referenceResourceText in resource.Texts)
{
var text = targetLocalizationInfo.Texts.FirstOrDefault(x => x.Name == referenceResourceText.LocalizationKey);
referenceResourceText.Target = text?.Value ?? string.Empty;
}
}
if (!allValues)
{
//Only include missing keys.
resource.Texts.RemoveAll(x => !x.Target.Equals(string.Empty));
}
if (resource.Texts.Any())
{
translateInfo.Resources.Add(resource);
}
}
return translateInfo;
}
private void ApplyAbpTranslateInfo(string directory, string filename)
{
var translateJsonPath = Path.Combine(directory, filename);
if (!File.Exists(translateJsonPath))
{
throw new CliUsageException(
$"{translateJsonPath} file does not exist.." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var translateInfo = GetAbpTranslateInfo(translateJsonPath);
foreach (var resource in translateInfo.Resources)
{
var targetFile = Path.Combine(resource.ResourcePath, translateInfo.TargetCulture + ".json");
var targetLocalizationInfo = File.Exists(targetFile)
? GetAbpLocalizationInfoOrNull(targetFile)
: new AbpLocalizationInfo()
{
Culture = translateInfo.TargetCulture,
Texts = new List<NameValue>()
};
if (targetLocalizationInfo == null)
{
throw new CliUsageException(
$"Failed to get localization information from {targetFile} file." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var referenceFile = Path.Combine(resource.ResourcePath, translateInfo.ReferenceCulture + ".json");
if (!File.Exists(referenceFile))
{
throw new CliUsageException(
$"{referenceFile} file does not exist.." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var referenceLocalizationInfo = GetAbpLocalizationInfoOrNull(referenceFile);
if (referenceLocalizationInfo == null)
{
throw new CliUsageException(
$"Failed to get localization information from {referenceFile} file." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
foreach (var text in resource.Texts)
{
var targetText = targetLocalizationInfo.Texts.FirstOrDefault(x => x.Name == text.LocalizationKey);
if (targetText != null)
{
Logger.LogInformation($"Update translation: {targetText.Name} => " + text.Target);
targetText.Value = text.Target;
}
else
{
Logger.LogInformation($"Create translation: {text.LocalizationKey} => " + text.Target);
targetLocalizationInfo.Texts.Add(new NameValue(text.LocalizationKey, text.Target));
}
}
Logger.LogInformation($"Write translation json to {targetFile}.");
// sort keys
targetLocalizationInfo = SortLocalizedKeys(targetLocalizationInfo, referenceLocalizationInfo);
File.WriteAllText(targetFile, AbpLocalizationInfoToJsonFile(targetLocalizationInfo));
// remove translate json file(abp-translation.json)
File.Delete(translateJsonPath);
Logger.LogInformation($"Delete the {translateJsonPath} file, if you need to translate again, please re-run the [abp translate] command.");
}
}
private static IEnumerable<string> GetCultureJsonFiles(string path,string cultureName)
{
var excludeDirectory = new List<string>()
{
"node_modules",
Path.Combine("bin", "debug"),
Path.Combine("obj", "debug")
};
var allCultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures);
return Directory.GetFiles(path, "*.json", SearchOption.AllDirectories)
.Where(file => excludeDirectory.All(x => file.IndexOf(x, StringComparison.OrdinalIgnoreCase) == -1))
.Where(jsonFile => allCultureInfos.Any(culture => jsonFile.EndsWith($"{cultureName}.json", StringComparison.OrdinalIgnoreCase)));
}
private AbpLocalizationInfo GetAbpLocalizationInfoOrNull(string path)
{
if (!File.Exists(path))
{
throw new CliUsageException(
$"File {path} does not exist!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var json = File.ReadAllText(path);
JObject jObject;
try
{
jObject = JObject.Parse(json);
}
catch (Exception e)
{
return null;
}
var culture = jObject.GetValue("culture");
var texts = jObject.GetValue("texts");
if (culture == null || texts == null)
{
return null;
}
var localizationInfo = new AbpLocalizationInfo
{
Culture = culture.Value<string>(),
Texts = new List<NameValue>()
};
foreach (var text in texts)
{
var property = (text as JProperty);
localizationInfo.Texts.Add(new NameValue(property?.Name, property?.Value.Value<string>()));
}
return localizationInfo;
}
private static string AbpLocalizationInfoToJsonFile(AbpLocalizationInfo localizationInfo)
{
var jObject = new JObject {{"culture", localizationInfo.Culture}};
var value = new JObject();
foreach (var text in localizationInfo.Texts)
{
value.Add(text.Name, text.Value);
}
jObject.Add("texts", value);
return jObject.ToString();
}
private AbpTranslateInfo GetAbpTranslateInfo(string path)
{
if (!File.Exists(path))
{
throw new CliUsageException(
$"File {path} does not exist!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
AbpTranslateInfo translateInfo;
try
{
translateInfo = JsonConvert.DeserializeObject<AbpTranslateInfo>(File.ReadAllText(path));
}
catch (Exception e)
{
throw new CliUsageException(
e.Message +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
return translateInfo;
}
private static AbpLocalizationInfo SortLocalizedKeys(AbpLocalizationInfo targetLocalizationInfo, AbpLocalizationInfo referenceLocalizationInfo)
{
var sortedLocalizationInfo = new AbpLocalizationInfo
{
Culture = targetLocalizationInfo.Culture,
Texts = new List<NameValue>()
};
foreach (var targetText in referenceLocalizationInfo.Texts.Select(text =>
targetLocalizationInfo.Texts.FirstOrDefault(x => x.Name == text.Name))
.Where(targetText => targetText != null))
{
sortedLocalizationInfo.Texts.Add(targetText);
}
return sortedLocalizationInfo;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("");
sb.AppendLine(" abp translate [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("");
sb.AppendLine("--culture|-c <culture> Target culture. eg: zh-Hans");
sb.AppendLine("--reference-culture|-r <culture> Default: en");
sb.AppendLine("--output|-o <file-name> Output file name, Default abp-translation.json");
sb.AppendLine("--all-values|-all Include all keys. Default false");
sb.AppendLine("--apply|-a Creates or updates the file for the translated culture.");
sb.AppendLine("--file|-f <file-name> Default: abp-translation.json");
sb.AppendLine("");
sb.AppendLine("Examples:");
sb.AppendLine("");
sb.AppendLine(" abp translate -c zh-Hans");
sb.AppendLine(" abp translate -c zh-Hans -r en -a");
sb.AppendLine(" abp translate --apply");
sb.AppendLine(" abp translate -a -f my-translation.json");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
return sb.ToString();
}
public string GetShortDescription()
{
return "Mainly used to translate ABP's resources (JSON files) easier.";
}
public static class Options
{
public static class Culture
{
public const string Short = "c";
public const string Long = "culture";
}
public static class ReferenceCulture
{
public const string Short = "r";
public const string Long = "reference-culture";
}
public static class Output
{
public const string Short = "o";
public const string Long = "output";
}
public static class AllValues
{
public const string Short = "all";
public const string Long = "all-values";
}
public static class Apply
{
public const string Short = "a";
public const string Long = "apply";
}
public static class File
{
public const string Short = "f";
public const string Long = "file";
}
}
public class AbpTranslateInfo
{
public string ReferenceCulture { get; set; }
public string TargetCulture { get; set; }
public List<AbpTranslateResource> Resources { get; set; }
}
public class AbpTranslateResource
{
public string ResourcePath { get; set; }
public List<AbpTranslateResourceText> Texts { get; set; }
}
public class AbpTranslateResourceText
{
public string LocalizationKey { get; set; }
public string Reference { get; set; }
public string Target { get; set; }
}
public class AbpLocalizationInfo
{
public string Culture { get; set; }
public List<NameValue> Texts { get; set; }
}
}
}

5
framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Events;
using System.IO;
@ -10,6 +11,8 @@ namespace Volo.Abp.Cli
{
private static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)

45
framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs

@ -42,5 +42,50 @@ namespace System
{
return list.Contains(item);
}
/// <summary>
/// Can be used to conditionally perform a function
/// on an object and return the modified or the original object.
/// It is useful for chained calls.
/// </summary>
/// <param name="obj">An object</param>
/// <param name="condition">A condition</param>
/// <param name="func">A function that is executed only if the condition is <code>true</code></param>
/// <typeparam name="T">Type of the object</typeparam>
/// <returns>
/// Returns the modified object (by the <paramref name="func"/> if the <paramref name="condition"/> is <code>true</code>)
/// or the original object if the <paramref name="condition"/> is <code>false</code>
/// </returns>
public static T If<T>(this T obj, bool condition, Func<T, T> func)
{
if (condition)
{
return func(obj);
}
return obj;
}
/// <summary>
/// Can be used to conditionally perform an action
/// on an object and return the original object.
/// It is useful for chained calls on the object.
/// </summary>
/// <param name="obj">An object</param>
/// <param name="condition">A condition</param>
/// <param name="action">An action that is executed only if the condition is <code>true</code></param>
/// <typeparam name="T">Type of the object</typeparam>
/// <returns>
/// Returns the original object.
/// </returns>
public static T If<T>(this T obj, bool condition, Action<T> action)
{
if (condition)
{
action(obj);
}
return obj;
}
}
}

16
framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO
{
@ -13,13 +14,22 @@ namespace System.IO
}
}
public static async Task<byte[]> GetAllBytesAsync(this Stream stream)
public static async Task<byte[]> GetAllBytesAsync(this Stream stream, CancellationToken cancellationToken = default)
{
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
await stream.CopyToAsync(memoryStream, cancellationToken);
return memoryStream.ToArray();
}
}
public static Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken)
{
return stream.CopyToAsync(
destination,
81920, //this is already the default value, but needed to set to be able to pass the cancellationToken
cancellationToken
);
}
}
}

15
framework/src/Volo.Abp.Core/Volo/Abp/AbpException.cs

@ -8,38 +8,23 @@ namespace Volo.Abp
/// </summary>
public class AbpException : Exception
{
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
public AbpException()
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
public AbpException(string message)
: base(message)
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public AbpException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Constructor for serializing.
/// </summary>
public AbpException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{

15
framework/src/Volo.Abp.Core/Volo/Abp/AbpInitializationException.cs

@ -5,38 +5,23 @@ namespace Volo.Abp
{
public class AbpInitializationException : AbpException
{
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
public AbpInitializationException()
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
public AbpInitializationException(string message)
: base(message)
{
}
/// <summary>
/// Creates a new <see cref="AbpException"/> object.
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="innerException">Inner exception</param>
public AbpInitializationException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Constructor for serializing.
/// </summary>
public AbpInitializationException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{

31
framework/src/Volo.Abp.Core/Volo/Abp/AbpShutdownException.cs

@ -0,0 +1,31 @@
using System;
using System.Runtime.Serialization;
namespace Volo.Abp
{
public class AbpShutdownException : AbpException
{
public AbpShutdownException()
{
}
public AbpShutdownException(string message)
: base(message)
{
}
public AbpShutdownException(string message, Exception innerException)
: base(message, innerException)
{
}
public AbpShutdownException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
}
}

9
framework/src/Volo.Abp.Core/Volo/Abp/DynamicProxy/ProxyHelper.cs

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.DynamicProxy
@ -6,6 +7,7 @@ namespace Volo.Abp.DynamicProxy
public static class ProxyHelper
{
private const string ProxyNamespace = "Castle.Proxies";
/// <summary>
/// Returns dynamic proxy target object if this is a proxied object, otherwise returns the given object.
/// It supports Castle Dynamic Proxies.
@ -28,5 +30,10 @@ namespace Volo.Abp.DynamicProxy
return targetField.GetValue(obj);
}
public static Type GetUnProxiedType(object obj)
{
return UnProxy(obj).GetType();
}
}
}

34
framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs

@ -17,6 +17,30 @@ namespace Volo.Abp.IO
}
}
public static void DeleteIfExists(string directory)
{
if (Directory.Exists(directory))
{
Directory.Delete(directory);
}
}
public static void DeleteIfExists(string directory, bool recursive)
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive);
}
}
public static void CreateIfNotExists(DirectoryInfo directory)
{
if (!directory.Exists)
{
directory.Create();
}
}
public static bool IsSubDirectoryOf([NotNull] string parentDirectoryPath, [NotNull] string childDirectoryPath)
{
Check.NotNull(parentDirectoryPath, nameof(parentDirectoryPath));
@ -28,7 +52,8 @@ namespace Volo.Abp.IO
);
}
public static bool IsSubDirectoryOf([NotNull] DirectoryInfo parentDirectory, [NotNull] DirectoryInfo childDirectory)
public static bool IsSubDirectoryOf([NotNull] DirectoryInfo parentDirectory,
[NotNull] DirectoryInfo childDirectory)
{
Check.NotNull(parentDirectory, nameof(parentDirectory));
Check.NotNull(childDirectory, nameof(childDirectory));
@ -58,10 +83,7 @@ namespace Volo.Abp.IO
Directory.SetCurrentDirectory(targetDirectory);
return new DisposeAction(() =>
{
Directory.SetCurrentDirectory(currentDirectory);
});
return new DisposeAction(() => { Directory.SetCurrentDirectory(currentDirectory); });
}
}
}
}

9
framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs

@ -17,12 +17,15 @@ namespace Volo.Abp.IO
/// Checks and deletes given file if it does exists.
/// </summary>
/// <param name="filePath">Path of the file</param>
public static void DeleteIfExists(string filePath)
public static bool DeleteIfExists(string filePath)
{
if (File.Exists(filePath))
if (!File.Exists(filePath))
{
File.Delete(filePath);
return false;
}
File.Delete(filePath);
return true;
}
/// <summary>

4
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs

@ -44,7 +44,7 @@ namespace Volo.Abp.Modularity
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex);
}
}
}
@ -76,7 +76,7 @@ namespace Volo.Abp.Modularity
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during the shutdown {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
throw new AbpShutdownException($"An error occurred during the shutdown {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex);
}
}
}

13
framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Microsoft/EntityFrameworkCore/AbpMySqlModelBuilderExtensions.cs

@ -0,0 +1,13 @@
using Volo.Abp.EntityFrameworkCore;
namespace Microsoft.EntityFrameworkCore
{
public static class AbpMySqlModelBuilderExtensions
{
public static void UseMySQL(
this ModelBuilder modelBuilder)
{
modelBuilder.SetDatabaseProvider(EfCoreDatabaseProvider.MySql);
}
}
}

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

Loading…
Cancel
Save