Browse Source

Merge branch 'dev' into EngincanV-berkan/design-localization

pull/11356/head
berkansasmaz 5 years ago
parent
commit
7059b54c80
No known key found for this signature in database GPG Key ID: 884D815C3F32BE00
  1. 2
      abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
  2. 12
      docs/en/CLI.md
  3. 4
      docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md
  4. 321
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md
  5. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/calendar-component.png
  6. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/component-folder-structure.png
  7. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/copy-license-key.png
  8. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-1.png
  9. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-2.png
  10. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-libs.png
  11. BIN
      docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-package.png
  12. 16
      docs/en/Index.md
  13. 14
      docs/en/Migration-Guides/Abp-5-0-Blazor.md
  14. 2
      docs/en/Tutorials/Part-10.md
  15. 22
      docs/en/Tutorials/Part-2.md
  16. 2
      docs/en/Tutorials/Part-9.md
  17. 2
      docs/en/UI/Angular/Service-Proxies.md
  18. BIN
      docs/en/images/abp-book.png
  19. 11
      docs/zh-Hans/CLI.md
  20. 4
      docs/zh-Hans/Distributed-Event-Bus.md
  21. 174
      docs/zh-Hans/Modules/IdentityServer.md
  22. 256
      docs/zh-Hans/Tutorials/Part-2.md
  23. 600
      docs/zh-Hans/Tutorials/Part-3.md
  24. 60
      docs/zh-Hans/Tutorials/Part-4.md
  25. 2
      docs/zh-Hans/Tutorials/Todo/Index.md
  26. BIN
      docs/zh-Hans/Tutorials/images/blazor-add-book-button.png
  27. BIN
      docs/zh-Hans/Tutorials/images/blazor-add-books-component.png
  28. BIN
      docs/zh-Hans/Tutorials/images/blazor-bookstore-book-list.png
  29. BIN
      docs/zh-Hans/Tutorials/images/blazor-delete-book-action.png
  30. BIN
      docs/zh-Hans/Tutorials/images/blazor-edit-book-action-2.png
  31. BIN
      docs/zh-Hans/Tutorials/images/blazor-edit-book-modal.png
  32. BIN
      docs/zh-Hans/Tutorials/images/blazor-menu-bookstore.png
  33. BIN
      docs/zh-Hans/Tutorials/images/blazor-new-book-modal.png
  34. BIN
      docs/zh-Hans/Tutorials/images/generated-proxies-3.png
  35. BIN
      docs/zh-Hans/Tutorials/images/vs-run-without-iisexpress.png
  36. 2
      docs/zh-Hans/UI/Angular/Service-Proxies.md
  37. 32
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs
  38. 112
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs
  39. 2
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
  40. 18
      framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/TestNoopHostLifetime.cs
  41. 23
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs
  42. 16
      framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs
  43. 19
      framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/EmptyHostingEnvironment.cs
  44. 35
      framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/WebApplicationBuilderExtensions.cs
  45. 37
      framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs
  46. 15
      framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs
  47. 8
      framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs
  48. 18
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs
  49. 4
      framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs
  50. 25
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs
  51. 6
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerManager.cs
  52. 11
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs
  53. 5
      framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IBackgroundWorkerManager.cs
  54. 42
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
  55. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs
  56. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs
  57. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs
  58. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs
  59. 64
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs
  60. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs
  61. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs
  62. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs
  63. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs
  64. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs
  65. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs
  66. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs
  67. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs
  68. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  69. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs
  70. 6
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs
  71. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs
  72. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs
  73. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs
  74. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs
  75. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/TranslateCommand.cs
  76. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs
  77. 17
      framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionApplicationExtensions.cs
  78. 125
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
  79. 2
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationCreationOptions.cs
  80. 55
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationFactory.cs
  81. 10
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs
  82. 7
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs
  83. 13
      framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplication.cs
  84. 8
      framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs
  85. 8
      framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithInternalServiceProvider.cs
  86. 5
      framework/src/Volo.Abp.Core/Volo/Abp/IOnApplicationInitialization.cs
  87. 5
      framework/src/Volo.Abp.Core/Volo/Abp/IOnApplicationShutdown.cs
  88. 43
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModule.cs
  89. 36
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/DefaultModuleLifecycleContributor.cs
  90. 6
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs
  91. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IModuleLifecycleContributor.cs
  92. 7
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IModuleManager.cs
  93. 5
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs
  94. 5
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs
  95. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IPostConfigureServices.cs
  96. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IPreConfigureServices.cs
  97. 14
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLifecycleContributorBase.cs
  98. 41
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
  99. 4
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj
  100. 12
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs

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

@ -193,7 +193,7 @@
"WhatHappensWhenLicenseEnds": "What happens when my license period ends?",
"WhatHappensWhenLicenseEndsExplanation1": "ABP Commercial license type is <a href=\"{0}\" target=\"_blank\">perpetual license</a>. After your license expires, you can continue developing your project. And you are not obliged to renew your license. Your license comes with a one-year Updates and Support plan out of the box. To continue to receive new features, performance enhancements, bug fixes, support and continue to use ABP Suite, make sure to renew your plan each year. When your license expires, you will not be able to get more of the following benefits;",
"WhatHappensWhenLicenseEndsExplanation2": "You can not create new solutions using the ABP Commercial, but you can continue to develop your existing applications forever.",
"WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MAJOR version. For example; if you are using v3.2.0 of a module, you can still get updates for v3.x.x (v3.3.0, v3.5.2... etc.) of that module. But you cannot get updates for the next major version (like v4.x, v5.x)",
"WhatHappensWhenLicenseEndsExplanation3": "You will be able to get updates for the modules and themes within your MAJOR version (except RC or Preview versions). For example; if you are using v3.2.0 of a module, you can still get updates for v3.x.x (v3.3.0, v3.5.2... etc.) of that module. But you cannot get updates for the next major version (like v4.x, v5.x). Let's say, when your license expired, the latest release was v4.4.3 and the latest preview version was v5.0.0-rc.2, you can only access the v4.X.X but you cannot access the v5.X.X.",
"WhatHappensWhenLicenseEndsExplanation4": "You can not install new modules and themes added to the ABP Commercial platform after your license ends.",
"WhatHappensWhenLicenseEndsExplanation5": "You can not use the ABP Suite.",
"WhatHappensWhenLicenseEndsExplanation6": "You can not get the <a href=\"{0}\">premium support</a> anymore.",

12
docs/en/CLI.md

@ -29,6 +29,7 @@ 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.
* **`clean`**: Deletes all `BIN` and `OBJ` folders in the current folder.
* **`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.
* **`list-modules`**: Lists names of open-source application modules.
@ -145,6 +146,17 @@ abp update [options]
* `--check-all`: Check the new version of each package separately. Default is `false`.
* `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used.
### clean
Deletes all `BIN` and `OBJ` folders in the current folder.
Usage:
````bash
abp clean
````
### add-package
Adds an ABP package to a project by,

4
docs/en/Community-Articles/2020-09-16-How-to-Setup-Azure-Active-Directory-and-Integrate-Abp-Angular-Application/POST.md

