diff --git a/.github/workflows/cancel-workflow.yml b/.github/workflows/cancel-workflow.yml
new file mode 100644
index 0000000000..78bbf94b3b
--- /dev/null
+++ b/.github/workflows/cancel-workflow.yml
@@ -0,0 +1,12 @@
+name: cancel-workflow
+on: [push]
+jobs:
+ cancel:
+ name: 'Cancel Previous Runs'
+ runs-on: ubuntu-latest
+ timeout-minutes: 3
+ steps:
+ - uses: styfle/cancel-workflow-action@0.6.0
+ with:
+ workflow_id: 10629,1299107,2792859,8268314
+ access_token: ${{ github.token }}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index 10ca9a0a67..e23ed3fe5b 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -303,6 +303,13 @@
"ProductPrice": "Product Price",
"AdditionalDeveloper": "Additional developer",
"ThisPaymentHasBeenAlreadyUsed": "This payment has been already used",
- "PaymentTimeCannotBeFutureTime": "Payment time cannot be future time"
+ "PaymentTimeCannotBeFutureTime": "Payment time cannot be a future time!",
+ "SaveAndDownload": "Save and Download",
+ "BillingInfo": "Billing Info",
+ "GenerateInvoice": "Create Invoice",
+ "DeleteInvoice" : "Delete Invoice",
+ "SuccessfullyDeleted": "Successfully deleted!",
+ "PaymentStateSetTo" : "Payment state set to {0}",
+ "ChangeState": "Change State"
}
}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
index aba7589487..6c61b37cf0 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/en.json
@@ -377,7 +377,7 @@
"AddressValidationMessage": "Address is too long!",
"TaxNoValidationMessage": "TAX/VAT No is too long!",
"NotesValidationMessage": "Notes field is too long!",
- "CheckYourBillingInfo": "You can create your invoice only once! Check your billing info before creating your invoice.",
+ "CheckYourBillingInfo": "You can create your invoice only once! Check your billing information before creating your invoice.",
"Volo.AbpIo.Commercial:030000": "You already used your trial period.",
"Volo.AbpIo.Commercial:030001": "There is already an organization called \"{CompanyName}\"",
"Volo.AbpIo.Commercial:030002": "You already own an organization that has a license.",
@@ -391,4 +391,4 @@
"TryForFree": "Try For Free",
"TrialLicenseExpiredInfo": "Your trial license period has expired!"
}
-}
\ No newline at end of file
+}
diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json
index 2031279d6d..ccb9f5ecd1 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Commercial/Localization/Resources/it.json
@@ -358,6 +358,25 @@
"WeWillSendYouADownloadLink": "Un link per scaricare il libro e-book è stato spedito a {0}. Controlla nella posta in arrivo / indesiderata / spam!",
"InvalidFormInputs": "Per favore, digita le informazioni valide specificate nel modulo",
"DDDBookEmailBody": "Grazie. Per scaricare il tuo libro, clicca qui.",
- "FreeDDDEBook": "E-Book Gratuito DDD"
+ "FreeDDDEBook": "E-Book Gratuito DDD",
+ "StartFree": "Inizia gratuitamente",
+ "FreeTrial": "Prova Gratuita",
+ "AcceptsMarketingCommunications": "Si desidero ricevere comunicazioni marketing riguardanti ABP Commercial",
+ "PurposeOfUsage": "Proposta di utilizzo",
+ "Industry": "Settore",
+ "Choose": "- Scegli -",
+ "CompanyOrganizationName": "Azienda / Nome organizzazione",
+ "CompanySize": "Dimensioni Azienda",
+ "Next": "Prossimo",
+ "StartTrial": "Inizia la mia prova gratuita",
+ "ContactUsIssues": "Contattaci per qualsiasi problema",
+ "TrialActivatedWarning": "Caro {0}, un utente ha diritto di avere 1 solo periodo di prova gratuito. Hai già utilizzato il tuo periodo di prova.",
+ "ActivationRequirement": "Ti manca un solo passo per iniziare il tuo periodo di prova gratuito. Abbiamo inviato un'email a {0} per attivare il tuo account. Controlla la tua casella di posta e non dimenticare la cartella spam!",
+ "SaveAndDownload": "Salva e Scarica",
+ "CompanyNameValidationMessage": "Il nome dell'azienda è troppo lungo!",
+ "AddressValidationMessage": "L'indirizzo è troppo lungo!",
+ "TaxNoValidationMessage": "TAX/VAT (IVA) è troppo lungo!",
+ "NotesValidationMessage": "Il campo note è troppo lungo!",
+ "CheckYourBillingInfo": "Puoi creare una fattura solo una volta! Controlla i tuoi dati di fatturazione prima di creare una fattura."
}
-}
\ No newline at end of file
+}
diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md
index 6b906fe194..66fded1a29 100644
--- a/docs/en/Authorization.md
+++ b/docs/en/Authorization.md
@@ -256,7 +256,7 @@ myGroup.AddPermission("Book_Creation")
#### Depending on a Global Feature
-Use the `RequireFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled:
+Use the `RequireGlobalFeatures` extension method on your permission definition to make the permission available only if a given feature is enabled:
````csharp
myGroup.AddPermission("Book_Creation")
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index 4e895e1277..0638543e44 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -161,7 +161,7 @@ abp add-package [options]
Example:
````
-abp add-package Volo.Abp.MongoDB
+abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
````
* This example adds the Volo.Abp.MongoDB package to the project.
@@ -170,7 +170,14 @@ abp add-package Volo.Abp.MongoDB
* `--project` or `-p`: Specifies the project (.csproj) file path. If not specified, CLI tries to find a .csproj file in the current directory.
* `--with-source-code`: Downloads the source code of the package to your solution folder and uses local project references instead of NuGet/NPM packages.
-* `--add-to-solution-file`: Adds the downloaded package to your solution file, so you will also see the package when you open the solution on a IDE. (only available when `--with-source-code` is True.)
+* `--add-to-solution-file`: Adds the downloaded package to your solution file, so you will also see the package when you open the solution on a IDE. (only available when `--with-source-code` is True)
+
+> Currently only the source code of the basic theme packages([MVC](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme) and [Blazor](https://docs.abp.io/en/abp/latest/UI/Blazor/Basic-Theme)) can be downloaded.
+> - Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic
+> - Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme
+> - Volo.Abp.AspNetCore.Components.Web.BasicTheme
+> - Volo.Abp.AspNetCore.Components.Server.BasicTheme
+
### add-module
@@ -454,4 +461,4 @@ abp install-libs [options]
## See Also
-* [Examples for the new command](CLI-New-Command-Samples.md)
\ No newline at end of file
+* [Examples for the new command](CLI-New-Command-Samples.md)
diff --git a/docs/en/Data-Filtering.md b/docs/en/Data-Filtering.md
index 6df738cf18..dc237f6151 100644
--- a/docs/en/Data-Filtering.md
+++ b/docs/en/Data-Filtering.md
@@ -239,15 +239,25 @@ protected override Expression> CreateFilterExpression
+[ExposeServices(typeof(IMongoDbRepositoryFilterer))]
+public class BookMongoDbRepositoryFilterer : MongoDbRepositoryFilterer , ITransientDependency
{
- protected override void AddGlobalFilters(List> filters)
+ public BookMongoDbRepositoryFilterer(
+ IDataFilter dataFilter,
+ ICurrentTenant currentTenant) :
+ base(dataFilter, currentTenant)
{
+ }
+
+ public override void AddGlobalFilters(List> filters)
+ {
+ base.AddGlobalFilters(filters);
+
if (DataFilter.IsEnabled())
{
filters.Add(Builders.Filter.Eq(e => ((IIsActive)e).IsActive, true));
@@ -256,7 +266,7 @@ public class BookRepository : MongoDbRepository : MongoDbRepository
@@ -271,6 +281,8 @@ public abstract class MyMongoRepository : MongoD
protected override void AddGlobalFilters(List> filters)
{
+ base.AddGlobalFilters(filters);
+
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))
&& DataFilter.IsEnabled())
{
@@ -278,6 +290,15 @@ public abstract class MyMongoRepository : MongoD
}
}
}
-````
-> See "Set Default Repository Classes" section of the [MongoDb Integration document](MongoDB.md) to learn how to replace default repository base with your custom class.
\ No newline at end of file
+
+public class MyMongoDbModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ //.......
+ context.Services
+ .Replace(ServiceDescriptor.Transient(typeof(IMongoDbRepositoryFilterer<,>),typeof(MyMongoDbRepositoryFilterer<,>)));
+ }
+}
+````
\ No newline at end of file
diff --git a/docs/en/Data-Transfer-Objects.md b/docs/en/Data-Transfer-Objects.md
index bc81205b66..4d45570c1d 100644
--- a/docs/en/Data-Transfer-Objects.md
+++ b/docs/en/Data-Transfer-Objects.md
@@ -210,15 +210,15 @@ namespace AbpDemo
{
//Create the query
var query = _productRepository
- .OrderBy(input.Sorting)
- .Skip(input.SkipCount)
- .Take(input.MaxResultCount);
+ .OrderBy(input.Sorting);
//Get total count from the repository
var totalCount = await query.CountAsync();
//Get entities from the repository
- List products = await query.ToListAsync();
+ List products = await query
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount).ToListAsync();
//Map entities to DTOs
List productDtos =
diff --git a/docs/en/Modules/Permission-Management.md b/docs/en/Modules/Permission-Management.md
index ba4b34d8ed..b97ddfca6c 100644
--- a/docs/en/Modules/Permission-Management.md
+++ b/docs/en/Modules/Permission-Management.md
@@ -28,7 +28,7 @@ In this dialog, you can grant permissions for the selected role. The tabs in the
## IPermissionManager
-`IPermissionManager` is the main service provided by this module. It is used to read and change the permission values. `IPermissionManager` is typically used by the *Feature Management Dialog*. However, you can inject it if you need to set a permission value.
+`IPermissionManager` is the main service provided by this module. It is used to read and change the permission values. `IPermissionManager` is typically used by the *Permission Management Dialog*. However, you can inject it if you need to set a permission value.
> If you just want to read/check permission values for the current user, use the `IAuthorizationService` or the `[Authorize]` attribute as explained in the [Authorization document](../Authorization.md).
@@ -91,9 +91,9 @@ public class CustomPermissionManagementProvider : PermissionManagementProvider
}
````
-`PermissionManagementProvider` base class makes the default implementation (using the `IPermissionGrantRepository`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record).
+`PermissionManagementProvider` base class makes the default implementation (using the `IPermissionGrantRepository`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each permission value record).
-Once you create your provider class, you should register it using the `FeatureManagementOptions` [options class](../Options.md):
+Once you create your provider class, you should register it using the `PermissionManagementOptions` [options class](../Options.md):
````csharp
Configure(options =>
diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md
index bf4ab30031..4eedcc969d 100644
--- a/docs/en/Modules/Setting-Management.md
+++ b/docs/en/Modules/Setting-Management.md
@@ -99,7 +99,7 @@ public class CustomSettingProvider : SettingManagementProvider
}
````
-`SettingManagementProvider` base class makes the default implementation (using the `ISettingManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each feature value record).
+`SettingManagementProvider` base class makes the default implementation (using the `ISettingManagementStore`) for you. You can override base methods as you need. Every provider must have a unique name, which is `Custom` in this example (keep it short since it is saved to database for each setting value record).
Once you create your provider class, you should register it using the `SettingManagementOptions` [options class](../Options.md):
diff --git a/docs/en/Text-Templating-Scriban.md b/docs/en/Text-Templating-Scriban.md
index 51b6c50f88..324fed7b14 100644
--- a/docs/en/Text-Templating-Scriban.md
+++ b/docs/en/Text-Templating-Scriban.md
@@ -1,4 +1,4 @@
-# Razor Integration
+# Scriban Integration
## Installation
diff --git a/docs/en/Tutorials/Part-9.md b/docs/en/Tutorials/Part-9.md
index 2a3e43c736..e2df7c262e 100644
--- a/docs/en/Tutorials/Part-9.md
+++ b/docs/en/Tutorials/Part-9.md
@@ -1022,7 +1022,7 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the `Acme.BookStor
````
-* This code is similar to the `Books.razor`, except it doesn't inherit from the `BlazorisePageBase`, but uses its own implementation.
+* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`, but uses its own implementation.
* Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../API/Dynamic-CSharp-API-Clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage.
* Injects the `IAuthorizationService` to check [permissions](../Authorization.md).
* Injects the `IObjectMapper` for [object to object mapping](../Object-To-Object-Mapping.md).
diff --git a/docs/en/Tutorials/Todo/Index.md b/docs/en/Tutorials/Todo/Index.md
index 9f4a33a9c0..8eca93fead 100644
--- a/docs/en/Tutorials/Todo/Index.md
+++ b/docs/en/Tutorials/Todo/Index.md
@@ -618,7 +618,7 @@ Open the `Index.razor` file in the `Pages` folder of the *TodoApp.Blazor* projec
### Index.razor.css
-As the final touch, open the `Index.razor.css` file in the `Pages` folder of the *TodoApp.Web* project and replace with the following content:
+As the final touch, open the `Index.razor.css` file in the `Pages` folder of the *TodoApp.Blazor* project and replace with the following content:
````css
#TodoList{
diff --git a/docs/en/UI/Angular/Settings.md b/docs/en/UI/Angular/Settings.md
index 0dd29025e5..69794d5d5b 100644
--- a/docs/en/UI/Angular/Settings.md
+++ b/docs/en/UI/Angular/Settings.md
@@ -2,7 +2,7 @@
You can get settings on the client-side using the [config state service](./Config-State.md) if they are allowed by their setting definition on the server-side.
-> This document only explains how settings work in the Angular UI projects. See the [settings document](../../../Settings.md) to understand the ABP setting system.
+> This document only explains how settings work in the Angular UI projects. See the [settings document](../../Settings.md) to understand the ABP setting system.
## Before Use
diff --git a/docs/zh-Hans/Data-Filtering.md b/docs/zh-Hans/Data-Filtering.md
index 45adcb6b05..74192cf426 100644
--- a/docs/zh-Hans/Data-Filtering.md
+++ b/docs/zh-Hans/Data-Filtering.md
@@ -188,15 +188,25 @@ protected override Expression> CreateFilterExpression
+[ExposeServices(typeof(IMongoDbRepositoryFilterer))]
+public class BookMongoDbRepositoryFilterer : MongoDbRepositoryFilterer , ITransientDependency
{
- protected override void AddGlobalFilters(List> filters)
+ public BookMongoDbRepositoryFilterer(
+ IDataFilter dataFilter,
+ ICurrentTenant currentTenant) :
+ base(dataFilter, currentTenant)
{
+ }
+
+ public override void AddGlobalFilters(List> filters)
+ {
+ base.AddGlobalFilters(filters);
+
if (DataFilter.IsEnabled())
{
filters.Add(Builders.Filter.Eq(e => ((IIsActive)e).IsActive, true));
@@ -205,7 +215,7 @@ public class BookRepository : MongoDbRepository : MongoDbRepository
@@ -220,6 +230,8 @@ public abstract class MyMongoRepository : MongoD
protected override void AddGlobalFilters(List> filters)
{
+ base.AddGlobalFilters(filters);
+
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))
&& DataFilter.IsEnabled())
{
@@ -227,6 +239,17 @@ public abstract class MyMongoRepository : MongoD
}
}
}
+
+
+public class MyMongoDbModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ //.......
+ context.Services
+ .Replace(ServiceDescriptor.Transient(typeof(IMongoDbRepositoryFilterer<,>),typeof(MyMongoDbRepositoryFilterer<,>)));
+ }
+}
````
> 参阅[MongoDb集成文档](MongoDB.md)的 "设置默认仓储" 部分了解如何使用自定义类替换默认仓储.
\ No newline at end of file
diff --git a/docs/zh-Hans/Data-Transfer-Objects.md b/docs/zh-Hans/Data-Transfer-Objects.md
index eb844e7ea7..5f8a1bc527 100644
--- a/docs/zh-Hans/Data-Transfer-Objects.md
+++ b/docs/zh-Hans/Data-Transfer-Objects.md
@@ -210,15 +210,15 @@ namespace AbpDemo
{
//Create the query
var query = _productRepository
- .OrderBy(input.Sorting)
- .Skip(input.SkipCount)
- .Take(input.MaxResultCount);
+ .OrderBy(input.Sorting);
//Get total count from the repository
var totalCount = await query.CountAsync();
//Get entities from the repository
- List products = await query.ToListAsync();
+ List products = await query
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount);.ToListAsync();
//Map entities to DTOs
List productDtos =
diff --git a/docs/zh-Hans/Tutorials/Todo/Index.md b/docs/zh-Hans/Tutorials/Todo/Index.md
new file mode 100644
index 0000000000..d940957c18
--- /dev/null
+++ b/docs/zh-Hans/Tutorials/Todo/Index.md
@@ -0,0 +1,830 @@
+# 快速入门
+
+````json
+//[doc-params]
+{
+ "UI": ["MVC", "Blazor", "BlazorServer", "NG"],
+ "DB": ["EF", "Mongo"]
+}
+````
+
+这是一个由单个部分组成的快速入门教程, 旨在使用ABP框架构建一个简单的待办事项应用程序. 这是最终应用程序的屏幕截图:
+
+
+
+你可以在[这里](https://github.com/abpframework/abp-samples/tree/master/TodoApp)找到已完成的项目源代码.
+
+## 先决条件
+
+* 一个集成开发环境 (比如: [Visual Studio](https://visualstudio.microsoft.com/vs/)) 它需要支持 [.NET 5.0+](https://dotnet.microsoft.com/download/dotnet) 的开发.
+
+{{if DB=="Mongo"}}
+
+* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/)
+
+{{end}}
+
+{{if UI=="NG"}}
+
+* [Node v14.x](https://nodejs.org/)
+
+{{end}}
+
+## 创建新的解决方案
+
+我们将使用[ABP CLI](../../CLI.md) 创建带有ABP框架的新解决方案. 你可以在命令行终端中运行以下命令来安装它:
+
+````bash
+dotnet tool install -g Volo.Abp.Cli
+````
+
+然后创建一个空文件夹, 打开命令行终端并在终端中执行以下命令:
+
+````bash
+abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}}
+````
+
+{{if UI=="NG"}}
+
+这将创建一个名为*TodoApp*的新解决方案, 其中包含`angular`和`aspnet core`文件夹. 一旦解决方案就绪, 请在你最喜欢的 IDE 中打开 ASP.NET Core 解决方案.
+
+{{else}}
+
+这将创建一个名为*TodoApp*的新解决方案. 一旦解决方案就绪, 请在你最喜欢的 IDE 中打开它.
+
+{{end}}
+
+### 创建数据库
+
+如果你使用的是Visual Studio, 请右键单击`TodoApp.DbMigrator`项目, 选择*设置为启动项目*, 然后按*Ctrl+F5*运行它而不进行调试. 它将创建初始数据库并生成初始数据.
+
+{{if DB=="EF"}}
+
+> 一些IDE (例如Rider) 可能会在第一次运行时遇到问题, 因为 *DbMigrator* 添加初始迁移并重新编译项目. 在这种情况下, 在 `.DbMigrator` 项目文件夹中打开命令行终端并执行 `dotnet run` 命令.
+
+{{end}}
+
+### 运行应用程序
+
+{{if UI=="MVC" || UI=="BlazorServer"}}
+
+最好在开始开发之前运行一下应用程序. 确保 {{if UI=="BlazorServer"}}`TodoApp.Blazor`{{else}}`TodoApp.Web`{{end}} 是启动项目, 然后运行应用程序(Visual Studio中是Ctrl+F5)来查看初始UI:
+
+{{else if UI=="Blazor"}}
+
+最好在开始开发之前运行一下应用程序. 解决方案中有两个主要应用程序
+
+* `TodoApp.HttpApi.Host` 承载服务器端的 HTTP API.
+* `TodoApp.Blazor` 是客户端的Blazor WebAssembly应用程序.
+
+确保 `TodoApp.HttpApi.Host` 是启动项目, 然后运行应用程序(Visual Studio中的Ctrl+F5)打开[Swagger UI](https://swagger.io/tools/swagger-ui/)来查看服务器端的 HTTP API:
+
+
+
+你可以使用此 UI 探索和测试你的 HTTP API. 现在, 我们可以将 `TodoApp.Blazor` 设置为启动项目并运行它来打开实际的 Blazor 应用程序UI:
+
+{{else if UI=="NG"}}
+
+最好在开始开发之前运行一下应用程序. 解决方案中有两个主要应用程序;
+
+* `TodoApp.HttpApi.Host` (在.NET解决方案中)承载服务器端HTTP API.
+* `Angular` 文件夹包含 Angular 应用程序.
+
+确保 `TodoApp.HttpApi.Host` 是启动项目, 然后运行应用程序(Visual Studio中的Ctrl+F5)打开[Swagger UI](https://swagger.io/tools/swagger-ui/)来查看服务器端的 HTTP API:
+
+
+
+你可以使用此 UI 探索和测试你的 HTTP API. 如果该功能正常, 我们可以运行Angular 客户端应用程序.
+
+首先, 运行以下命令来还原NPM包:
+
+````bash
+npm install
+````
+
+安装所有软件包需要一些时间. 然后可以使用以下命令运行应用程序:
+
+````bash
+npm start
+````
+
+此命令需要时间, 但最终在你的默认浏览器中运行并打开应用程序:
+
+{{end}}
+
+
+
+你可以单击 *登录* 按钮, 以`admin`作为用户名和`1q2w3E*` 作为密码登录到应用程序.
+
+一切就绪. 我们可以开始编程!
+
+## 领域层
+
+此应用程序只有一个 [实体](../../Entities.md), 接下来我们开始创建它. 在 *TodoApp.Domain* 项目中创建一个新的 `TodoItem` 类:
+
+````csharp
+using System;
+using Volo.Abp.Domain.Entities;
+
+namespace TodoApp
+{
+ public class TodoItem : BasicAggregateRoot
+ {
+ public string Text { get; set; }
+ }
+}
+````
+
+`BasicAggregateRoot` 是创建根实体的最简单的基础类. `Guid` 是这里实体的主键 (`Id`).
+
+## 数据库集成
+
+{{if DB=="EF"}}
+
+下一步是设置 [Entity Framework Core](../../Entity-Framework-Core.md)配置
+
+### 映射配置
+
+打开在 *TodoApp.EntityFrameworkCore* 项目中 `EntityFrameworkCore` 文件夹中的 `TodoAppDbContext` 类, 并向该类添加新的 `DbSet` 属性:
+
+````csharp
+public DbSet TodoItems { get; set; }
+````
+
+然后在 `TodoAppDbContext` 类中定位到 `OnModelCreating` 方法, 并为 `TodoItem` 实体添加映射代码:
+
+````csharp
+protected override void OnModelCreating(ModelBuilder builder)
+{
+ base.OnModelCreating(builder);
+
+ /* Include modules to your migration db context */
+
+ builder.ConfigurePermissionManagement();
+ ...
+
+ /* Configure your own tables/entities inside here */
+ builder.Entity(b =>
+ {
+ b.ToTable("TodoItems");
+ });
+}
+````
+
+我们已经将 `TodoItem` 实体映射到数据库中的 `TodoItems` 表.
+
+### Code First 迁移
+
+解决方案快速模版已经配置为使用Entity Framework Core的 [Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). 由于我们已经更改了数据库映射配置, 因此我们应该创建一个新的迁移并将更改应用于数据库.
+
+在 *TodoApp.EntityFrameworkCore* 项目目录中打开一个命令行终端并输入以下命令:
+
+````bash
+dotnet ef migrations add Added_TodoItem
+````
+
+这将向项目添加一个新的迁移类:
+
+
+
+你可以在同一命令行终端中使用以下命令将更改应用于数据库:
+
+````bash
+dotnet ef database update
+````
+
+> 如果你使用的是Visual Studio, 则可能希望在 *包管理器控制台 (PMC)* 中使用 `Add-Migration Added_TodoItem` 和 `Update-Database` 命令. 在这种情况下, 请确保 {{if UI=="MVC"}}`TodoApp.Web`{{else if UI=="BlazorServer"}}`TodoApp.Blazor`{{else if UI=="Blazor" || UI=="NG"}}`TodoApp.HttpApi.Host`{{end}} 是启动项目, 并且 `TodoApp.EntityFrameworkCore` 是PMC中的 *默认项目*.
+
+{{else if DB=="Mongo"}}
+
+下一步是设置 [MongoDB](../../MongoDB.md) 配置. 打开 *TodoApp.MongoDB* 项目中 `MongoDb` 文件夹的`TodoAppMongoDbContext` 类并做出以下更改;
+
+1. 向类添加新属性:
+
+````csharp
+public IMongoCollection TodoItems => Collection();
+````
+
+2. 在 `CreateModel` 方法中添加以下代码:
+
+````csharp
+modelBuilder.Entity(b =>
+{
+ b.CollectionName = "TodoItems";
+});
+````
+
+{{end}}
+
+现在, 我们可以使用ABP仓库保存和检索待办事项, 如我们在下一节所做的那样.
+
+## 应用层
+
+[应用程序服务](../../Application-Services.md) 用于执行应用程序的用例. 我们需要执行以下用例:
+
+* 获取待办事项列表
+* 创建新的待办事项
+* 删除现有的待办事项
+
+### 应用服务接口
+
+我们可以先从为应用程序服务定义接口开始. 在 *TodoApp.Application.Contracts* 项目中创建新的 `ITodoAppService` 接口, 如下所示:
+
+````csharp
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+
+namespace TodoApp
+{
+ public interface ITodoAppService : IApplicationService
+ {
+ Task> GetListAsync();
+ Task CreateAsync(string text);
+ Task DeleteAsync(Guid id);
+ }
+}
+````
+
+### 数据传输对象
+
+`GetListAsync` 和`CreateAsync` 方法返回 `TodoItemDto`. `应用程序服务` 通常获取并返回 DTO([数据传输对象](../../Data-Transfer-Objects.md)) 而不是实体. 因此, 我们应该在这里定义DTO类. 在 *TodoApp.Application.Contracts* 项目中创建新的 `TodoItemDto` 类:
+
+````csharp
+using System;
+
+namespace TodoApp
+{
+ public class TodoItemDto
+ {
+ public Guid Id { get; set; }
+ public string Text { get; set; }
+ }
+}
+````
+
+这是一个非常简单的DTO类, 它与我们的 `TodoItem` 实体相对应. 接下来, 我们准备实现 `ITodoAppService`接口.
+
+### 应用服务实现
+
+在 *TodoApp.Application* 项目中创建 `TodoAppService` 类, 如下所示:
+
+````csharp
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Domain.Repositories;
+
+namespace TodoApp
+{
+ public class TodoAppService : ApplicationService, ITodoAppService
+ {
+ private readonly IRepository _todoItemRepository;
+
+ public TodoAppService(IRepository todoItemRepository)
+ {
+ _todoItemRepository = todoItemRepository;
+ }
+
+ // TODO: Implement the methods here...
+ }
+}
+````
+
+该类继承自ABP框架的`ApplicationService`类, 并实现了之前定义的 `ITodoAppService`接口. ABP为实体提供默认的泛型 [仓储](../../Repositories.md). 我们可以使用它们来执行基本的数据库操作. 此类中 [注入](../../Dependency-Injection.md) 的 `IRepository`, 它就是 `TodoItem` 实体的默认存储库. 我们将使用它来实现之前描述的用例.
+
+#### 获取待办事项
+
+让我们先实现 `GetListAsync` 方法:
+
+````csharp
+public async Task> GetListAsync()
+{
+ var items = await _todoItemRepository.GetListAsync();
+ return items
+ .Select(item => new TodoItemDto
+ {
+ Id = item.Id,
+ Text = item.Text
+ }).ToList();
+}
+````
+
+我们只是从数据库中获取完整的`TodoItem`列表, 将它们映射到`TodoItemDto`对象并作为结果返回.
+
+#### 创建一个新的待办事项
+
+下一个我们可以实现方法是 `CreateAsync` , 如下所示:
+
+````csharp
+public async Task CreateAsync(string text)
+{
+ var todoItem = await _todoItemRepository.InsertAsync(
+ new TodoItem {Text = text}
+ );
+
+ return new TodoItemDto
+ {
+ Id = todoItem.Id,
+ Text = todoItem.Text
+ };
+}
+````
+
+仓储的 `InsertAsync` 方法将给定的`TodoItem`插入数据库, 并返回相同的`TodoItem` 对象. 它还设置了`Id`, 因此我们可以在返回对象上使用它. 我们只是通过从新的 `TodoItem` 实体创建和返回 `TodoItemDto`.
+
+#### 删除待办事项
+
+最后, 我们来实现 `DeleteAsync` 方法, 代码如下:
+
+````csharp
+public async Task DeleteAsync(Guid id)
+{
+ await _todoItemRepository.DeleteAsync(id);
+}
+````
+
+至此, 应用程序服务已准备好了让UI层来使用.
+
+## 用户界面层
+
+现在是在UI上显示待办事项的时候了! 在开始编写代码之前, 最好记住我们正在尝试构建的内容. 这里是示例程序的最终用户界面的截图:
+
+
+
+> **我们将在本教程中保持最简洁的UI端, 以使本教程简单且重点突出. 请参阅 [web应用程序开发教程](../Part-1.md) 来了解构建实际应用各个方面.**
+
+{{if UI=="MVC"}}
+
+### Index.cshtml.cs
+
+打开在 *TodoApp.Web* 项目的 `Pages` 文件夹中`Index.cshtml.cs`文件, 并用以下代码块替换它的默认内容:
+
+````csharp
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace TodoApp.Web.Pages
+{
+ public class IndexModel : TodoAppPageModel
+ {
+ public List TodoItems { get; set; }
+
+ private readonly ITodoAppService _todoAppService;
+
+ public IndexModel(ITodoAppService todoAppService)
+ {
+ _todoAppService = todoAppService;
+ }
+
+ public async Task OnGetAsync()
+ {
+ TodoItems = await _todoAppService.GetListAsync();
+ }
+ }
+}
+````
+
+此类使用 `ITodoAppService` 获取待办事项列表并将它赋值给 `TodoItems` 属性. 我们将用它来渲染razor页面上的待办事项目列表.
+
+### Index.cshtml
+
+打开 *TodoApp.Web* 项目中`Pages`文件夹下的`Index.cshtml`, 并替换为以下内容:
+
+````xml
+@page
+@model TodoApp.Web.Pages.IndexModel
+@section styles {
+
+}
+@section scripts {
+
+}
+
+
+
+
+ TODO LIST
+
+
+
+
+
+
+
+
+ @foreach (var todoItem in Model.TodoItems)
+ {
+