@ -12,7 +12,7 @@ The most common question is;
The answer is, **you don't**. ABP Angular application is integrated with the backend (HttpApi.Host project) where it loads the configurations, **permissions** etc. For none-tiered angular applications, **HttpApi.Host** project also has IdentityServer4 embedded; also serving as **Authorization Server**. Angular application authentication flow is shown below.
<img src="auth-diagram.jpeg" alt="auth-diagram" style="zoom:50%;" />
![auth-diagram](auth-diagram.jpeg)
> What if I want Azure AD as my authorization server and not IdentityServer?
@ -80,7 +80,7 @@ Navigate to Manage Azure Active Directory in [azure portal](https://portal.azure
Enter a name for your application and **App.SelfUrl** + **AzureAd.CallbackPath** as redirect uri then register.
<img src="azure-app-register.JPG" alt="azure-app-register" style="zoom:75%;" />
![azure-app-register](azure-app-register.JPG)
Now navigate to **Authentication** on the left menu and enable **ID tokens**.

321
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/POST.md

@ -0,0 +1,321 @@
# Integrating the Syncfusion MVC Components to the ABP MVC UI
## Introduction
In this article we will see how we can integrate the Syncfusion MVC Components into our ABP application.
## Source Code
You can find the source code of the application at https://github.com/EngincanV/ABP-Syncfusion-Components-Demo.
## Prerequisites
* [.NET 6](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
* In this article, we will create a new startup template in v5.0.0-rc.2 and if you follow this article from top to bottom and create a new startup template with me, you need to install the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) before starting.
**NOTE:** ABP v5.X stable version has been released. You can replace v5.0.0-rc.2 with the latest stable version in your steps.
Also, you need to update your ABP CLI to the v5.0.0-rc.2, you can use the command below to update your CLI version:
```bash
dotnet tool update Volo.Abp.Cli -g --version 5.0.0-rc.2
```
or install it if you haven't installed it before:
```bash
dotnet tool install Volo.Abp.Cli -g --version 5.0.0-rc.2
```
## Creating the Solution
In this article, we will create a new startup template with EF Core as a database provider and MVC for the UI framework. But if you already have a project with MVC UI, you don't need to create a new startup template, you can directly implement the following steps to your existing project.
> If you already have a project with MVC/Razor Pages UI, you can skip this section.
We can create a new startup template by using the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI):
```bash
abp new SyncfusionComponentsDemo -t app --preview
```
Our project boilerplate will be ready after the download is finished. Then, we can open the solution and start developing.
## Starting the Development
### Pre-requisite
> If you've already had a license from Syncfusion, you can skip this section.
* The first thing we need to do is create an account to be able to get a license from Syncfusion.
* So, let's navigate to https://www.syncfusion.com/aspnet-core-ui-controls and click the "Download Free Trial" button.
* Then fill the form and start your 30-day free trial.
* After that, navigate to https://www.syncfusion.com/account/manage-trials/downloads to get our license key that will be used in our application.
![](./manage-trial-1.png)
Click the "Get License Key" link for "ASP.NET Core (Essential JS 2)".
![](./manage-trial-2.png)
Then a modal will be opened like in the above image, select a version and click the "Get License Key" button.
![](./copy-license-key.png)
Lastly, copy the generated license key value.
In order to use the relevant components, Syncfusion needs to check this license key to know that our license is valid.
### Configurations
After providing a license key from Syncfusion, we can start with the configuration that needs to be done in our application.
#### 1-) Install the Syncfusion.EJ2.AspNet.Core package
We need to install the `Syncfusion.EJ2.AspNet.Core` Nuget package to our Web project (*.Web).
We can install it via **Visual Studio's Nuget Package Manager**:
![](./syncfusion-package.png)
or via dotnet cli:
```bash
dotnet add package Syncfusion.EJ2.AspNet.Core --version 19.3.0.57
```
> In this article, I've used the package in version 19.3.0.57.
#### 2-) Register the License Key
* After installing the package, we need to register our license key to be able to use the Syncfusion Components.
* To register the license key, open your web module class and update the `ConfigureServices` method as follows:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
//Register Syncfusion license
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: configuration["Syncfusion:LicenseKey"].ToString());
ConfigureUrls(configuration);
ConfigureBundles();
ConfigureAuthentication(context, configuration);
ConfigureAutoMapper();
ConfigureVirtualFileSystem(hostingEnvironment);
ConfigureLocalizationServices();
ConfigureNavigationServices();
ConfigureAutoApiControllers();
ConfigureSwaggerServices(context.Services);
}
```
Instead of writing the license key directly in here we can define it in the **appsettings.json** file and use it here by using the Configuration system of .NET.
* Open your **appsettings.json** file and add a new section named "Syncfusion" as below:
```json
{
//...
"Syncfusion": {
"LicenseKey": "<your-license-key>"
}
}
```
> Replace the `<your-license-key> part with your license key that we've obtained in the previous section.`
* To be able to use the Syncfusion Components we need to define them in our **_ViewImports.cshtml** file. By doing that we can use the Syncfusion components everywhere in our application.
* Open your **/Pages/_ViewImports.cshtml** file and add a new tag helper:
```cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling
@addTagHelper *, Syncfusion.EJ2 //use Syncfusion components
```
#### 3-) Adding Syncfusion styles and scripts to our application
Firstly, let's install the `@syncfusion/ej2` package from **npm**.
* Open your **package.json** file and add the `@syncfusion/ej2` package with version **19.3.57**:
```json
{
"version": "1.0.0",
"name": "my-app",
"private": true,
"dependencies": {
"@abp/aspnetcore.mvc.ui.theme.basic": "^5.0.0-rc.2",
"@syncfusion/ej2": "^19.3.57"
}
}
```
* Then, open the **abp.resourcemapping.js** file and update the **mappings** section:
```js
module.exports = {
aliases: {
},
mappings: {
"@node_modules/@syncfusion/ej2/dist/ej2.min.js": "@libs/syncfusion/",
"@node_modules/@syncfusion/ej2/material.css": "@libs/syncfusion/"
}
};
```
> ABP copies related packages from **node_modules** folder to the **libs** folder by examining this file. You can read this [document](docs.abp.io/en/abp/latest/UI/AspNetCore/Client-Side-Package-Management#mapping-the-library-resources) for more info.
* Then run the `abp install-libs` to install the dependencies and copy them into the libs folder by your mappings configuration. After running this command, in your **libs** folder it should be a folder named **syncfusion** folder.
![](./syncfusion-libs.png)
The last thing we need to do is, add some style and script files provided by Syncfusion, between our head-body tags.
* We can do this by creating two view components (one for Styles and the other for Scripts). Let's do that.
First, create a folder structure as shown below under the **Components** folder.
![](./component-folder-structure.png)
Then open the related files and add the following codes to each of these files.
* **Default.cshtml** (/Components/Syncfusion/Script/Default.cshtml)
```cshtml
@addTagHelper *, Syncfusion.EJ2 //add this line
<!-- Syncfusion Essential JS 2 Scripts -->
<script src="/libs/syncfusion/ej2.min.js"></script>
<!-- Syncfusion Essential JS 2 ScriptManager -->
<ejs-scripts></ejs-scripts>
```
* **SyncfusionScriptComponent.cs**
```csharp
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Script
{
public class SyncfusionScriptComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("~/Components/Syncfusion/Script/Default.cshtml");
}
}
}
```
* **Default.cshtml** (/Components/Syncfusion/Style/Default.cshtml)
```cshtml
<!-- Syncfusion Essential JS 2 Styles -->
<link rel="stylesheet" href="/libs/syncfusion/material.css">
```
* SyncfusionStyleComponent.cs
```csharp
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace SyncfusionComponentsDemo.Web.Components.Syncfusion.Style
{
public class SyncfusionStyleComponent : AbpViewComponent
{
public IViewComponentResult Invoke()
{
return View("~/Components/Syncfusion/Style/Default.cshtml");
}
}
}
```
After creating these two components, we can use the [**Layout Hooks**](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Layout-Hooks) system of ABP to inject these two components between head and script tags.
To do this, open your web module class and update the `ConfigureServices` method as below:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
//Register Syncfusion license
var licenseKey = configuration["Syncfusion:LicenseKey"].ToString();
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(licenseKey: licenseKey);
Configure<AbpLayoutHookOptions>(options =>
{
//Now, the SyncfusionStyleComponent code will be inserted in the head of the page as the last item.
options.Add(LayoutHooks.Head.Last, typeof(SyncfusionStyleComponent));
//the SyncfusionScriptComponent will be inserted in the body of the page as the last item.
options.Add(LayoutHooks.Body.Last, typeof(SyncfusionScriptComponent));
});
ConfigureUrls(configuration);
ConfigureBundles();
ConfigureAuthentication(context, configuration);
ConfigureAutoMapper();
ConfigureVirtualFileSystem(hostingEnvironment);
ConfigureLocalizationServices();
ConfigureNavigationServices();
ConfigureAutoApiControllers();
ConfigureSwaggerServices(context.Services);
}
```
After injecting the Syncfusion style and script into our application, our configurations have been completed. We can try with a simple component to see if it works as we expected.
* Let's try with the [Calendar](https://www.syncfusion.com/aspnet-core-ui-controls/calendar) component. Open your **Index.cshtml** file and update with the below content:
```cshtml
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using SyncfusionComponentsDemo.Localization
@using Volo.Abp.Users
@model SyncfusionComponentsDemo.Web.Pages.IndexModel
@section styles {
<abp-style src="/Pages/Index.css" />
}
@section scripts {
<abp-script src="/Pages/Index.js" />
}
<div class="container">
<h2>Syncfusion - Calendar Component</h2>
<ejs-calendar id="calendar"></ejs-calendar>
</div>
```
* Then when we run the application, we need to see the relevant calendar component as below.
![](./calendar-component.png)
### Conclusion
In this article, we've explained how to integrate the **Syncfusion Components** into our applications. After following this article, you can use the Syncfusion components in your application.
Thanks for reading the article, I hope you've found it useful :)

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/calendar-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/component-folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/copy-license-key.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/manage-trial-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-libs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/en/Community-Articles/2021-12-13-Integrating-the-Syncfusion-MVC-Components-to-the-ABP-MVC-UI/syncfusion-package.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

16
docs/en/Index.md

@ -1,6 +1,6 @@
# ABP Documentation
ABP Framework is a complete **infrastructure** based on the **ASP.NET Core** to create **modern web applications** and **APIs** by following the software development **best practices** and the **latest technologies**.
ABP Framework offers an **opinionated architecture** to build enterprise software solutions with **best practices** on top of the **.NET** and the **ASP.NET Core** platforms. It provides the fundamental infrastructure, production-ready startup templates, modules, themes, tooling, guides and documentation to implement that architecture properly and **automate the details** and repetitive works as much as possible.
## Getting Started
@ -56,13 +56,23 @@ See the [Application Modules](Modules/Index.md) document for all pre-built modul
The [Startup templates](Startup-Templates/Index.md) are pre-built Visual Studio solution templates. You can create your own solution based on these templates to **immediately start your development**.
## Free E-Book: Implementing Domain Driven Design
## Books
### Mastering ABP Framework
![abp-book](images/abp-book.png)
*Mastering ABP Framework* is an ultimate guide to get started and expertise with the ABP Framework. It is authored by Halil İbrahim Kalkan, the creator and the lead developer of the ABP Framework.
**[You can order it from Amazon now](https://www.amazon.com/Mastering-ABP-Framework-maintainable-implementing-dp-1801079242/dp/1801079242)!**
### Free E-Book: Implementing Domain Driven Design
![Implementing Domain Driven Design](images/implementing-domain-driven-design-book.png)
A **practical guide** for implementing Domain Driven Design (DDD). While the implementation details are **based on the ABP Framework** infrastructure, the basic concepts, principles and models can be applied to any solution, even if it is not a .NET solution.
[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc).
**[Click here to get your free copy](https://abp.io/books/implementing-domain-driven-design?ref=doc).**
## ABP Community

14
docs/en/Migration-Guides/Abp-5-0-Blazor.md

@ -4,4 +4,16 @@
## Upgrading to the latest Blazorise
ABP 5.0 uses the latest version of the [Blazorise](https://blazorise.com/) library. Please upgrade the Blazorise NuGet packages in your solution.
ABP 5.0 uses the latest version of the [Blazorise](https://blazorise.com/) library. Please upgrade the Blazorise NuGet packages in your solution.
## Bootstrap 5
ABP 5.0 now works with Bootstrap 5. For details, please refer to the official [migration guide](https://getbootstrap.com/docs/5.0/migration/) provided by Bootstrap.
Replace `Blazorise.Bootstrap` with `Blazorise.Bootstrap5` package.
Replace `AddBootstrapProviders()` with `AddBootstrap5Providers()`.
## Update Bundle
Use `abp bundle` command to update bundles if you are using Blazor WebAssembly.

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

@ -948,7 +948,7 @@ Since the HTTP APIs have been changed, you need to update Angular client side [s
Run the following command in the `angular` folder (you may need to stop the angular application):
```bash
abp generate-proxy
abp generate-proxy -t ng
```
This command will update the service proxy files under the `/src/app/proxy/` folder.

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

@ -10,7 +10,7 @@
In this tutorial series, you will build an ABP based web application named `Acme.BookStore`. This application is used to manage a list of books and their authors. It is developed using the following technologies:
* **{{DB_Value}}** as the ORM provider.
* **{{DB_Value}}** as the ORM provider.
* **{{UI_Value}}** as the UI Framework.
This tutorial is organized as the following parts;
@ -80,13 +80,13 @@ You can see the **book list** returned from the server. You can also check the *
Let's **create a new book** using the `create` function:
````js
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
});
````
@ -187,7 +187,7 @@ namespace Acme.BookStore.Web.Pages.Books
{
public void OnGet()
{
}
}
}
@ -461,7 +461,7 @@ For more information, see the [RoutesService document](../UI/Angular/Modifying-t
Once the host application is running, execute the following command in the `angular` folder:
```bash
abp generate-proxy
abp generate-proxy -t ng
```
This command will create the following files under the `/src/app/proxy/books` folder:
@ -654,7 +654,7 @@ Open the `Books.razor` and replace the content as the following:
> If you see some syntax errors, you can ignore them if your application properly built and run. Visual Studio still has some bugs with Blazor.
* Inherited from the `AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` which implements all the CRUD details for us.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base blass.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class.
* Injected `IStringLocalizer<BookStoreResource>` (as `L` object) and used for localization.
While the code above pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better.

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

@ -605,7 +605,7 @@ function configureRoutes(routes: RoutesService) {
Run the following command in the `angular` folder:
```bash
abp generate-proxy
abp generate-proxy -t ng
```
This command generates the service proxy for the author service and the related model (DTO) classes:

2
docs/en/UI/Angular/Service-Proxies.md

@ -15,7 +15,7 @@ ABP introduces an endpoint that exposes server-side method contracts. When the `
Run the following command in the **root folder** of the angular application:
```bash
abp generate-proxy
abp generate-proxy -t ng
```
The command without any parameters creates proxies only for your own application's services and places them in your default Angular application. There are several parameters you may use to modify this behavior. See the [CLI documentation](../../CLI) for details.

BIN
docs/en/images/abp-book.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

11
docs/zh-Hans/CLI.md

@ -29,6 +29,7 @@ dotnet tool update -g Volo.Abp.Cli
* **`help`**: 展示ABP CLI的用法帮助信息.
* **`new`**:生成基于ABP的[启动模板](Startup-Templates/Index.md).
* **`update`**:自动更新的ABP解决方案ABP相关的NuGet和NPM包.
* **`clean`**: 删除当前目录下所有的 `BIN``OBJ` 子目录.
* **`add-package`**: 添加ABP包到项目.
* **`add-module`**: 添加[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index)到解决方案.
* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点.
@ -129,6 +130,16 @@ abp update [options]
* `--check-all`: 分别检查每个包的新版本. 默认是 `false`.
* `--version` or `-v`: 指定用于升级的版本. 如果没有指定,则使用最新版本.
### clean
删除当前目录下所有的 `BIN``OBJ` 子目录.
用法:
````bash
abp clean
````
### add-package
通过以下方式将ABP包添加到项目中

4
docs/zh-Hans/Distributed-Event-Bus.md

@ -45,7 +45,7 @@ namespace AbpDemo
public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
{
await _distributedEventBus.PublishAsync(
new StockCountChangedEvent
new StockCountChangedEto
{
ProductId = productId,
NewCount = newCount
@ -299,4 +299,4 @@ namespace AbpDemo
}
````
此示例使用AutoMapper的 `AutoMap` 属性配置的映射. 你可以创建一个配置文件类代替. 请参阅AutoMapper文档了解更多选项.
此示例使用AutoMapper的 `AutoMap` 属性配置的映射. 你可以创建一个配置文件类代替. 请参阅AutoMapper文档了解更多选项.

174
docs/zh-Hans/Modules/IdentityServer.md

@ -1 +1,173 @@
TODO...
# 身份服务器模块
身份服务器模块提供了一个 [IdentityServer](https://github.com/IdentityServer/IdentityServer4) (IDS) 的完全集成,该框架提供高级身份验证功能,如单点登录和API访问控制.此模块将客户端,资源以及其他 IDS 相关的对象保存到数据库中.
## 如何安装
当你使用 ABP 框架 [创建一个新的解决方案](https://abp.io/get-started) 时,此模块将被预安装(作为 NuGet/NPM 包).你可以继续用其作为包并轻松地获取更新,也可以将其源代码包含在解决方案中(请参阅 `get-source` [CLI](../CLI.md))以开发自定义模块.
### 源代码
可以 [在此处](https://github.com/abpframework/abp/tree/dev/modules/identityserver) 访问源代码.源代码使用 [MIT](https://choosealicense.com/licenses/mit/) 许可,所以你可以免费使用和自定义它.
## 用户界面
此模块使用了领域逻辑和数据库集成,但没有提供任何 UI.如果你需要动态添加客户端和资源, 管理 UI 是非常有用的.在这种情况下,你可以自己构建管理 UI,或者考虑购买为此模块提供了管理 UI 的 [ABP 商业版](https://commercial.abp.io/).
## 与其他模块的关系
此模块基于 [身份模块](Identity.md) 并且[账户模块](Account.md) 有一个 [集成包](https://www.nuget.org/packages/Volo.Abp.Account.Web.IdentityServer).
## 选项
### AbpIdentityServerBuilderOptions
`AbpIdentityServerBuilderOptions` 在你的身份服务器 [模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics) 中的 `PreConfigureServices` 方法中配置.例如:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpIdentityServerBuilderOptions>(builder =>
{
//Set options here...
});
}
````
`AbpIdentityServerBuilderOptions` 属性:
* `UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap` (默认值:true):更新 `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap` 使其与身份服务器声明兼容.
* `UpdateAbpClaimTypes` (默认值:true):更新 `AbpClaimTypes` 与身份服务器声明兼容.
* `IntegrateToAspNetIdentity` (默认值:true):集成到 ASP.NET Identity.
* `AddDeveloperSigningCredential` (默认值:true):设置为 false 禁止调用 IIdentityServerBuilder 中的 `AddDeveloperSigningCredential()`.
`IIdentityServerBuilder` 可以在你的身份服务器 [模块](https://docs.abp.io/en/abp/latest/Module-Development-Basics) 中的 `PreConfigureServices` 方法中配置.例如:
````csharp
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IIdentityServerBuilder>(builder =>
{
builder.AddSigningCredential(...);
});
}
````
## 内部结构
### 领域层
#### 聚合
##### API 资源
需要 API 资源来允许客户端请求访问令牌.
* `ApiResource` (聚合根):表示系统中的 API 资源.
* `ApiSecret` (集合):API 资源的密钥.
* `ApiScope` (集合):API 资源的作用域.
* `ApiResourceClaim` (集合):API 资源的声明.
##### 客户端
客户端表示可以从你的身份服务器请求令牌的应用程序.
* `Client` (聚合根):表示一个身份服务器的客户端应用程序.
* `ClientScope` (集合):客户端的作用域.
* `ClientSecret` (集合):客户端的密钥.
* `ClientGrantType` (集合):客户端的授权类型.
* `ClientCorsOrigin` (集合):客户端的 CORS 源.
* `ClientRedirectUri` (集合):客户端的重定向 URIs.
* `ClientPostLogoutRedirectUri` (集合):客户端的登出重定向 URIs.
* `ClientIdPRestriction` (集合):客户端的提供程序约束.
* `ClientClaim` (集合):客户端的声明.
* `ClientProperty` (集合):客户端的自定义属性.
##### 持续化授权
持续化授权存储了授权码,刷新令牌和用户准许.
* `PersistedGrant` (聚合根):表示为身份服务器持续化授权.
##### 身份资源
身份资源是用户的用户 ID ,名称或邮件地址等数据.
* `IdentityResource` (聚合根):表示与身份服务器的身份资源.
* `IdentityClaim` (集合):身份资源的声明.
#### 仓储
为此模块定义了以下自定义仓储:
* `IApiResourceRepository`
* `IClientRepository`
* `IPersistentGrantRepository`
* `IIdentityResourceRepository`
#### 领域服务
此模块不包含任何领域服务,但重写了下面的服务;
* `AbpProfileService` (当 `AbpIdentityServerBuilderOptions.IntegrateToAspNetIdentity` 为 true 时使用)
* `AbpClaimsService`
* `AbpCorsPolicyService`
### 设置
此模块未定义任何设置.
### 应用层
#### 应用服务
* `ApiResourceAppService` (实现 `IApiResourceAppService`):实现了 API 资源管理 UI 的用例.
* `IdentityServerClaimTypeAppService` (实现 `IIdentityServerClaimTypeAppService`):用于获取声明列表.
* `ApiResourceAppService` (实现 `IApiResourceAppService`):实现了 API 管理资源 UI 的用例.
* `IdentityResourceAppService` (实现 `IIdentityResourceAppService`):实现了身份资源管理 UI 的用例.
### 数据库提供程序
#### 公共
##### 表/集合 前缀 & 架构
所有表/集合都使用 `IdentityServer` 作为默认前缀.如果你需要改变表的前缀或设置一个架构名称(如果你的数据库提供程序支持),请设置 `AbpIdentityServerDbProperties` 类的静态属性.
##### 连接字符串
此模块使用 `AbpIdentityServer` 作为连接字符串的名称.如果你没有用这个名称定义连接字符串,它将回退到 `Default` 连接字符串.
有关详细信息,请参阅 [连接字符串](https://docs.abp.io/en/abp/latest/Connection-Strings) 文档.
#### EF Core
##### 表
* **IdentityServerApiResources**
* IdentityServerApiSecrets
* IdentityServerApiScopes
* IdentityServerApiScopeClaims
* IdentityServerApiClaims
* **IdentityServerClients**
* IdentityServerClientScopes
* IdentityServerClientSecrets
* IdentityServerClientGrantTypes
* IdentityServerClientCorsOrigins
* IdentityServerClientRedirectUris
* IdentityServerClientPostLogoutRedirectUris
* IdentityServerClientIdPRestrictions
* IdentityServerClientClaims
* IdentityServerClientProperties
* **IdentityServerPersistedGrants**
* **IdentityServerIdentityResources**
* IdentityServerIdentityClaims
#### MongoDB
##### 集合
* **IdentityServerApiResources**
* **IdentityServerClients**
* **IdentityServerPersistedGrants**
* **IdentityServerIdentityResources**

256
docs/zh-Hans/Tutorials/Part-2.md

@ -2,26 +2,10 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","BlazorServer","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
DB="ef"
DB_Text="Entity Framework Core"
UI_Text="mvc"
else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
DB ="?"
UI_Text="?"
end
}}
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
@ -44,16 +28,30 @@ end
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合:
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### 视频教程
本章也被录制为视频教程 **<a href="https://www.youtube.com/watch?v=UDNlLiPiBiw&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=2" target="_blank">发布在YouTube</a>**.
{{end}}
{{if UI == "MVC"}}
## 动态JavaScript代理
通常在 **JavaScript** 端通过AJAX调用HTTP API端点. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法.
**JavaScript** 端通过AJAX调用HTTP API端点是常见的做法. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法.
ABP**动态**为所有API端点创建 **[JavaScript代理](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md)**. 所以你可以像调用**Javascript本地方法**一样使用任何**端点**.
@ -79,21 +77,21 @@ acme.bookStore.books.book.getList({}).done(function (result) { console.log(resul
![bookstore-getlist-result-network](images/bookstore-getlist-result-network.png)
Let's **create a new book** using the `create` function:
让我们使用 `create` 函数**创建一本书**:
````js
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
});
````
> 如果你下载了本教程的源代码并按照示例中的步骤操作,你需要传递`authorId`参数给创建方法以**创建一本新书**.
您应该在控制台中看到类似以下的消息:
````text
@ -102,7 +100,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
检查数据库中的 `Books` 表你会看到新的一行. 你可以自己尝试使用 `get`, `update``delete` 函数.
我们将利用这些动态代理功能在接下来的章节来与服务器通信.
在接下来的章节,我们将利用这些动态代理函数与服务器通信.
{{end}}
@ -112,7 +110,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
本地化文本位于 `Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹下:
![bookstore-localization-files](./images/bookstore-localization-files-v2.png)
![bookstore-localization-files](images/bookstore-localization-files-v2.png)
打开 `en.json` (*英文翻译*)文件并更改内容,如下所示:
@ -154,7 +152,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
* 为按钮项添加 `Menu:` 前缀.
* 使用 `Enum:<enum-type>:<enum-value>` 命名约定来本地化枚举成员. 当您这样做时ABP可以在某些适当的情况下自动将枚举本地化.
如果未在本地化文件中定义文本,则文本将**回退**到本地化键(作为ASP.NET Core的标准行为).
如果未在本地化文件中定义文本,则文本将**回退**到本地化键(ASP.NET Core的标准行为).
> ABP本地化系统建立在[ASP.NET Core标准本地化](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization)系统之上,并以多种方式进行了扩展. 有关详细信息请参见[本地化文档](../Localization.md).
@ -162,7 +160,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
## 创建图书页面
是时候创建可见的和可用的东西了! 代替经典的MVC,我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start).
是时候创建可见的和可用的东西了! 我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start),而不是经典的MVC.
`Acme.BookStore.Web` 项目的 `Pages` 文件夹下创建一个名为新的 `Books` 的文件夹. 然后在文件夹右键选择 **添加 > Razor Page** 菜单. 输入名称 `Index`:
@ -189,13 +187,13 @@ namespace Acme.BookStore.Web.Pages.Books
{
public void OnGet()
{
}
}
}
```
### 将Book页面添加到主菜单
### 将图书页面添加到主菜单
打开 `Menus` 文件夹中的 `BookStoreMenuContributor` 类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码:
@ -223,9 +221,7 @@ context.Menu.AddItem(
### 图书列表
We will use the [Datatables.net](https://datatables.net/) jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience.
我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验.
我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示图书列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验.
> Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,无需在页面中引用样式和脚本文件.
@ -261,7 +257,7 @@ We will use the [Datatables.net](https://datatables.net/) jQuery library to show
`Pages/Books/` 文件夹中创建 `index.js`文件
![bookstore-index-js-file](./images/bookstore-index-js-file-v3.png)
![bookstore-index-js-file](images/bookstore-index-js-file-v3.png)
`index.js` 的内容如下:
@ -321,8 +317,8 @@ $(function () {
````
* `abp.localization.getResource` 获取一个函数,该函数用于使用服务器端定义的相同JSON文件对文本进行本地化. 通过这种方式你可以与客户端共享本地化值.
* `abp.libs.datatables.normalizeConfiguration`一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置.
* `abp.libs.datatables.createAjax`帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法.
* `abp.libs.datatables.normalizeConfiguration`是一个辅助方法.不是必须的, 但是它通过为缺省的选项提供约定的值来简化[Datatables](https://datatables.net/)配置.
* `abp.libs.datatables.createAjax`另一个辅助方法,用来适配ABP的动态JavaScript API代理和[Datatable](https://datatables.net/)期望的参数格式.
* `acme.bookStore.books.book.getList` 是动态JavaScript代理函数(上面已经介绍过了)
* [luxon](https://moment.github.io/luxon/) 库也是该解决方案中预先配置的标准库,你可以轻松地执行日期/时间操作.
@ -334,15 +330,13 @@ $(function () {
![Book list](images/bookstore-book-list-3.png)
这是一个完全正常工作的服务端分页,排序和本地化的图书列表.
{{end}}
这是一个可以正常工作的,服务端分页,排序和本地化的图书列表.
{{if UI == "NG"}}
## 安装NPM包
>注意: 本教程基于ABP Framework v3.0.3+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md).
>注意: 本教程基于ABP Framework v3.1.0+. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md).
`angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包:
@ -357,9 +351,7 @@ yarn
- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库.
- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库.
### BookModule
运行以下命令创建一个名为 `BookModule` 的新模块:
运行以下命令在angular应用程序根目录创建一个名为 `BookModule` 的新模块:
```bash
yarn ng generate module book --module app --routing --route books
@ -417,7 +409,7 @@ const routes: Routes = [
];
````
现在打开 `src/app/route.provider.ts` 以下替换 `configureRoutes` 函数:
现在打开 `src/app/route.provider.ts` 替换 `configureRoutes` 函数为以下代码:
```js
function configureRoutes(routes: RoutesService) {
@ -450,27 +442,31 @@ function configureRoutes(routes: RoutesService) {
`RoutesService` 是ABP框架提供的用于配置主菜单和路由的服务.
* `path` 路由的URL.
* `path` 路由的URL.
* `name` 菜单项的名称(参阅[本地化文档](../UI/Angular/Localization.md)了解更多).
* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标).
* `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面.
* `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account``eLayoutType.empty`.
* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标).
* `order` 菜单项的排序.
* `layout` BooksModule路由的布局. (有三个预定义的布局类型: `eLayoutType.application`, `eLayoutType.account``eLayoutType.empty`).
更多信息请参阅[RoutesService 文档](../UI/Angular/Modifying-the-Menu.md#via-routesservice).
更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice).
### 生成服务代理
### 生成代理
[ABP CLI](../CLI.md) 提供 `generate-proxy` 命令为HTTP APIs生成客户端代理.有了这些代理,在客户端使用HTTP APIs变得更加方便. 运行 `generate-proxy` 命令前, 你的 host 必须正在运行.
ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md).
> **警告**: 使用IIS Express时有一个问题; 它不允许从另一个进程连接应用程序. 如果你使用Visual Studio, 在运行按钮的下拉框中选择`Acme.BookStore.HttpApi.Host`,不要选择IIS Express, 如下图:
`angular` 文件夹下运行以下命令:
![vs-run-without-iisexpress](images/vs-run-without-iisexpress.png)
启动host应用程序后,在 `angular` 文件夹下运行以下命令:
```bash
abp generate-proxy
abp generate-proxy -t ng
```
生成的文件如下:
这个命令将在`/src/app/proxy/books`文件夹下产生以下文件:
![Generated files](./images/generated-proxies-2.png)
![Generated files](images/generated-proxies-3.png)
### BookComponent
@ -479,8 +475,7 @@ abp generate-proxy
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto } from '@proxy/books';
@Component({
selector: 'app-book',
@ -494,7 +489,7 @@ export class BookComponent implements OnInit {
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -503,8 +498,8 @@ export class BookComponent implements OnInit {
}
```
* 我们注入了生成的 `BookService`.
* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索.
* 我们引入并注入了生成的 `BookService`.
* 我们使用 [ListService](../UI/Angular/List-Service.md),它是一个工具服务,提供了易用的分页,排序和搜索.
打开 `/src/app/book/book.component.html` 用以下内容替换它:
@ -545,9 +540,138 @@ export class BookComponent implements OnInit {
现在你可以在浏览器看到最终结果:
![Book list final result](./images/bookstore-book-list.png)
![图书列表最终结果](images/bookstore-book-list.png)
{{end}}
{{else if UI == "Blazor" || UI == "BlazorServer"}}
## 创建图书页面
是时候创建可见和可用的东西了! 右击`Acme.BookStore.Blazor`项目下的`Pages`文件夹,新建一个名为`Books.razor`的**razor组件**.
![blazor-add-books-component](images/blazor-add-books-component.png)
用以下内容替换这个组件的内容:
````html
@page "/books"
<h2>Books</h2>
@code {
}
````
### 将图书页面添加到主菜单
打开`Blazor`项目中的`BookStoreMenuContributor`类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码:
````csharp
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
)
)
);
````
运行项目,使用用户名 `admin` 和密码 `1q2w3E*` 登录到应用程序. 看到新菜单项已添加到顶部栏:
![blazor-menu-bookstore](images/blazor-menu-bookstore.png)
点击BookStore下的Books子菜单项就会跳转到空的图书页面.
### 图书列表
我们将使用[Blazorise library](https://blazorise.com/)作为UI组件.它是一个强大的库,支持主要的HTML/CSS框架,包括Bootstrap.
ABP提供了一个通用的基类,`AbpCrudPageBase<...>`,用来创建CRUD风格的页面.这个基类兼容用来构建`IBookAppService`的`ICrudAppService`.所以我们从`AbpCrudPageBase`继承,获得标准CRUD的默认实现.
打开`Books.razor` 并把内容修改成下面这样:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<BookStoreResource> L
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<h2>@L["Books"]</h2>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType:{(int)context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
<DisplayTemplate>
@context.CreationTime.ToLongDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
````
> 如果你可以编译并运行成功,但看到一些语法错误.你可以忽略这些错误,因为Visual Studio处理Blazor还有一些bug.
* `AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>`实现了所有的CRUD细节,我们从它继承.
* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync`定义在基类中.
* 注入`IStringLocalizer<BookStoreResource>` (作为`L`对象),用于本地化.
虽然上面的代码非常容易理解,你仍然可以查看Blazorise [Card](https://blazorise.com/docs/components/card/)和[DataGrid](https://blazorise.com/docs/extensions/datagrid/)文档以更好地理解它们.
#### 关于AbpCrudPageBase
对于图书页面,我们将持续从`AbpCrudPageBase`获得益处. 你可以只注入`IBookAppService`并自己执行所有的服务端调用(感谢ABP的[动态C# HTTP API客户端代理](../API/Dynamic-CSharp-API-Clients.md)系统).
## 运行最终应用程序
你可以运行应用程序!该部分的最终用户界面如下所示:
![blazor-bookstore-book-list](images/blazor-bookstore-book-list.png)
这是一个可以正常工作的,服务端分页,排序和本地化的图书列表.
{{end # UI }}
## 下一章

600
docs/zh-Hans/Tutorials/Part-3.md

@ -2,27 +2,10 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","BlazorServer","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
UI_Text="mvc"
else if UI == "NG"
UI_Text="angular"
else
UI_Text="?"
end
if DB == "EF"
DB_Text="Entity Framework Core"
else if DB == "Mongo"
DB_Text="MongoDB"
else
DB_Text="?"
end
}}
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
@ -45,16 +28,30 @@ end
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合:
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### 视频教程
本章也被录制为视频教程 **<a href="https://www.youtube.com/watch?v=TLShZO8u2VE&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=3" target="_blank">发布在YouTube</a>**.
{{end}}
{{if UI == "MVC"}}
## 创建新书籍
通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示:
通过本节, 你将会了解如何创建一个 modal form 实现新增书籍的功能. model dialog将如下图所示:
![bookstore-create-dialog](./images/bookstore-create-dialog-2.png)
@ -66,7 +63,7 @@ end
#### CreateModal.cshtml.cs
打开 `CreateModal.cshtml.cs` 代码文件,用如下代码替换 `CreateModalModel` 类的实现:
打开 `CreateModal.cshtml.cs` 代码文件(`CreateModalModel` 类),替换成以下代码:
````C#
using System.Threading.Tasks;
@ -101,10 +98,10 @@ namespace Acme.BookStore.Web.Pages.Books
}
````
* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法.
* 该类派生于 `BookStorePageModel` 而非默认的 `PageModel`. `BookStorePageModel` 间接继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法.
* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上.
* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法.
* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中具有一些默认值分配或代码执行,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值.
* 它在 `OnGet` 方法中创建一个新的 `CreateUpdateBookDto` 对象。 ASP.NET Core不需要像这样创建一个新实例就可以正常工作. 但是它不会为你创建实例,并且如果你的类在类构造函数中赋值一些默认值或执行一些代码,它们将无法工作. 对于这种情况,我们为某些 `CreateUpdateBookDto` 属性设置了默认值.
#### CreateModal.cshtml
@ -134,10 +131,9 @@ namespace Acme.BookStore.Web.Pages.Books
* 这个 modal 使用 `abp-dynamic-form` [tag Helper](../UI/AspNetCore/Tag-Helpers/Dynamic-Forms.md) 根据 `CreateBookViewModel` 类自动构建了表单.
* `abp-model` 指定了 `Book` 属性为模型对象.
* `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发.
* `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要).
> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态时,我们不希望包括所有布局.
> 提示: 就像在本示例中一样,`Layout` 应该为 `null`,因为当通过AJAX加载模态窗口时,我们不希望包括所有布局.
### 添加 "New book" 按钮
@ -195,9 +191,9 @@ namespace Acme.BookStore.Web.Pages.Books
如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮:
![bookstore-new-book-button](./images/bookstore-new-book-button-2.png)
![bookstore-new-book-button](images/bookstore-new-book-button-2.png)
打开 `Pages/book/index.js``datatable` 配置代码后面添加如下代码:
打开 `Pages/Book/Index.js``datatable` 配置代码后面添加如下代码:
````js
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
@ -212,9 +208,9 @@ $('#NewBookButton').click(function (e) {
});
````
* `abp.ModalManager` 是一个在客户端打开和管理modal的辅助类.它基于Twitter Bootstrap的标准modal组件通过简化的API抽象隐藏了许多细节.
* `abp.ModalManager` 是一个在客户端管理modal的辅助类.它内部使用了Twitter Bootstrap的标准modal组件,但通过简化的API抽象了许多细节.
* `createModal.onResult(...)` 用于在创建书籍后刷新数据表格.
* `createModal.open();` 用于打开模态创建新书籍.
* `createModal.open();` 用于打开modal创建新书籍.
`Index.js` 的内容最终如下所示:
@ -335,8 +331,8 @@ namespace Acme.BookStore.Web.Pages.Books
}
````
* `[HiddenInput]``[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串中获取Id的值.
* 在 `OnGetAsync` 方法中,`BookAppService.GetAsync` 方法返回的 `BookDto` 映射成 `CreateUpdateBookDto` 并赋值给Book属性.
* `[HiddenInput]``[BindProperty]` 是标准的 ASP.NET Core MVC 特性.这里启用 `SupportsGet` 从Http请求的查询字符串参数中获取Id的值.
* 在 `OnGetAsync` 方法中, 我们从 `BookAppService` 获得 `BookDto` ,并将它映射成DTO对象 `CreateUpdateBookDto`.
* `OnPostAsync` 方法直接使用 `BookAppService.UpdateAsync` 来更新实体.
### BookDto 到 CreateUpdateBookDto 对象映射
@ -391,10 +387,10 @@ namespace Acme.BookStore.Web
这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点:
* 它包含`id`属性的`abp-input`, 用于存储编辑书的 `id` (它是隐藏的Input)
* 它包含`id`属性的`abp-input`, 用于存储编辑书`id` (它是隐藏的Input)
* 此页面指定的post地址是`Books/EditModal`.
### 为表格添加 "操作(Actions)" 下拉菜单
### 为表格添加 "操作(Actions)" 下拉菜单
我们将为表格每行添加下拉按钮 ("Actions"):
@ -516,9 +512,9 @@ $(function () {
}
````
* `confirmMessage` 用来在实际执行 `action` 前向用户进行确认.
* 通过javascript代理方法 `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求删除一个book实体.
* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息.
* `confirmMessage` 执行 `action` 前向用户进行确认.
* `acme.bookStore.books.book.delete(...)` 执行一个AJAX请求删除一个book.
* `abp.notify.info` 执行删除操作后显示一个通知信息.
由于我们使用了两个新的本地化文本(`BookDeletionConfirmationMessage`和`SuccesslyDeleted`),因此你需要将它们添加到本地化文件(`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下的`en.json`):
@ -640,7 +636,7 @@ $(function () {
## 创建新书籍
下面的章节中,你将学习到如何创建一个新的模态对话框来新增书籍.
下面的章节中,你将学习到如何创建一个新的模态窗口新增书籍.
### BookComponent
@ -649,8 +645,7 @@ $(function () {
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto } from '@proxy/books';
@Component({
selector: 'app-book',
@ -666,7 +661,7 @@ export class BookComponent implements OnInit {
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -680,7 +675,8 @@ export class BookComponent implements OnInit {
}
```
* 我们定义了一个名为 `isModalOpen` 的变量和 `createBook` 方法.
* 我们定义了一个名为 `isModalOpen` 的属性和 `createBook` 方法.
打开 `/src/app/book/book.component.html` 做以下更改:
@ -690,9 +686,9 @@ export class BookComponent implements OnInit {
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">{%{{{ '::Menu:Books' | abpLocalization }}}%}</h5>
</div>
</div>
<div class="text-right col col-md-6">
<!-- Add the "new book" button here -->
<div class="text-lg-right pt-2">
<button id="create" class="btn btn-primary" type="button" (click)="createBook()">
@ -700,7 +696,7 @@ export class BookComponent implements OnInit {
<span>{%{{{ "::NewBook" | abpLocalization }}}%}</span>
</button>
</div>
</div>
</div>
</div>
@ -726,11 +722,11 @@ export class BookComponent implements OnInit {
```
* 添加了 `New book` 按钮到卡片头部.
* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处.
* 添加了 `abp-modal` 渲染模态框,允许用户创建新书. `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些额外的好处.
你可以打开浏览器,点击**New book**按钮看到模态框.
![Empty modal for new book](./images/bookstore-empty-new-book-modal.png)
![Empty modal for new book](images/bookstore-empty-new-book-modal.png)
### 添加响应式表单
@ -741,8 +737,7 @@ export class BookComponent implements OnInit {
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models'; // add BookType
import { BookService } from './services';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books'; // add bookTypeOptions
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // add this
@Component({
@ -756,12 +751,8 @@ export class BookComponent implements OnInit {
form: FormGroup; // add this line
bookType = BookType; // add this line
// add bookTypes as a list of BookType enum members
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -772,7 +763,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -800,7 +791,7 @@ export class BookComponent implements OnInit {
return;
}
this.bookService.createByInput(this.form.value).subscribe(() => {
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
@ -809,12 +800,11 @@ export class BookComponent implements OnInit {
}
```
* 导入了 `FormGroup, FormBuilder and Validators`.
* 从` @angular/forms `导入了 `FormGroup, FormBuilder and Validators`.
* 添加了 `form: FormGroup` 变量.
* 添加了 `bookType` 属性,你可以从模板中获取 `BookType` 枚举成员.
* 添加了 `bookTypes` 属性作为 `BookType` 枚举成员列表. 将在表单选项中使用.
* 我们注入了 `fb: FormBuilder` 服务到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 服务为生成控件提供了方便的方法. 它减少了构建复杂表单所需的样板文件的数量.
* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法. 该方法创建一个响应式表单去创建新书.
* 我们注入了 `FormBuilder` 到构造函数. [FormBuilder](https://angular.io/api/forms/FormBuilder) 提供了简便的方法生成表单控件. 它减少了构建复杂表单所需的样板文件的数量.
* 我们添加了 `buildForm` 方法到文件末尾, 在 `createBook` 方法调用 `buildForm()` 方法.
* 添加了`save` 方法.
打开 `/src/app/book/book.component.html`,使用以下内容替换 `<ng-template #abpBody> </ng-template>`:
@ -836,7 +826,7 @@ export class BookComponent implements OnInit {
<label for="book-type">Type</label><span> * </span>
<select class="form-control" id="book-type" formControlName="type">
<option [ngValue]="null">Select a book type</option>
<option [ngValue]="bookType[type]" *ngFor="let type of bookTypes"> {%{{{ type }}}%}</option>
<option [ngValue]="type.value" *ngFor="let type of bookTypes"> {%{{{ type.key }}}%}</option>
</select>
</div>
@ -897,13 +887,12 @@ export class BookModule { }
* 我们导入了 `NgbDatepickerModule` 来使用日期选择器.
打开 `/src/app/book/book.component.ts` 使用以内容替换:
打开 `/src/app/book/book.component.ts` 使用以内容替换:
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType } from './models';
import { BookService } from './services';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
// added this line
@ -923,11 +912,7 @@ export class BookComponent implements OnInit {
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -938,7 +923,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -964,7 +949,7 @@ export class BookComponent implements OnInit {
return;
}
this.bookService.createByInput(this.form.value).subscribe(() => {
this.bookService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
@ -974,11 +959,11 @@ export class BookComponent implements OnInit {
```
* 导入了 `NgbDateNativeAdapter``NgbDateAdapter`.
* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 有关更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview).
* 我们添加了一个新的 `NgbDateAdapter` 提供程序,它将Datepicker值转换为 `Date` 类型. 更多详细信息,请参见[datepicker adapters](https://ng-bootstrap.github.io/#/components/datepicker/overview).
现在你可以打开浏览器看到以下变化:
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
![Save button to the modal](images/bookstore-new-book-form-v2.png)
## 更新书籍
@ -987,8 +972,7 @@ export class BookComponent implements OnInit {
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto, BookType, CreateUpdateBookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto, bookTypeOptions } from '@proxy/books';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@ -1001,15 +985,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;
selectedBook = new BookDto(); // declare selectedBook
selectedBook = {} as BookDto; // declare selectedBook
form: FormGroup;
bookType = BookType;
bookTypes = Object.keys(this.bookType).filter(
(key) => typeof this.bookType[key] === 'number'
);
bookTypes = bookTypeOptions;
isModalOpen = false;
@ -1020,7 +1000,7 @@ export class BookComponent implements OnInit {
) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput(query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -1028,14 +1008,14 @@ export class BookComponent implements OnInit {
}
createBook() {
this.selectedBook = new BookDto(); // reset the selected book
this.selectedBook = {} as BookDto; // reset the selected book
this.buildForm();
this.isModalOpen = true;
}
// Add editBook method
editBook(id: string) {
this.bookService.getById(id).subscribe((book) => {
this.bookService.get(id).subscribe((book) => {
this.selectedBook = book;
this.buildForm();
this.isModalOpen = true;
@ -1061,8 +1041,8 @@ export class BookComponent implements OnInit {
}
const request = this.selectedBook.id
? this.bookService.updateByIdAndInput(this.form.value, this.selectedBook.id)
: this.bookService.createByInput(this.form.value);
? this.bookService.update(this.selectedBook.id, this.form.value)
: this.bookService.create(this.form.value);
request.subscribe(() => {
this.isModalOpen = false;
@ -1074,10 +1054,10 @@ export class BookComponent implements OnInit {
```
* 我们声明了类型为 `BookDto``selectedBook` 变量.
* 我们添加了 `editBook` 方法, 根据给定`Id` 设置 `selectedBook` 对象.
* 我们添加了 `editBook` 方法, 根据给定书 `Id` 设置 `selectedBook` 对象.
* 我们替换了 `buildForm` 方法使用 `selectedBook` 数据创建表单.
* 我们替换了 `createBook` 方法,设置 `selectedBook` 为空对象.
* 我们替换了 `save` 方法.
* 我们修改了 `save` 方法,同时处理新建和更新操作.
### 添加 "Actions" 下拉框到表格
@ -1111,7 +1091,7 @@ export class BookComponent implements OnInit {
在表格的第一列添加了一个 "Actions" 下拉菜单,如下图所示:
![Action buttons](./images/bookstore-actions-buttons.png)
![Action buttons](images/bookstore-actions-buttons.png)
同时如下所示更改 `ng-template #abpHeader` 部分:
@ -1147,13 +1127,13 @@ constructor(
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure').subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.bookService.deleteById(id).subscribe(() => this.list.get());
this.bookService.delete(id).subscribe(() => this.list.get());
}
});
}
```
* 我们入了 `ConfirmationService`.
* 我们入了 `ConfirmationService`.
* 我们注入了 `ConfirmationService` 到构造函数.
* 添加了 `delete` 方法.
@ -1161,6 +1141,7 @@ delete(id: string) {
### 添加删除按钮:
打开 `/src/app/book/book.component.html` 修改 `ngbDropdownMenu` 添加删除按钮:
```html
@ -1174,14 +1155,445 @@ delete(id: string) {
最终操作下拉框UI看起来如下:
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
![bookstore-final-actions-dropdown](images/bookstore-final-actions-dropdown.png)
点击 `delete` 操作调用 `delete` 方法,然后显示一个确认弹层如下图所示.
![bookstore-confirmation-popup](images/bookstore-confirmation-popup.png)
{{end}}
{{if UI == "Blazor" || UI == "BlazorServer"}}
## 创建新书籍
通过本节, 你将会了解如何创建一个模态窗口实现新增书籍的功能. 因为我们已经从 `AbpCrudPageBase` 继承, 所以只需要开发视图部分.
### 添加 "New Button" 按钮
打开 `Books.razor` 替换 `<CardHeader>` 部分为以下代码:
````xml
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
````
如下图所示,卡片头 **右侧** 添加了 **New book** 按钮:
![blazor-add-book-button](images/blazor-add-book-button.png)
现在, 我们可以添加点击按钮后打开的模态窗口了.
### 书籍创建模态窗口
打开 `Books.razor`, 添加以下代码到页面底部:
````xml
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
这段代码需要一个服务; 在文件顶部, `@inherits...` 行前, 注入 `AbpBlazorMessageLocalizerHelper<T>`:
````csharp
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
````
* 表单实现了验证功能, `AbpBlazorMessageLocalizerHelper` 用于本地化验证消息.
* `CreateModal` 对象, `CloseCreateModalAsync``CreateEntityAsync` 方法定义在基类中. 参阅 [Blazorise文档](https://blazorise.com/docs/) 以深入理解 `Modal` 和其它组件.
这就是全部了. 运行应用程序, 尝试添加一本新书.
![blazor-new-book-modal](images/blazor-new-book-modal.png)
## 更新书籍
编辑书籍与新建书籍很类似.
点击 `delete` 动作调用 `delete` 方法,然后无法显示一个确认弹层如下图所示.
### 操作下拉菜单
![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png)
打开 `Books.razor` , 在 `DataGridColumns` 中添加以下 `DataGridEntityActionsColumn` 作为第一项:
````xml
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
````
* `OpenEditModalAsync` 定义在基类中, 它接收实体(书籍)参数, 编辑这个实体.
`DataGridEntityActionsColumn` 组件用于显示 `DataGrid` 每一行中的"操作" 下拉菜单. 如果其中只有唯一的操作, `DataGridEntityActionsColumn` 显示 **唯一按钮**, 而不是下拉菜单.
![blazor-edit-book-action](images/blazor-edit-book-action-2.png)
### 编辑模态窗口
我们现在可以定义一个模态窗口编辑书籍. 加入下面的代码到 `Books.razor` 页面的底部:
````xml
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
### AutoMapper 配置
基类 `AbpCrudPageBase` 使用 [对象到对象映射](../Object-To-Object-Mapping.md) 系统将 `BookDto` 对象转化为`CreateUpdateBookDto` 对象. 因此, 我们需要定义映射.
打开 `Acme.BookStore.Blazor` 项目中的 `BookStoreBlazorAutoMapperProfile `, 替换成以下内容:
````csharp
using Acme.BookStore.Books;
using AutoMapper;
namespace Acme.BookStore.Blazor
{
public class BookStoreBlazorAutoMapperProfile : Profile
{
public BookStoreBlazorAutoMapperProfile()
{
CreateMap<BookDto, CreateUpdateBookDto>();
}
}
}
````
* `CreateMap<BookDto, CreateUpdateBookDto>();` 行用于定义映射.
### 测试编辑模态窗口
你可以运行程序并尝试编辑一本书.
![blazor-edit-book-modal](images/blazor-edit-book-modal.png)
> 提示: 尝试保留 *Name* 字段为空并提交表单, 将显示验证错误消息.
## 删除书籍
打开 `Books.razor` 页面, 在 `EntityActions` 中的"编辑" 操作下面加入以下的 `EntityAction`:
````xml
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="() => GetDeleteConfirmationMessage(context)" />
````
* `DeleteEntityAsync` 定义在基类中. 通过向服务器发起请求删除实体.
* `ConfirmationMessage` 执行操作前显示确认消息的回调函数.
* `GetDeleteConfirmationMessage` 定义在基类中. 你可以覆写这个方法 (或传递其它值给 `ConfirmationMessage` 参数) 以定制本地化消息.
因为"操作" 按钮现在有了两个操作, 变成了下拉菜单:
![blazor-edit-book-action](images/blazor-delete-book-action.png)
运行程序并尝试删除一本书.
## 完整的 CRUD UI 代码
下面是完整的创建图书管理CRUD页面的代码, 这些代码在上面是分成两部分开发的:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Components.Web
@inject IStringLocalizer<BookStoreResource> L
@inject AbpBlazorMessageLocalizerHelper<BookStoreResource> LH
@inherits AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>
<Card>
<CardHeader>
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h2>@L["Books"]</h2>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
<Button Color="Color.Primary"
Clicked="OpenCreateModalAsync">@L["NewBook"]</Button>
</Column>
</Row>
</CardHeader>
<CardBody>
<DataGrid TItem="BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
CurrentPage="CurrentPage"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="BookDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="BookDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="BookDto"
Text="@L["Edit"]"
Clicked="() => OpenEditModalAsync(context)" />
<EntityAction TItem="BookDto"
Text="@L["Delete"]"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]"></DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
<DisplayTemplate>
@L[$"Enum:BookType:{(int) context.Type}"]
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
<DisplayTemplate>
@context.PublishDate.ToShortDateString()
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
</DataGridColumn>
<DataGridColumn TItem="BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
<DisplayTemplate>
@context.CreationTime.ToLongDateString()
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
</CardBody>
</Card>
<Modal @ref="@CreateModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@L["NewBook"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@NewEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="NewEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="NewEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="CreateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
<Modal @ref="@EditModal">
<ModalBackdrop />
<ModalContent IsCentered="true">
<Form>
<ModalHeader>
<ModalTitle>@EditingEntity.Name</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["Name"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<Field>
<FieldLabel>@L["Type"]</FieldLabel>
<Select TValue="BookType" @bind-SelectedValue="@EditingEntity.Type">
@foreach (int bookTypeValue in Enum.GetValues(typeof(BookType)))
{
<SelectItem TValue="BookType" Value="@((BookType) bookTypeValue)">
@L[$"Enum:BookType:{bookTypeValue}"]
</SelectItem>
}
</Select>
</Field>
<Field>
<FieldLabel>@L["PublishDate"]</FieldLabel>
<DateEdit TValue="DateTime" @bind-Date="EditingEntity.PublishDate"/>
</Field>
<Field>
<FieldLabel>@L["Price"]</FieldLabel>
<NumericEdit TValue="float" @bind-Value="EditingEntity.Price"/>
</Field>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary"
Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<Button Color="Color.Primary"
Type="@ButtonType.Submit"
PreventDefaultOnSubmit="true"
Clicked="UpdateEntityAsync">@L["Save"]</Button>
</ModalFooter>
</Form>
</ModalContent>
</Modal>
````
{{end}}
## 下一章
查看本教程的[下一章](Part-4.md).
查看本教程的[下一章](Part-4.md).

60
docs/zh-Hans/Tutorials/Part-4.md

@ -2,27 +2,10 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","BlazorServer","NG"],
"DB": ["EF","Mongo"]
}
````
{{
if UI == "MVC"
UI_Text="mvc"
else if UI == "NG"
UI_Text="angular"
else
UI_Text="?"
end
if DB == "EF"
DB_Text="Entity Framework Core"
else if DB == "Mongo"
DB_Text="MongoDB"
else
DB_Text="?"
end
}}
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
@ -45,16 +28,32 @@ end
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合:
本教程根据你的**UI** 和 **数据库**偏好有多个版本,我们准备了几种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore)
* [Blazor UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore)
* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb)
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later).
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" && DB == "EF"}}
### 视频教程
本章也被录制为视频教程 **<a href="https://www.youtube.com/watch?v=aidRB4YFDLM&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=4" target="_blank">发布在YouTube</a>**.
{{end}}
## 解决方案中的测试项目
这一部分涵盖了 **服务器端** 测试. 解决方案中有多个测试项目:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
![bookstore-test-projects-v2](./images/bookstore-test-projects-mvc.png)
> 根据你选择的UI和数据库, 测试项目略微有所不同. 例如, 如果选择MongoDB, 那么 `Acme.BookStore.EntityFrameworkCore.Tests` 会变为 `Acme.BookStore.MongoDB.Tests`.
每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试:
@ -64,11 +63,11 @@ end
{{if DB=="EF"}}
> 测试项目配置为使用 **SQLite内存** 作为数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库.
> 测试项目配置为使用 **SQLite内存** 作为数据库. 创建一个单独的数据库实例并使用[数据种子系统](../Data-Seeding.md)初始化种子数据,为每个测试准备一个新的数据库.
{{else if DB=="Mongo"}}
> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)**库用于模拟MongoDB数据库. 创建一个单独的数据库实例并使用数据种子系统进行初始化种子数据,为每个测试准备一个新的数据库.
> **[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)**库用于模拟MongoDB数据库. 创建一个单独的数据库实例并使用[数据种子系统](../Data-Seeding.md)初始化种子数据,为每个测试准备一个新的数据库.
{{end}}
@ -78,12 +77,15 @@ end
## 测试 BookAppService
`Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类:
`Acme.BookStore.Application.Tests` 项目`Books` 命名空间(文件夹)中创建一个名叫 `BookAppService_Tests` 的测试类:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Xunit;
namespace Acme.BookStore.Books
@ -129,7 +131,7 @@ public async Task Should_Create_A_Valid_Book()
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
@ -201,7 +203,7 @@ namespace Acme.BookStore.Books
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(b => b.Name == "1984");
}
[Fact]
public async Task Should_Create_A_Valid_Book()
{
@ -211,7 +213,7 @@ namespace Acme.BookStore.Books
{
Name = "New test book 42",
Price = 10,
PublishDate = System.DateTime.Now,
PublishDate = DateTime.Now,
Type = BookType.ScienceFiction
}
);
@ -220,7 +222,7 @@ namespace Acme.BookStore.Books
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("New test book 42");
}
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
@ -244,7 +246,7 @@ namespace Acme.BookStore.Books
}
````
打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试:
打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行所有**测试:
![bookstore-appservice-tests](./images/bookstore-appservice-tests.png)
@ -252,4 +254,4 @@ namespace Acme.BookStore.Books
## 下一章
查看本教程的[下一章](Part-5.md).
查看本教程的[下一章](Part-5.md).

2
docs/zh-Hans/Tutorials/Todo/Index.md

@ -682,7 +682,7 @@ ABP提供了一个便捷的功能来自动创建客户端服务, 以方便地使
当启动 `TodoApp.HttpApi.Host` 项目后, 在`angular`文件夹中打开一个命令行终端并输入以下命令:
````bash
abp generate-proxy
abp generate-proxy -t ng
````
如果一切顺利, 它应该生成如下输出:

BIN
docs/zh-Hans/Tutorials/images/blazor-add-book-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-add-books-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-bookstore-book-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-delete-book-action.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-edit-book-action-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-edit-book-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-menu-bookstore.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
docs/zh-Hans/Tutorials/images/blazor-new-book-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/zh-Hans/Tutorials/images/generated-proxies-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/zh-Hans/Tutorials/images/vs-run-without-iisexpress.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

2
docs/zh-Hans/UI/Angular/Service-Proxies.md

@ -15,7 +15,7 @@ ABP CLI 的`generate-proxies` 命令在 `src/app` 文件夹中创建按模块名
在angular应用程序的**根文件夹**中运行以下命令:
```bash
abp generate-proxy
abp generate-proxy -t ng
```
它只为你自己的应用程序的服务创建代理. 不会为你正在使用的应用程序模块的服务创建代理(默认情况下). 有几个选项,参见[CLI文档](../../CLI).

32
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs

@ -17,6 +17,30 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting;
public static class AbpWebAssemblyHostBuilderExtensions
{
public async static Task<IAbpApplicationWithExternalServiceProvider> AddApplicationAsync<TStartupModule>(
[NotNull] this WebAssemblyHostBuilder builder,
Action<AbpWebAssemblyApplicationCreationOptions> options)
where TStartupModule : IAbpModule
{
Check.NotNull(builder, nameof(builder));
// Related this commit(https://github.com/dotnet/aspnetcore/commit/b99d805bc037fcac56afb79abeb7d5a43141c85e)
// Microsoft.AspNetCore.Blazor.BuildTools has been removed in net 5.0.
// This call may be removed when we find a suitable solution.
// System.Runtime.CompilerServices.AsyncStateMachineAttribute
Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add<AsyncStateMachineAttribute>();
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
builder.Services.AddSingleton(builder);
var application = await builder.Services.AddApplicationAsync<TStartupModule>(opts =>
{
options?.Invoke(new AbpWebAssemblyApplicationCreationOptions(builder, opts));
});
return application;
}
public static IAbpApplicationWithExternalServiceProvider AddApplication<TStartupModule>(
[NotNull] this WebAssemblyHostBuilder builder,
Action<AbpWebAssemblyApplicationCreationOptions> options)
@ -41,7 +65,7 @@ public static class AbpWebAssemblyHostBuilderExtensions
return application;
}
public static async Task InitializeAsync(
public async static Task InitializeAsync(
[NotNull] this IAbpApplicationWithExternalServiceProvider application,
[NotNull] IServiceProvider serviceProvider)
{
@ -51,12 +75,12 @@ public static class AbpWebAssemblyHostBuilderExtensions
((ComponentsClientScopeServiceProviderAccessor)serviceProvider
.GetRequiredService<IClientScopeServiceProviderAccessor>()).ServiceProvider = serviceProvider;
application.Initialize(serviceProvider);
await application.InitializeAsync(serviceProvider);
await InitializeModulesAsync(serviceProvider);
await SetCurrentLanguageAsync(serviceProvider);
}
private static async Task InitializeModulesAsync(IServiceProvider serviceProvider)
private async static Task InitializeModulesAsync(IServiceProvider serviceProvider)
{
foreach (var service in serviceProvider.GetServices<IAsyncInitialize>())
{
@ -64,7 +88,7 @@ public static class AbpWebAssemblyHostBuilderExtensions
}
}
private static async Task SetCurrentLanguageAsync(IServiceProvider serviceProvider)
private async static Task SetCurrentLanguageAsync(IServiceProvider serviceProvider)
{
var configurationClient = serviceProvider.GetRequiredService<ICachedApplicationConfigurationClient>();
var utilsService = serviceProvider.GetRequiredService<IAbpUtilsService>();

112
framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreAsyncIntegratedTestBase.cs

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.TestBase;
public class AbpAspNetCoreAsyncIntegratedTestBase<TModule>
where TModule : IAbpModule
{
protected WebApplication WebApplication { get; set; }
protected TestServer Server { get; set; }
protected HttpClient Client { get; set; }
protected IServiceProvider ServiceProvider { get; set; }
protected virtual T GetService<T>()
{
return ServiceProvider.GetService<T>();
}
protected virtual T GetRequiredService<T>()
{
return ServiceProvider.GetRequiredService<T>();
}
public virtual async Task InitializeAsync()
{
var builder = WebApplication.CreateBuilder();
builder.Host.ConfigureServices(services =>
{
services.AddSingleton<IHostLifetime, TestNoopHostLifetime>();
services.AddSingleton<IServer, TestServer>();
})
.UseAutofac();
await builder.Services.AddApplicationAsync<TModule>(options =>
{
options.Services.ReplaceConfiguration(builder.Configuration);
});
await ConfigureServicesAsync(builder.Services);
WebApplication = builder.Build();
await WebApplication.InitializeApplicationAsync();
await WebApplication.StartAsync();
Server = WebApplication.Services.GetRequiredService<IHost>().GetTestServer();
Client = Server.CreateClient();
ServiceProvider = Server.Services;
ServiceProvider.GetRequiredService<ITestServerAccessor>().Server = Server;
}
public virtual async Task DisposeAsync()
{
await WebApplication.DisposeAsync();
}
protected virtual Task ConfigureServicesAsync(IServiceCollection services)
{
return Task.CompletedTask;
}
#region GetUrl
/// <summary>
/// Gets default URL for given controller type.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>()
{
return "/" + typeof(TController).Name.RemovePostFix("Controller", "AppService", "ApplicationService", "Service");
}
/// <summary>
/// Gets default URL for given controller type's given action.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>(string actionName)
{
return GetUrl<TController>() + "/" + actionName;
}
/// <summary>
/// Gets default URL for given controller type's given action with query string parameters (as anonymous object).
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
protected virtual string GetUrl<TController>(string actionName, object queryStringParamsAsAnonymousObject)
{
var url = GetUrl<TController>(actionName);
var dictionary = new RouteValueDictionary(queryStringParamsAsAnonymousObject);
if (dictionary.Any())
{
url += "?" + dictionary.Select(d => $"{d.Key}={d.Value}").JoinAsString("&");
}
return url;
}
#endregion
}

2
framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs

@ -17,8 +17,6 @@ public abstract class AbpAspNetCoreIntegratedTestBase<TStartup> : AbpTestBaseWit
protected HttpClient Client { get; }
protected override IServiceProvider ServiceProvider { get; }
private readonly IHost _host;
protected AbpAspNetCoreIntegratedTestBase()

18
framework/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/TestNoopHostLifetime.cs

@ -0,0 +1,18 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
namespace Volo.Abp.AspNetCore.TestBase;
public class TestNoopHostLifetime : IHostLifetime
{
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

23
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.RequestLocalization;
using Microsoft.Extensions.DependencyInjection;
@ -11,6 +12,7 @@ using Volo.Abp.AspNetCore.Security.Claims;
using Volo.Abp.AspNetCore.Tracing;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Microsoft.AspNetCore.Builder;
@ -18,6 +20,27 @@ public static class AbpApplicationBuilderExtensions
{
private const string ExceptionHandlingMiddlewareMarker = "_AbpExceptionHandlingMiddleware_Added";
public async static Task InitializeApplicationAsync([NotNull] this IApplicationBuilder app)
{
Check.NotNull(app, nameof(app));
app.ApplicationServices.GetRequiredService<ObjectAccessor<IApplicationBuilder>>().Value = app;
var application = app.ApplicationServices.GetRequiredService<IAbpApplicationWithExternalServiceProvider>();
var applicationLifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
applicationLifetime.ApplicationStopping.Register(() =>
{
AsyncHelper.RunSync(() => application.ShutdownAsync());
});
applicationLifetime.ApplicationStopped.Register(() =>
{
application.Dispose();
});
await application.InitializeAsync(app.ApplicationServices);
}
public static void InitializeApplication([NotNull] this IApplicationBuilder app)
{
Check.NotNull(app, nameof(app));

16
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/AbpAspNetCoreServiceCollectionExtensions.cs

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Hosting;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Extensions.DependencyInjection;
@ -6,6 +8,16 @@ public static class AbpAspNetCoreServiceCollectionExtensions
{
public static IWebHostEnvironment GetHostingEnvironment(this IServiceCollection services)
{
return services.GetSingletonInstance<IWebHostEnvironment>();
var hostingEnvironment = services.GetSingletonInstanceOrNull<IWebHostEnvironment>();
if (hostingEnvironment == null)
{
return new EmptyHostingEnvironment()
{
EnvironmentName = Environments.Development
};
}
return hostingEnvironment;
}
}

19
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/EmptyHostingEnvironment.cs

@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
namespace Microsoft.Extensions.DependencyInjection;
internal class EmptyHostingEnvironment : IWebHostEnvironment
{
public string EnvironmentName { get; set; }
public string ApplicationName { get; set; }
public string WebRootPath { get; set; }
public IFileProvider WebRootFileProvider { get; set; }
public string ContentRootPath { get; set; }
public IFileProvider ContentRootFileProvider { get; set; }
}

35
framework/src/Volo.Abp.AspNetCore/Microsoft/Extensions/DependencyInjection/WebApplicationBuilderExtensions.cs

@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Volo.Abp;
using Volo.Abp.Modularity;
namespace Microsoft.Extensions.DependencyInjection;
public static class WebApplicationBuilderExtensions
{
public async static Task<IAbpApplicationWithExternalServiceProvider> AddApplicationAsync<TStartupModule>(
[NotNull] this WebApplicationBuilder builder,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
where TStartupModule : IAbpModule
{
return await builder.Services.AddApplicationAsync<TStartupModule>(options =>
{
options.Services.ReplaceConfiguration(builder.Configuration);
optionsAction?.Invoke(options);
});
}
public async static Task<IAbpApplicationWithExternalServiceProvider> AddApplicationAsync(
[NotNull] this WebApplicationBuilder builder,
[NotNull] Type startupModuleType,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
{
return await builder.Services.AddApplicationAsync(startupModuleType, options =>
{
options.Services.ReplaceConfiguration(builder.Configuration);
optionsAction?.Invoke(options);
});
}
}

37
framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/AbpBackgroundJobsRabbitMqModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.RabbitMQ;
using Volo.Abp.Threading;
@ -17,31 +18,37 @@ public class AbpBackgroundJobsRabbitMqModule : AbpModule
context.Services.AddSingleton(typeof(IJobQueue<>), typeof(JobQueue<>));
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
await StartJobQueueManagerAsync(context);
}
public async override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
await StopJobQueueManagerAsync(context);
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
StartJobQueueManager(context);
AsyncHelper.RunSync(() => StartJobQueueManagerAsync(context));
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
StopJobQueueManager(context);
AsyncHelper.RunSync(() => StopJobQueueManagerAsync(context));
}
private static void StartJobQueueManager(ApplicationInitializationContext context)
private async static Task StartJobQueueManagerAsync(ApplicationInitializationContext context)
{
AsyncHelper.RunSync(
() => context.ServiceProvider
.GetRequiredService<IJobQueueManager>()
.StartAsync()
);
await context.ServiceProvider
.GetRequiredService<IJobQueueManager>()
.StartAsync();
}
private static void StopJobQueueManager(ApplicationShutdownContext context)
private async static Task StopJobQueueManagerAsync(ApplicationShutdownContext context)
{
AsyncHelper.RunSync(
() => context.ServiceProvider
.GetRequiredService<IJobQueueManager>()
.StopAsync()
);
await context.ServiceProvider
.GetRequiredService<IJobQueueManager>()
.StopAsync();
}
}

15
framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs

@ -1,9 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
namespace Volo.Abp.BackgroundJobs;
@ -17,12 +19,21 @@ namespace Volo.Abp.BackgroundJobs;
)]
public class AbpBackgroundJobsModule : AbpModule
{
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
if (options.IsJobExecutionEnabled)
{
await context.AddBackgroundWorkerAsync<IBackgroundJobWorker>();
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value;
if (options.IsJobExecutionEnabled)
{
context.AddBackgroundWorker<IBackgroundJobWorker>();
AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync<IBackgroundJobWorker>());
}
}
}

8
framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@ -21,7 +21,7 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
return Task.CompletedTask;
}
public void Add(IBackgroundWorker worker)
public async Task AddAsync(IBackgroundWorker worker)
{
if (worker is IHangfireBackgroundWorker hangfireBackgroundWorker)
{
@ -32,7 +32,7 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
}
else
{
RecurringJob.AddOrUpdate(hangfireBackgroundWorker.RecurringJobId, () => hangfireBackgroundWorker.DoWorkAsync(),
RecurringJob.AddOrUpdate(hangfireBackgroundWorker.RecurringJobId,() => hangfireBackgroundWorker.DoWorkAsync(),
hangfireBackgroundWorker.CronExpression);
}
}
@ -42,7 +42,7 @@ public class HangfireBackgroundWorkerManager : IBackgroundWorkerManager, ISingle
if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase)
{
var timer = (AbpTimer)worker.GetType()
var timer = (AbpAsyncTimer) worker.GetType()
.GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker);
period = timer?.Period;
}

18
framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/AbpBackgroundWorkersQuartzModule.cs

@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Volo.Abp.Modularity;
using Volo.Abp.Quartz;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers.Quartz;
@ -34,6 +35,21 @@ public class AbpBackgroundWorkersQuartzModule : AbpModule
}
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var quartzBackgroundWorkerOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerQuartzOptions>>().Value;
if (quartzBackgroundWorkerOptions.IsAutoRegisterEnabled)
{
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IQuartzBackgroundWorker>().Where(x => x.AutoRegister);
foreach (var work in works)
{
await backgroundWorkerManager.AddAsync(work);
}
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var quartzBackgroundWorkerOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerQuartzOptions>>().Value;
@ -44,7 +60,7 @@ public class AbpBackgroundWorkersQuartzModule : AbpModule
foreach (var work in works)
{
backgroundWorkerManager.Add(work);
AsyncHelper.RunSync(() => backgroundWorkerManager.AddAsync(work));
}
}
}

4
framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzBackgroundWorkerManager.cs

@ -33,9 +33,9 @@ public class QuartzBackgroundWorkerManager : IBackgroundWorkerManager, ISingleto
}
}
public virtual void Add(IBackgroundWorker worker)
public virtual async Task AddAsync(IBackgroundWorker worker)
{
AsyncHelper.RunSync(() => ReScheduleJobAsync(worker));
await ReScheduleJobAsync(worker);
}
protected virtual async Task ReScheduleJobAsync(IBackgroundWorker worker)

25
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersModule.cs

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Modularity;
using Volo.Abp.Threading;
@ -10,6 +11,28 @@ namespace Volo.Abp.BackgroundWorkers;
)]
public class AbpBackgroundWorkersModule : AbpModule
{
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
if (options.IsEnabled)
{
await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.StartAsync();
}
}
public async override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
if (options.IsEnabled)
{
await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.StopAsync();
}
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;

6
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkerManager.cs

@ -26,15 +26,13 @@ public class BackgroundWorkerManager : IBackgroundWorkerManager, ISingletonDepen
_backgroundWorkers = new List<IBackgroundWorker>();
}
public virtual void Add(IBackgroundWorker worker)
public virtual async Task AddAsync(IBackgroundWorker worker)
{
_backgroundWorkers.Add(worker);
if (IsRunning)
{
AsyncHelper.RunSync(
() => worker.StartAsync()
);
await worker.StartAsync();
}
}

11
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/BackgroundWorkersApplicationInitializationContextExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@ -6,17 +7,17 @@ namespace Volo.Abp.BackgroundWorkers;
public static class BackgroundWorkersApplicationInitializationContextExtensions
{
public static ApplicationInitializationContext AddBackgroundWorker<TWorker>([NotNull] this ApplicationInitializationContext context)
public async static Task<ApplicationInitializationContext> AddBackgroundWorkerAsync<TWorker>([NotNull] this ApplicationInitializationContext context)
where TWorker : IBackgroundWorker
{
Check.NotNull(context, nameof(context));
context.AddBackgroundWorker(typeof(TWorker));
await context.AddBackgroundWorkerAsync(typeof(TWorker));
return context;
}
public static ApplicationInitializationContext AddBackgroundWorker([NotNull] this ApplicationInitializationContext context, [NotNull] Type workerType)
public async static Task<ApplicationInitializationContext> AddBackgroundWorkerAsync([NotNull] this ApplicationInitializationContext context, [NotNull] Type workerType)
{
Check.NotNull(context, nameof(context));
Check.NotNull(workerType, nameof(workerType));
@ -26,9 +27,9 @@ public static class BackgroundWorkersApplicationInitializationContextExtensions
throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!");
}
context.ServiceProvider
await context.ServiceProvider
.GetRequiredService<IBackgroundWorkerManager>()
.Add(
.AddAsync(
(IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType)
);

5
framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IBackgroundWorkerManager.cs

@ -1,4 +1,5 @@
using Volo.Abp.Threading;
using System.Threading.Tasks;
using Volo.Abp.Threading;
namespace Volo.Abp.BackgroundWorkers;
@ -13,5 +14,5 @@ public interface IBackgroundWorkerManager : IRunnable
/// <param name="worker">
/// The worker. It should be resolved from IOC.
/// </param>
void Add(IBackgroundWorker worker);
Task AddAsync(IBackgroundWorker worker);
}

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

@ -41,29 +41,29 @@ public class AbpCliCoreModule : AbpModule
Configure<AbpCliOptions>(options =>
{
//TODO: Define constants like done for GenerateProxyCommand.Name.
options.Commands["help"] = typeof(HelpCommand);
options.Commands["prompt"] = typeof(PromptCommand);
options.Commands["new"] = typeof(NewCommand);
options.Commands["get-source"] = typeof(GetSourceCommand);
options.Commands["update"] = typeof(UpdateCommand);
options.Commands["add-package"] = typeof(AddPackageCommand);
options.Commands["add-module"] = typeof(AddModuleCommand);
options.Commands["list-modules"] = typeof(ListModulesCommand);
options.Commands["login"] = typeof(LoginCommand);
options.Commands["login-info"] = typeof(LoginInfoCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands[HelpCommand.Name] = typeof(HelpCommand);
options.Commands[PromptCommand.Name] = typeof(PromptCommand);
options.Commands[NewCommand.Name] = typeof(NewCommand);
options.Commands[GetSourceCommand.Name] = typeof(GetSourceCommand);
options.Commands[UpdateCommand.Name] = typeof(UpdateCommand);
options.Commands[AddPackageCommand.Name] = typeof(AddPackageCommand);
options.Commands[AddModuleCommand.Name] = typeof(AddModuleCommand);
options.Commands[ListModulesCommand.Name] = typeof(ListModulesCommand);
options.Commands[LoginCommand.Name] = typeof(LoginCommand);
options.Commands[LoginInfoCommand.Name] = typeof(LoginInfoCommand);
options.Commands[LogoutCommand.Name] = typeof(LogoutCommand);
options.Commands[GenerateProxyCommand.Name] = typeof(GenerateProxyCommand);
options.Commands[RemoveProxyCommand.Name] = typeof(RemoveProxyCommand);
options.Commands["suite"] = typeof(SuiteCommand);
options.Commands["switch-to-preview"] = typeof(SwitchToPreviewCommand);
options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand);
options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand);
options.Commands["translate"] = typeof(TranslateCommand);
options.Commands["build"] = typeof(BuildCommand);
options.Commands["bundle"] = typeof(BundleCommand);
options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand);
options.Commands["install-libs"] = typeof(InstallLibsCommand);
options.Commands[SuiteCommand.Name] = typeof(SuiteCommand);
options.Commands[SwitchToPreviewCommand.Name] = typeof(SwitchToPreviewCommand);
options.Commands[SwitchToStableCommand.Name] = typeof(SwitchToStableCommand);
options.Commands[SwitchToNightlyCommand.Name] = typeof(SwitchToNightlyCommand);
options.Commands[TranslateCommand.Name] = typeof(TranslateCommand);
options.Commands[BuildCommand.Name] = typeof(BuildCommand);
options.Commands[BundleCommand.Name] = typeof(BundleCommand);
options.Commands[CreateMigrationAndRunMigratorCommand.Name] = typeof(CreateMigrationAndRunMigratorCommand);
options.Commands[InstallLibsCommand.Name] = typeof(InstallLibsCommand);
options.Commands[CleanCommand.Name] = typeof(CleanCommand);
});
Configure<AbpCliServiceProxyOptions>(options =>

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

@ -14,6 +14,8 @@ namespace Volo.Abp.Cli.Commands;
public class AddModuleCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "add-module";
private AddModuleInfoOutput _lastAddedModuleInfo;
public ILogger<AddModuleCommand> Logger { get; set; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs

@ -14,6 +14,8 @@ namespace Volo.Abp.Cli.Commands;
public class AddPackageCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "add-package";
public ILogger<AddPackageCommand> Logger { get; set; }
protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands;
public class BuildCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "build";
public IDotNetProjectDependencyFiller DotNetProjectDependencyFiller { get; set; }
public IChangedProjectFinder ChangedProjectFinder { get; set; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BundleCommand.cs

@ -11,6 +11,8 @@ namespace Volo.Abp.Cli.Commands;
public class BundleCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "bundle";
public ILogger<BundleCommand> Logger { get; set; }
public IBundlingService BundlingService { get; set; }

64
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CleanCommand.cs

@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Cli.Commands;
public class CleanCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "clean";
public ILogger<CleanCommand> Logger { get; set; }
public CleanCommand()
{
Logger = NullLogger<CleanCommand>.Instance;
}
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var binEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "bin", SearchOption.AllDirectories);
var objEntries = Directory.EnumerateDirectories(Directory.GetCurrentDirectory(), "obj", SearchOption.AllDirectories);
foreach (var path in binEntries.Concat(objEntries))
{
if (path.IndexOf("node_modules", StringComparison.OrdinalIgnoreCase) > 0)
{
Logger.LogInformation($"Skipping: {path}");
}
else
{
Logger.LogInformation($"Deleting: {path}");
Directory.Delete(path, true);
}
}
Logger.LogInformation($"BIN and OBJ folders have been successfully deleted!");
return Task.CompletedTask;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine(" abp clean");
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 "Delete all BIN and OBJ folders in current folder.";
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CreateMigrationAndRunMigratorCommand.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands;
public class CreateMigrationAndRunMigratorCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "create-migration-and-run-migrator";
public ICmdHelper CmdHelper { get; }
public ILogger<CreateMigrationAndRunMigratorCommand> Logger { get; set; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs

@ -13,6 +13,8 @@ namespace Volo.Abp.Cli.Commands;
public class GetSourceCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "get-source";
private readonly SourceCodeDownloadService _sourceCodeDownloadService;
public ModuleProjectBuilder ModuleProjectBuilder { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands;
public class HelpCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "help";
public ILogger<HelpCommand> Logger { get; set; }
protected AbpCliOptions AbpCliOptions { get; }
protected IServiceScopeFactory ServiceScopeFactory { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands;
public class InstallLibsCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "install-libs";
public ILogger<InstallLibsCommand> Logger { get; set; }
protected IInstallLibsService InstallLibsService { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ListModulesCommand.cs

@ -12,6 +12,8 @@ namespace Volo.Abp.Cli.Commands;
public class ListModulesCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "list-modules";
public ModuleInfoProvider ModuleInfoProvider { get; }
public ILogger<ListModulesCommand> Logger { get; set; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs

@ -18,6 +18,8 @@ namespace Volo.Abp.Cli.Commands;
public class LoginCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "login";
public ILogger<LoginCommand> Logger { get; set; }
protected AuthService AuthService { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginInfoCommand.cs

@ -11,6 +11,8 @@ namespace Volo.Abp.Cli.Commands;
public class LoginInfoCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "login-info";
public ILogger<LoginInfoCommand> Logger { get; set; }
protected AuthService AuthService { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs

@ -9,6 +9,8 @@ namespace Volo.Abp.Cli.Commands;
public class LogoutCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "logout";
public ILogger<LogoutCommand> Logger { get; set; }
protected AuthService AuthService { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -19,6 +19,8 @@ namespace Volo.Abp.Cli.Commands;
public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransientDependency
{
public const string Name = "new";
public ILogger<NewCommand> Logger { get; set; }
protected TemplateProjectBuilder TemplateProjectBuilder { get; }

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/PromptCommand.cs

@ -7,6 +7,8 @@ namespace Volo.Abp.Cli.Commands;
public class PromptCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "prompt";
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
return Task.CompletedTask;

6
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/ConnectionStringProvider.cs

@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.DependencyInjection;
@ -21,7 +22,8 @@ public class ConnectionStringProvider : ITransientDependency
case DatabaseManagementSystem.OracleDevart:
return "Data Source=MyProjectName;Integrated Security=yes;";
case DatabaseManagementSystem.SQLite:
return $"Data Source={Path.Combine(outputFolder, "MyProjectName.db")};".Replace("\\", "\\\\");
var comment = outputFolder.IsNullOrWhiteSpace() ? "//You need to change to an absolute filename" : string.Empty;
return $"Data Source={Path.Combine(outputFolder, "MyProjectName.db")};".Replace("\\", "\\\\") + comment;
default:
return null;
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SuiteCommand.cs

@ -15,6 +15,8 @@ namespace Volo.Abp.Cli.Commands;
public class SuiteCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "suite";
public ICmdHelper CmdHelper { get; }
private readonly AbpNuGetIndexUrlService _nuGetIndexUrlService;
private readonly NuGetService _nuGetService;

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToNightlyCommand.cs

@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands;
public class SwitchToNightlyCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "switch-to-nightly";
private readonly PackagePreviewSwitcher _packagePreviewSwitcher;
public SwitchToNightlyCommand(PackagePreviewSwitcher packagePreviewSwitcher)

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToPreviewCommand.cs

@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands;
public class SwitchToPreviewCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "switch-to-preview";
private readonly PackagePreviewSwitcher _packagePreviewSwitcher;
public SwitchToPreviewCommand(PackagePreviewSwitcher packagePreviewSwitcher)

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchToStableCommand.cs

@ -8,6 +8,8 @@ namespace Volo.Abp.Cli.Commands;
public class SwitchToStableCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "switch-to-stable";
private readonly PackagePreviewSwitcher _packagePreviewSwitcher;
public SwitchToStableCommand(PackagePreviewSwitcher packagePreviewSwitcher)

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

@ -15,6 +15,8 @@ namespace Volo.Abp.Cli.Commands;
public class TranslateCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "translate";
public ILogger<TranslateCommand> Logger { get; set; }
public Task ExecuteAsync(CommandLineArgs commandLineArgs)

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/UpdateCommand.cs

@ -13,6 +13,8 @@ namespace Volo.Abp.Cli.Commands;
public class UpdateCommand : IConsoleCommand, ITransientDependency
{
public const string Name = "update";
public ILogger<UpdateCommand> Logger { get; set; }
private readonly VoloNugetPackagesVersionUpdater _nugetPackagesVersionUpdater;

17
framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionApplicationExtensions.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Modularity;
@ -22,4 +23,20 @@ public static class ServiceCollectionApplicationExtensions
{
return AbpApplicationFactory.Create(startupModuleType, services, optionsAction);
}
public async static Task<IAbpApplicationWithExternalServiceProvider> AddApplicationAsync<TStartupModule>(
[NotNull] this IServiceCollection services,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
where TStartupModule : IAbpModule
{
return await AbpApplicationFactory.CreateAsync<TStartupModule>(services, optionsAction);
}
public async static Task<IAbpApplicationWithExternalServiceProvider> AddApplicationAsync(
[NotNull] this IServiceCollection services,
[NotNull] Type startupModuleType,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
{
return await AbpApplicationFactory.CreateAsync(startupModuleType, services, optionsAction);
}
}

125
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -23,6 +24,8 @@ public abstract class AbpApplicationBase : IAbpApplication
public IReadOnlyList<IAbpModuleDescriptor> Modules { get; }
private bool _configuredServices;
internal AbpApplicationBase(
[NotNull] Type startupModuleType,
[NotNull] IServiceCollection services,
@ -46,7 +49,21 @@ public abstract class AbpApplicationBase : IAbpApplication
services.AddCoreAbpServices(this, options);
Modules = LoadModules(services, options);
ConfigureServices();
if (!options.SkipConfigureServices)
{
ConfigureServices();
}
}
public virtual async Task ShutdownAsync()
{
using (var scope = ServiceProvider.CreateScope())
{
await scope.ServiceProvider
.GetRequiredService<IModuleManager>()
.ShutdownModulesAsync(new ApplicationShutdownContext(scope.ServiceProvider));
}
}
public virtual void Shutdown()
@ -70,6 +87,17 @@ public abstract class AbpApplicationBase : IAbpApplication
ServiceProvider.GetRequiredService<ObjectAccessor<IServiceProvider>>().Value = ServiceProvider;
}
protected virtual async Task InitializeModulesAsync()
{
using (var scope = ServiceProvider.CreateScope())
{
WriteInitLogs(scope.ServiceProvider);
await scope.ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModulesAsync(new ApplicationInitializationContext(scope.ServiceProvider));
}
}
protected virtual void InitializeModules()
{
using (var scope = ServiceProvider.CreateScope())
@ -111,8 +139,99 @@ public abstract class AbpApplicationBase : IAbpApplication
}
//TODO: We can extract a new class for this
protected virtual void ConfigureServices()
public virtual async Task ConfigureServicesAsync()
{
CheckMultipleConfigureServices();
var context = new ServiceConfigurationContext(Services);
Services.AddSingleton(context);
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = context;
}
}
//PreConfigureServices
foreach (var module in Modules.Where(m => m.Instance is IPreConfigureServices))
{
try
{
await ((IPreConfigureServices)module.Instance).PreConfigureServicesAsync(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPreConfigureServices.PreConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
var assemblies = new HashSet<Assembly>();
//ConfigureServices
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
if (!abpModule.SkipAutoServiceRegistration)
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
}
}
}
try
{
await module.Instance.ConfigureServicesAsync(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IAbpModule.ConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
//PostConfigureServices
foreach (var module in Modules.Where(m => m.Instance is IPostConfigureServices))
{
try
{
await ((IPostConfigureServices)module.Instance).PostConfigureServicesAsync(context);
}
catch (Exception ex)
{
throw new AbpInitializationException($"An error occurred during {nameof(IPostConfigureServices.PostConfigureServicesAsync)} phase of the module {module.Type.AssemblyQualifiedName}. See the inner exception for details.", ex);
}
}
foreach (var module in Modules)
{
if (module.Instance is AbpModule abpModule)
{
abpModule.ServiceConfigurationContext = null;
}
}
_configuredServices = true;
}
private void CheckMultipleConfigureServices()
{
if (_configuredServices)
{
throw new AbpInitializationException("Services have already been configured! If you call ConfigureServicesAsync method, you must have set AbpApplicationCreationOptions.SkipConfigureServices tu true before.");
}
}
//TODO: We can extract a new class for this
public virtual void ConfigureServices()
{
CheckMultipleConfigureServices();
var context = new ServiceConfigurationContext(Services);
Services.AddSingleton(context);
@ -185,5 +304,7 @@ public abstract class AbpApplicationBase : IAbpApplication
abpModule.ServiceConfigurationContext = null;
}
}
_configuredServices = true;
}
}

2
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationCreationOptions.cs

@ -19,6 +19,8 @@ public class AbpApplicationCreationOptions
[NotNull]
public AbpConfigurationBuilderOptions Configuration { get; }
public bool SkipConfigureServices { get; set; }
public AbpApplicationCreationOptions([NotNull] IServiceCollection services)
{
Services = Check.NotNull(services, nameof(services));

55
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationFactory.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
@ -7,6 +8,60 @@ namespace Volo.Abp;
public static class AbpApplicationFactory
{
public async static Task<IAbpApplicationWithInternalServiceProvider> CreateAsync<TStartupModule>(
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
where TStartupModule : IAbpModule
{
var app = Create(typeof(TStartupModule), options =>
{
options.SkipConfigureServices = true;
optionsAction?.Invoke(options);
});
await app.ConfigureServicesAsync();
return app;
}
public async static Task<IAbpApplicationWithInternalServiceProvider> CreateAsync(
[NotNull] Type startupModuleType,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
{
var app = new AbpApplicationWithInternalServiceProvider(startupModuleType, options =>
{
options.SkipConfigureServices = true;
optionsAction?.Invoke(options);
});
await app.ConfigureServicesAsync();
return app;
}
public async static Task<IAbpApplicationWithExternalServiceProvider> CreateAsync<TStartupModule>(
[NotNull] IServiceCollection services,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
where TStartupModule : IAbpModule
{
var app = Create(typeof(TStartupModule), services, options =>
{
options.SkipConfigureServices = true;
optionsAction?.Invoke(options);
});
await app.ConfigureServicesAsync();
return app;
}
public async static Task<IAbpApplicationWithExternalServiceProvider> CreateAsync(
[NotNull] Type startupModuleType,
[NotNull] IServiceCollection services,
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
{
var app = new AbpApplicationWithExternalServiceProvider(startupModuleType, services, options =>
{
options.SkipConfigureServices = true;
optionsAction?.Invoke(options);
});
await app.ConfigureServicesAsync();
return app;
}
public static IAbpApplicationWithInternalServiceProvider Create<TStartupModule>(
[CanBeNull] Action<AbpApplicationCreationOptions> optionsAction = null)
where TStartupModule : IAbpModule

10
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@ -35,6 +36,15 @@ internal class AbpApplicationWithExternalServiceProvider : AbpApplicationBase, I
SetServiceProvider(serviceProvider);
}
public async Task InitializeAsync(IServiceProvider serviceProvider)
{
Check.NotNull(serviceProvider, nameof(serviceProvider));
SetServiceProvider(serviceProvider);
await InitializeModulesAsync();
}
public void Initialize([NotNull] IServiceProvider serviceProvider)
{
Check.NotNull(serviceProvider, nameof(serviceProvider));

7
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
@ -44,6 +45,12 @@ internal class AbpApplicationWithInternalServiceProvider : AbpApplicationBase, I
return ServiceProvider;
}
public async Task InitializeAsync()
{
CreateServiceProvider();
await InitializeModulesAsync();
}
public void Initialize()
{
CreateServiceProvider();

13
framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplication.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
@ -22,6 +23,18 @@ public interface IAbpApplication : IModuleContainer, IDisposable
/// This can not be used before initialize the application.
/// </summary>
IServiceProvider ServiceProvider { get; }
/// <summary>
/// Calls the Pre/Post/ConfigureServicesAsync methods of the modules.
/// If you use this method, you must have set the <see cref="AbpApplicationCreationOptions.SkipConfigureServices"/>
/// option to true before.
/// </summary>
Task ConfigureServicesAsync();
/// <summary>
/// Used to gracefully shutdown the application and all modules.
/// </summary>
Task ShutdownAsync();
/// <summary>
/// Used to gracefully shutdown the application and all modules.

8
framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp;
@ -9,6 +10,13 @@ public interface IAbpApplicationWithExternalServiceProvider : IAbpApplication
/// Sets the service provider, but not initializes the modules.
/// </summary>
void SetServiceProvider([NotNull] IServiceProvider serviceProvider);
/// <summary>
/// Sets the service provider and initializes all the modules.
/// If <see cref="SetServiceProvider"/> was called before, the same
/// <see cref="serviceProvider"/> instance should be passed to this method.
/// </summary>
Task InitializeAsync([NotNull] IServiceProvider serviceProvider);
/// <summary>
/// Sets the service provider and initializes all the modules.

8
framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithInternalServiceProvider.cs

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp;
@ -10,6 +11,13 @@ public interface IAbpApplicationWithInternalServiceProvider : IAbpApplication
/// </summary>
IServiceProvider CreateServiceProvider();
/// <summary>
/// Creates the service provider and initializes all the modules.
/// If <see cref="CreateServiceProvider"/> method was called before,
/// it does not re-create it, but uses the previous one.
/// </summary>
Task InitializeAsync();
/// <summary>
/// Creates the service provider and initializes all the modules.
/// If <see cref="CreateServiceProvider"/> method was called before,

5
framework/src/Volo.Abp.Core/Volo/Abp/IOnApplicationInitialization.cs

@ -1,8 +1,11 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp;
public interface IOnApplicationInitialization
{
Task OnApplicationInitializationAsync([NotNull] ApplicationInitializationContext context);
void OnApplicationInitialization([NotNull] ApplicationInitializationContext context);
}

5
framework/src/Volo.Abp.Core/Volo/Abp/IOnApplicationShutdown.cs

@ -1,8 +1,11 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp;
public interface IOnApplicationShutdown
{
Task OnApplicationShutdownAsync([NotNull] ApplicationShutdownContext context);
void OnApplicationShutdown([NotNull] ApplicationShutdownContext context);
}

43
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModule.cs

@ -1,5 +1,6 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -30,36 +31,78 @@ public abstract class AbpModule :
private ServiceConfigurationContext _serviceConfigurationContext;
public virtual Task PreConfigureServicesAsync(ServiceConfigurationContext context)
{
PreConfigureServices(context);
return Task.CompletedTask;
}
public virtual void PreConfigureServices(ServiceConfigurationContext context)
{
}
public virtual Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
ConfigureServices(context);
return Task.CompletedTask;
}
public virtual void ConfigureServices(ServiceConfigurationContext context)
{
}
public virtual Task PostConfigureServicesAsync(ServiceConfigurationContext context)
{
PostConfigureServices(context);
return Task.CompletedTask;
}
public virtual void PostConfigureServices(ServiceConfigurationContext context)
{
}
public virtual Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
OnPreApplicationInitialization(context);
return Task.CompletedTask;
}
public virtual void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
OnApplicationInitialization(context);
return Task.CompletedTask;
}
public virtual void OnApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
{
OnPostApplicationInitialization(context);
return Task.CompletedTask;
}
public virtual void OnPostApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
{
OnApplicationShutdown(context);
return Task.CompletedTask;
}
public virtual void OnApplicationShutdown(ApplicationShutdownContext context)
{

36
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/DefaultModuleLifecycleContributor.cs

@ -1,7 +1,17 @@
namespace Volo.Abp.Modularity;
using System.Threading.Tasks;
namespace Volo.Abp.Modularity;
public class OnApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)
{
if (module is IOnApplicationInitialization onApplicationInitialization)
{
await onApplicationInitialization.OnApplicationInitializationAsync(context);
}
}
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnApplicationInitialization)?.OnApplicationInitialization(context);
@ -10,6 +20,14 @@ public class OnApplicationInitializationModuleLifecycleContributor : ModuleLifec
public class OnApplicationShutdownModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public async override Task ShutdownAsync(ApplicationShutdownContext context, IAbpModule module)
{
if (module is IOnApplicationShutdown onApplicationShutdown)
{
await onApplicationShutdown.OnApplicationShutdownAsync(context);
}
}
public override void Shutdown(ApplicationShutdownContext context, IAbpModule module)
{
(module as IOnApplicationShutdown)?.OnApplicationShutdown(context);
@ -18,6 +36,14 @@ public class OnApplicationShutdownModuleLifecycleContributor : ModuleLifecycleCo
public class OnPreApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)
{
if (module is IOnPreApplicationInitialization onPreApplicationInitialization)
{
await onPreApplicationInitialization.OnPreApplicationInitializationAsync(context);
}
}
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnPreApplicationInitialization)?.OnPreApplicationInitialization(context);
@ -26,6 +52,14 @@ public class OnPreApplicationInitializationModuleLifecycleContributor : ModuleLi
public class OnPostApplicationInitializationModuleLifecycleContributor : ModuleLifecycleContributorBase
{
public async override Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)
{
if (module is IOnPostApplicationInitialization onPostApplicationInitialization)
{
await onPostApplicationInitialization.OnPostApplicationInitializationAsync(context);
}
}
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnPostApplicationInitialization)?.OnPostApplicationInitialization(context);

6
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs

@ -1,6 +1,10 @@
namespace Volo.Abp.Modularity;
using System.Threading.Tasks;
namespace Volo.Abp.Modularity;
public interface IAbpModule
{
Task ConfigureServicesAsync(ServiceConfigurationContext context);
void ConfigureServices(ServiceConfigurationContext context);
}

7
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IModuleLifecycleContributor.cs

@ -1,11 +1,16 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Modularity;
public interface IModuleLifecycleContributor : ITransientDependency
{
Task InitializeAsync([NotNull] ApplicationInitializationContext context, [NotNull] IAbpModule module);
void Initialize([NotNull] ApplicationInitializationContext context, [NotNull] IAbpModule module);
Task ShutdownAsync([NotNull] ApplicationShutdownContext context, [NotNull] IAbpModule module);
void Shutdown([NotNull] ApplicationShutdownContext context, [NotNull] IAbpModule module);
}

7
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IModuleManager.cs

@ -1,10 +1,15 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Modularity;
public interface IModuleManager
{
Task InitializeModulesAsync([NotNull] ApplicationInitializationContext context);
void InitializeModules([NotNull] ApplicationInitializationContext context);
Task ShutdownModulesAsync([NotNull] ApplicationShutdownContext context);
void ShutdownModules([NotNull] ApplicationShutdownContext context);
}

5
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs

@ -1,8 +1,11 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Modularity;
public interface IOnPostApplicationInitialization
{
Task OnPostApplicationInitializationAsync([NotNull] ApplicationInitializationContext context);
void OnPostApplicationInitialization([NotNull] ApplicationInitializationContext context);
}

5
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs

@ -1,8 +1,11 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Modularity;
public interface IOnPreApplicationInitialization
{
Task OnPreApplicationInitializationAsync([NotNull] ApplicationInitializationContext context);
void OnPreApplicationInitialization([NotNull] ApplicationInitializationContext context);
}

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

@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Volo.Abp.Modularity;
public interface IPostConfigureServices
{
Task PostConfigureServicesAsync(ServiceConfigurationContext context);
void PostConfigureServices(ServiceConfigurationContext context);
}

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

@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
namespace Volo.Abp.Modularity;
public interface IPreConfigureServices
{
Task PreConfigureServicesAsync(ServiceConfigurationContext context);
void PreConfigureServices(ServiceConfigurationContext context);
}

14
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleLifecycleContributorBase.cs

@ -1,11 +1,23 @@
namespace Volo.Abp.Modularity;
using System.Threading.Tasks;
namespace Volo.Abp.Modularity;
public abstract class ModuleLifecycleContributorBase : IModuleLifecycleContributor
{
public virtual Task InitializeAsync(ApplicationInitializationContext context, IAbpModule module)
{
return Task.CompletedTask;
}
public virtual void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
}
public virtual Task ShutdownAsync(ApplicationShutdownContext context, IAbpModule module)
{
return Task.CompletedTask;
}
public virtual void Shutdown(ApplicationShutdownContext context, IAbpModule module)
{
}

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -30,6 +31,26 @@ public class ModuleManager : IModuleManager, ISingletonDependency
.ToArray();
}
public virtual async Task InitializeModulesAsync(ApplicationInitializationContext context)
{
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in _moduleContainer.Modules)
{
try
{
await contributor.InitializeAsync(context, module.Instance);
}
catch (Exception 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);
}
}
}
_logger.LogInformation("Initialized all ABP modules.");
}
public void InitializeModules(ApplicationInitializationContext context)
{
foreach (var contributor in _lifecycleContributors)
@ -50,6 +71,26 @@ public class ModuleManager : IModuleManager, ISingletonDependency
_logger.LogInformation("Initialized all ABP modules.");
}
public virtual async Task ShutdownModulesAsync(ApplicationShutdownContext context)
{
var modules = _moduleContainer.Modules.Reverse().ToList();
foreach (var contributor in _lifecycleContributors)
{
foreach (var module in modules)
{
try
{
await contributor.ShutdownAsync(context, module.Instance);
}
catch (Exception 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);
}
}
}
}
public void ShutdownModules(ApplicationShutdownContext context)
{
var modules = _moduleContainer.Modules.Reverse().ToList();

4
framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo.Abp.EntityFrameworkCore.Oracle.Devart.csproj

@ -19,8 +19,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Devart.Data.Oracle.EFCore" Version="9.14.1369" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Devart.Data.Oracle.EFCore" Version="9.15.1410" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" />
</ItemGroup>
</Project>

12
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs

@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.DistributedLocking;
using Volo.Abp.EventBus.Abstractions;
@ -12,6 +13,7 @@ using Volo.Abp.Json;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
namespace Volo.Abp.EventBus;
@ -30,10 +32,16 @@ public class AbpEventBusModule : AbpModule
AddEventHandlers(context.Services);
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
await context.AddBackgroundWorkerAsync<OutboxSenderManager>();
await context.AddBackgroundWorkerAsync<InboxProcessManager>();
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
context.AddBackgroundWorker<OutboxSenderManager>();
context.AddBackgroundWorker<InboxProcessManager>();
AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync<OutboxSenderManager>());
AsyncHelper.RunSync(() => context.AddBackgroundWorkerAsync<InboxProcessManager>());
}
private static void AddEventHandlers(IServiceCollection services)

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

Loading…
Cancel
Save